{"id":13639989,"url":"https://github.com/robkorn/rust-urbit-http-api","last_synced_at":"2026-03-27T04:05:32.325Z","repository":{"id":54063201,"uuid":"322881758","full_name":"robkorn/rust-urbit-http-api","owner":"robkorn","description":"Wraps the Urbit ship http api exposing it as an easy-to-use Rust crate.","archived":false,"fork":false,"pushed_at":"2021-10-24T12:47:16.000Z","size":221,"stargazers_count":24,"open_issues_count":0,"forks_count":8,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-13T22:13:39.930Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robkorn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-19T15:46:12.000Z","updated_at":"2024-08-21T20:11:43.000Z","dependencies_parsed_at":"2022-08-13T06:20:57.011Z","dependency_job_id":null,"html_url":"https://github.com/robkorn/rust-urbit-http-api","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/robkorn/rust-urbit-http-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robkorn%2Frust-urbit-http-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robkorn%2Frust-urbit-http-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robkorn%2Frust-urbit-http-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robkorn%2Frust-urbit-http-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robkorn","download_url":"https://codeload.github.com/robkorn/rust-urbit-http-api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robkorn%2Frust-urbit-http-api/sbom","scorecard":{"id":780892,"data":{"date":"2025-08-11","repo":{"name":"github.com/robkorn/rust-urbit-http-api","commit":"dea317b5746355af7fbe422e823a92dda6dc4e9d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-23T04:53:06.261Z","repository_id":54063201,"created_at":"2025-08-23T04:53:06.261Z","updated_at":"2025-08-23T04:53:06.261Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31018546,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-27T03:51:26.850Z","status":"ssl_error","status_checked_at":"2026-03-27T03:51:09.693Z","response_time":164,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T01:01:06.772Z","updated_at":"2026-03-27T04:05:32.300Z","avatar_url":"https://github.com/robkorn.png","language":"Rust","funding_links":[],"categories":["Developer Tools"],"sub_categories":["HTTP APIs"],"readme":"# Rust Urbit HTTP API\n\nThis library wraps the Urbit ship http interface exposing it as an easy-to-use Rust crate.\n\n[![awesome urbit badge](https://img.shields.io/badge/~-awesome%20urbit-lightgrey)](https://github.com/urbit/awesome-urbit)\n\nAll implementation details such as auth cookies, EventSource connections, tracking message ids, and other such matters are automatically handled for you, and as enables a greatly improved experience in writing Rust apps that interact with Urbit ships.\n\nThis crate currently enables devs to:\n\n1. Authorize oneself and open a channel with the ship.\n2. Subscribe to any app/path so that one can read the events currently taking place inside of the ship.\n3. Issue pokes/scries/threads.\n4. Graph-store support with native Rust `Graph` interface for working with graphs.\n5. Simple rust-based interface for Urbit chats.\n6. Simple rust-based interface for Urbit notebooks.\n\n## Basic Design\n\nThere are 3 main structs that this library exposes for interacting with an Urbit ship:\n\n1. `ShipInterface`\n2. `Channel`\n3. `Subscription`\n\nA `Subscription` is created by a `Channel` which is created by a `ShipInterface`. In other words, first you need to connect to an Urbit ship (using `ShipInterface`) before you can initiate a messaging `Channel`, before you can create a `Subscription` to an app/path.\n\n### ShipInterface\n\nThe `ShipInterface` exposes a few useful methods that will be useful when creating apps.\n\nThe more commonly used methods below these allow you to create a new `ShipInterface` (thereby authorizing yourself with the ship), and create a new `Channel`.\n\n```rust\n/// Logs into the given ship and creates a new `ShipInterface`.\n/// `ship_url` should be `http://ip:port` of the given ship. Example:\n/// `http://0.0.0.0:8080`. `ship_code` is the code acquire from your ship\n/// by typing `+code` in dojo.\npub fn new(ship_url: \u0026str, ship_code: \u0026str) -\u003e Result\u003cShipInterface\u003e;\n\n/// Create a `Channel` using this `ShipInterface`\npub fn create_channel(\u0026mut self) -\u003e Result\u003cChannel\u003e;\n```\n\nYou also have the ability to scry and run threads via spider.\n\n```rust\n/// Send a scry using the `ShipInterface`\npub fn scry(\u0026self, app: \u0026str, path: \u0026str) -\u003e Result\u003cResponse\u003e;\n\n/// Run a thread via spider using the `ShipInterface`\npub fn spider(\u0026self, input_mark: \u0026str, output_mark: \u0026str, thread_name: \u0026str, body: \u0026JsonValue) -\u003e Result\u003cResponse\u003e;\n```\n\n### Channel\n\n`Channel` is the most useful struct, because it holds methods related to interacting with both pokes and subscriptions.\n\nIt is instructive to look at the definition of the `Channel` struct to understand how it works:\n\n```rust\n// A Channel which is used to interact with a ship\npub struct Channel\u003c'a\u003e {\n    /// `ShipInterface` this channel is created from\n    pub ship_interface: \u0026'a ShipInterface,\n    /// The uid of the channel\n    pub uid: String,\n    /// The url of the channel\n    pub url: String,\n    // The list of `Subscription`s for this channel\n    pub subscription_list: Vec\u003cSubscription\u003e,\n    // / The `EventSource` for this channel which reads all of\n    // / the SSE events.\n    event_receiver: ReceiverSource,\n    /// The current number of messages that have been sent out (which are\n    /// also defined as message ids) via this `Channel`\n    pub message_id_count: u64,\n}\n```\n\nOnce a `Channel` is created, an `EventSource` connection is created with the ship on a separate thread. This thread accepts all of the incoming events, and queues them on a (Rust) unbounded channel which is accessible internally via the `event_receiver`. This field itself isn't public, but processing events in this crate is handled with a much higher-level interface for the app developer.\n\nTake note that a `Channel` has a `subscription_list`. As you will see below, each `Channel` exposes methods for creating subscriptions, which automatically get added to the `subscription_list`.\nOnce `Subscription`s are created/added to the list, the `Channel` will evidently start to receive event messages via SSE (which will be queued for reading in the `event_receiver`).\n\nFrom the app developer's perspective, all one has to do is call the `parse_event_messages()` method on your `Channel`, and all of the queued events will be processed and passed on to the correct `Subscription`'s `message_list`. This is useful once multiple `Subscriptions` are created on a single channel, as the messages will be pre-sorted automatically for you.\n\nOnce the event messages are parsed, then one can simply call the `find_subscription` method in order to interact with the `Subscription` and read its messages.\n\nThe following are the useful methods exposed by a `Channel`:\n\n```rust\n/// Sends a poke over the channel\npub fn poke(\u0026mut self, app: \u0026str, mark: \u0026str, json: \u0026JsonValue) -\u003e Result\u003cResponse\u003e;\n\n/// Create a new `Subscription` and thus subscribes to events on the ship with the provided app/path.\npub fn create_new_subscription(\u0026mut self, app: \u0026str, path: \u0026str) -\u003e Result\u003cCreationID\u003e;\n\n/// Parses SSE messages for this channel and moves them into\n/// the proper corresponding `Subscription`'s `message_list`.\npub fn parse_event_messages(\u0026mut self);\n\n/// Finds the first `Subscription` in the list which has a matching\n/// `app` and `path`;\npub fn find_subscription(\u0026self, app: \u0026str, path: \u0026str) -\u003e Option\u003c\u0026Subscription\u003e;\n\n/// Finds the first `Subscription` in the list which has a matching\n/// `app` and `path`, removes it from the list, and tells the ship\n/// that you are unsubscribing.\npub fn unsubscribe(\u0026mut self, app: \u0026str, path: \u0026str) -\u003e Option\u003cbool\u003e;\n\n/// Deletes the channel\npub fn delete_channel(self);\n\n/// Exposes an interface for interacting with a ship's Graph Store directly.\npub fn graph_store(\u0026mut self) -\u003e GraphStore;\n\n/// Exposes an interface for interacting with Urbit chats.\npub fn chat(\u0026mut self) -\u003e Chat;\n\n/// Exposes an interface for interacting with Urbit notebooks.\npub fn notebook(\u0026mute self) -\u003e Notebook;\n\n```\n\n### Subscription\n\nAs mentioned in the previous section, a `Subscription` contains it's own `message_list` field where messages are stored after a `Channel` processes them.\n\nFrom an app developer's perspective, this is the only useful feature of the `Subscription` struct. Once acquired, it is used simply to read the messages.\n\nTo improve the message reading experience, the `Subscription` struct exposes a useful method:\n\n```rust\n/// Pops a message from the front of `Subscription`'s `message_list`.\n/// If no messages are left, returns `None`.\npub fn pop_message(\u0026mut self) -\u003e Option\u003cString\u003e;\n```\n\n## Code Examples\n\n### Poke Example\n\nThis example displays how to connect to a ship using a `ShipInterface`, opening a `Channel`, issuing a `poke` over said channel, and then deleting the `Channel` to finish.\n\n```rust\n// Import the `ShipInterface` struct\nuse urbit_http_api::ShipInterface;\n\nfn main() {\n    // Create a new `ShipInterface` for a local ~zod ship\n    let mut ship_interface =\n        ShipInterface::new(\"http://0.0.0.0:8080\", \"lidlut-tabwed-pillex-ridrup\").unwrap();\n    // Create a `Channel`\n    let mut channel = ship_interface.create_channel().unwrap();\n\n    // Issue a poke over the channel\n    let poke_res = channel.poke(\"hood\", \"helm-hi\", \u0026\"This is a poke\".into());\n\n    // Cleanup/delete the `Channel` once finished\n    channel.delete_channel();\n}\n```\n\n### Graph Store Subscription Example\n\nThis example shows how to create, interact with, and delete a `Subscription`. In this scenario we desire to read all new updates from Graph Store via our `Subscription` for 10 seconds, and then perform cleanup.\n\n```rust\nuse std::thread;\nuse std::time::Duration;\nuse urbit_http_api::ShipInterface;\n\nfn main() {\n    // Create a new `ShipInterface` for a local ~zod ship\n    let mut ship_interface =\n        ShipInterface::new(\"http://0.0.0.0:8080\", \"lidlut-tabwed-pillex-ridrup\").unwrap();\n    // Create a `Channel`\n    let mut channel = ship_interface.create_channel().unwrap();\n    // Create a `Subscription` for the `graph-store` app with the `/updates` path. This `Subscription`\n    // is automatically added to the `Channel`'s `subscription_list`.\n    channel\n        .create_new_subscription(\"graph-store\", \"/updates\")\n        .unwrap();\n\n    // Create a loop that iterates 10 times\n    for _ in 0..10 {\n        // Parse all of the event messages to move them into the correct\n        // `Subscription`s in the `Channel`'s `subscription_list`.\n        channel.parse_event_messages();\n\n        // Find our graph-store `Subscription`\n        let gs_sub = channel.find_subscription(\"graph-store\", \"/updates\").unwrap();\n\n        // Pop all of the messages from our `gs_sub` and print them\n        loop {\n            let pop_res = gs_sub.pop_message();\n            if let Some(mess) = \u0026pop_res {\n                println!(\"Message: {:?}\", mess);\n            }\n            // If no messages left, stop\n            if let None = \u0026pop_res {\n                break;\n            }\n        }\n\n        // Wait for 1 second before trying to parse the event messages again\n        thread::sleep(Duration::new(1, 0));\n    }\n\n    // Once finished, unsubscribe/destroy our `Subscription`\n    channel.unsubscribe(\"graph-store\", \"/updates\");\n    // Delete the channel\n    channel.delete_channel();\n}\n```\n\n### Urbit Chat Messaging Example\n\nThis example displays how to connect to a ship and send a message to an Urbit chat using the `Chat` struct interface.\n\n```rust\n// Import the `ShipInterface` struct\nuse urbit_http_api::{ShipInterface, chat::Message};\n\nfn main() {\n    // Create a new `ShipInterface` for a local ~zod ship\n    let mut ship_interface =\n        ShipInterface::new(\"http://0.0.0.0:8080\", \"lidlut-tabwed-pillex-ridrup\").unwrap();\n    // Create a `Channel`\n    let mut channel = ship_interface.create_channel().unwrap();\n\n    // Create a `Message` which is formatted properly for an Urbit chat\n    let message = Message::new()\n        // Add text to your message\n        .add_text(\"Checkout this cool article by ~wicdev-wisryt:\")\n        // Add a URL link to your message after the previous text (which gets automatically added on a new line)\n        .add_url(\"https://urbit.org/blog/io-in-hoon/\")\n        // Add an image URL to your message after the previous url (which gets automatically added on a new line as a rendered image)\n        .add_url(\"https://media.urbit.org/site/posts/essays/zion-canyon-1.jpg\");\n    // Send the message to a chat hosted by ~zod named \"test-93\".\n    // Note the connected ship must already have joined the chat in order to send a message to the chat.\n    let _mess_res = channel\n        .chat()\n        .send_message(\"~zod\", \"test-93\", \u0026message);\n\n    // Cleanup/delete the `Channel` once finished\n    channel.delete_channel();\n}\n```\n\n### Urbit Chat Subscription Example\n\nThis example shows how to utilize the higher-level `Chat` interface to subscribe to a chat and read all of the messages being posted in said chat.\n\n```rust\nuse std::thread;\nuse std::time::Duration;\nuse urbit_http_api::ShipInterface;\n\nfn main() {\n    // Create a new `ShipInterface` for a local ~zod ship\n    let mut ship_interface =\n        ShipInterface::new(\"http://0.0.0.0:8080\", \"lidlut-tabwed-pillex-ridrup\").unwrap();\n    // Create a `Channel`\n    let mut channel = ship_interface.create_channel().unwrap();\n    // Subscribe to a specific chat, and obtain a `Receiver` back which contains a stream of messages from the chat\n    let chat_receiver = channel\n        .chat()\n        .subscribe_to_chat(\"~mocrux-nomdep\", \"test-93\")\n        .unwrap();\n\n    // Create a loop that iterates 10 times\n    for _ in 0..10 {\n        // If a message has been posted to the chat, unwrap it and acquire the `AuthoredMessage`\n        if let Ok(authored_message) = chat_receiver.try_recv() {\n            // Pretty print the author ship @p and the message contents\n            println!(\n                \"~{}:{}\",\n                authored_message.author,\n                authored_message.message.to_formatted_string()\n            );\n        }\n        // Wait for 1 second before checking again\n        thread::sleep(Duration::new(1, 0));\n    }\n\n    // Delete the channel\n    channel.delete_channel();\n}\n```\n\n---\n\nThis library was created by ~mocrux-nomdep([Robert Kornacki](https://github.com/robkorn)).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobkorn%2Frust-urbit-http-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobkorn%2Frust-urbit-http-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobkorn%2Frust-urbit-http-api/lists"}