diff --git a/docs.json b/docs.json
index fdafde7..9004070 100644
--- a/docs.json
+++ b/docs.json
@@ -84,6 +84,7 @@
"iroh-services/quickstart",
"iroh-services/projects",
"iroh-services/access",
+ "iroh-services/tickets",
"iroh-services/support"
]
},
diff --git a/iroh-services/tickets.mdx b/iroh-services/tickets.mdx
new file mode 100644
index 0000000..9eb9591
--- /dev/null
+++ b/iroh-services/tickets.mdx
@@ -0,0 +1,183 @@
+---
+title: "Hosted Tickets"
+description: "Automatically exchange tickets between app instances using Iroh Services"
+---
+
+Tickets tell your application what to do. They bundle connection details and
+application-specific data into a single token that one app instance can create
+and others can consume. The tickets service gives you a central place to
+publish and discover these tickets, so your users don't need to copy-paste
+strings or scan QR codes to get connected.
+
+
+New to tickets? Read the [Tickets concept page](/concepts/tickets) first for
+background on how tickets work in iroh.
+
+
+## The Problem
+
+Iroh tickets are powerful — they contain everything needed to connect to a peer
+and start working. But getting a ticket from one device to another has always
+required an outside channel: a text message, a QR code, a shared clipboard.
+
+For many applications, that manual handoff is friction you'd rather remove. If
+your users are already signed into a service, why should they need to text
+themselves a ticket?
+
+## How It Works
+
+The tickets service is a central server where your application can **publish**
+and **fetch** tickets scoped to your [project](/iroh-services/projects). Any
+endpoint connected to your project can list and retrieve tickets published by
+other endpoints in the same project.
+
+The workflow is simple:
+
+1. **Alice publishes a ticket** — her app creates a ticket and publishes it to
+ the service with a name
+2. **Bob lists or fetches tickets** — his app queries the service and gets
+ Alice's ticket back
+3. **Bob connects to Alice** — using the ticket's connection details, Bob's app
+ connects directly to Alice
+
+Tickets are **live** — they're only available while the publishing endpoint is
+online. When Alice's endpoint disconnects, her tickets are automatically removed.
+
+## Defining a Ticket Type
+
+Tickets use the exact same format you'd use for a QR code or copy-paste. The
+key principle is: **tickets tell your application what to do, and there are many
+ways to get tickets between app instances**. The tickets service is one of those
+ways, but the ticket format itself is transport-agnostic.
+
+Your application should define a custom ticket type that carries
+whatever data your app needs:
+
+```rust
+use iroh::EndpointId;
+use iroh_tickets::Ticket;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct TopicTicket {
+ message: String,
+ endpoint_id: EndpointId,
+}
+
+impl Ticket for TopicTicket {
+ const KIND: &'static str = "coolapp";
+
+ fn to_bytes(&self) -> Vec {
+ postcard::to_stdvec(&self).expect("serialization failed")
+ }
+
+ fn from_bytes(bytes: &[u8]) -> Result {
+ Ok(postcard::from_bytes(bytes)?)
+ }
+}
+```
+
+The `KIND` constant is a short string prefix that identifies your ticket type.
+This is what you'd see at the start of a ticket string — the same string whether
+the ticket comes from a QR code, a URL, or the tickets service.
+
+Design your ticket type to carry the information your app needs to act on it.
+Include endpoint IDs for connection details, content hashes for data references,
+topic names, capabilities — whatever makes sense for your use case.
+
+## Publishing Tickets
+
+To publish a ticket, create your n0des client and call `publish_ticket` with a
+name and your ticket value:
+
+```rust
+use iroh::Endpoint;
+use iroh_n0des::Client;
+
+let endpoint = Endpoint::bind().await?;
+let client = Client::builder(&endpoint)
+ .api_secret_from_env()?
+ .build()
+ .await?;
+
+let ticket = TopicTicket {
+ message: "cool_pokemon".to_string(),
+ endpoint_id: endpoint.id(),
+};
+
+client.publish_ticket("alice_cool_pokemon", ticket).await?;
+```
+
+Ticket names are strings scoped to your project. Choose a naming convention
+that works for your application — for example, prefixing with a username or
+device identifier.
+
+## Fetching & Listing Tickets
+
+Other endpoints in your project can fetch a specific ticket by name, or list
+all available tickets:
+
+```rust
+// Fetch a specific ticket by name
+let ticket = client
+ .fetch_ticket::("alice_cool_pokemon")
+ .await?;
+
+if let Some(published) = ticket {
+ println!("found ticket: {:?}", published.ticket);
+ println!("published by: {}", published.name);
+}
+
+// List all tickets in the project
+let all_tickets = client
+ .fetch_tickets::(0, 100)
+ .await?;
+
+for t in &all_tickets {
+ println!("{}: {:?}", t.name, t.ticket);
+}
+```
+
+## Tickets Go Offline With You
+
+Published tickets are live — they're available as long as the publishing endpoint
+is connected. When an endpoint disconnects, its tickets are automatically
+cleaned up. This keeps the ticket list fresh and avoids stale entries pointing
+to peers that are no longer reachable.
+
+```rust
+// Alice publishes a ticket
+alice_client.publish_ticket("my_topic", ticket).await?;
+
+// Bob can see it
+let tickets = bob_client.fetch_tickets::(0, 100).await?;
+assert_eq!(tickets.len(), 1);
+
+// Alice goes offline
+alice_endpoint.close().await;
+
+// After a short delay, the ticket is cleaned up
+let tickets = bob_client.fetch_tickets::(0, 100).await?;
+assert_eq!(tickets.len(), 0);
+```
+
+## One Format, Many Channels
+
+The ticket format is the same regardless of how it's delivered. A `TopicTicket`
+published through the tickets service serializes to the same string as one
+shared via a QR code or pasted into a chat. This means your application can
+support multiple discovery methods without changing its ticket handling:
+
+- **Tickets service** for automatic discovery between signed-in users
+- **QR codes** for in-person device pairing
+- **Deep links** for sharing via messaging apps
+- **Copy-paste** for developer workflows and debugging
+
+Your app only needs to know how to *handle* a ticket — how the ticket arrives
+is a separate concern.
+
+## Next Steps
+
+- [Tickets concept page](/concepts/tickets): How tickets work under the hood
+- [Projects](/iroh-services/projects): Understanding project scoping
+- [Quickstart](/iroh-services/quickstart): Set up your first Iroh Services app