{"openapi":"3.1.0","info":{"title":"Intent Network service","description":"REST API beside the Intent Network relay. The wire contract is docs/protocol.md.","license":{"name":""},"version":"0.1.0"},"paths":{"/.well-known/intent":{"get":{"tags":["service"],"summary":"The service key, relays, event kinds, limits and trusted issuers.","operationId":"well_known","responses":{"200":{"description":"See docs/protocol.md","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/.well-known/ucp":{"get":{"tags":["service"],"summary":"The UCP platform profile buyers name in `UCP-Agent` when they re-read a checkout.","operationId":"ucp_profile","responses":{"200":{"description":"A UCP platform profile, spec 2026-08-25","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/health":{"get":{"tags":["service"],"summary":"Liveness only: always 200 with an empty body.","operationId":"health","responses":{"200":{"description":""}}}},"/v1/air/bindings":{"post":{"tags":["air"],"summary":"Ask for a link that binds this agent's AIR key to its user's AIR account.","description":"Send the link to the user. They log in to AIR there and approve, and the service binds\nthe agent's public key to their account. Poll `GET /v1/air/bindings/{id}` for the result.\nThe link expires after 15 minutes.","operationId":"create_binding","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewBinding"}}},"required":true},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Binding"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"This service does not bind agents to AIR","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]}},"/v1/air/bindings/{id}":{"get":{"tags":["air"],"summary":"One of your bindings: still pending, bound, or expired.","operationId":"status","parameters":[{"name":"id","in":"path","description":"The binding id from POST /v1/air/bindings","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Binding"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]}},"/v1/intents":{"get":{"tags":["intents"],"summary":"The public board: current versions of intents, oldest change first.","operationId":"list_intents","parameters":[{"name":"since","in":"query","description":"Return entries with a higher sequence number. Start at 0 and pass back `next`.","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"category","in":"query","description":"Category (`t` tag).","required":false,"schema":{"type":"string"}},{"name":"near","in":"query","description":"Geohash prefix of 2 to 5 characters.","required":false,"schema":{"type":"string"}},{"name":"country","in":"query","description":"ISO 3166-1 alpha-2 ship-to country.","required":false,"schema":{"type":"string"}},{"name":"author","in":"query","description":"Only intents by this author (hex public key).","required":false,"schema":{"type":"string"}},{"name":"badge","in":"query","description":"Only intents with a current badge for this claim, e.g. `xyz.yentin.intent.funded`.","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"`open` (default): open intents that have not expired. `all`: every current version.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"At most 200; default 50.","required":false,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Board"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"post":{"tags":["intents"],"summary":"Publish a signed intent, or a new version of one (to cancel or fulfil it).\nThe request must be signed with the intent's own key. Posting the same\nevent again returns 200 with the current state.","operationId":"publish_intent","requestBody":{"description":"A signed kind 30468 event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NostrEvent"}}},"required":true},"responses":{"200":{"description":"Already published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Published"}}}},"201":{"description":"Published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Published"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Not an allowed change to the current version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Expired, or contains forbidden data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]}},"/v1/intents/check":{"post":{"tags":["intents"],"summary":"Check a draft for data that must never be public, before signing it.","operationId":"check","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]}},"/v1/intents/{pubkey}/{d}":{"get":{"tags":["intents"],"summary":"One intent by its author and `d` value.","operationId":"get","parameters":[{"name":"pubkey","in":"path","description":"Author public key, hex","required":true,"schema":{"type":"string"}},{"name":"d","in":"path","description":"The intent's d tag","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Entry"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/v1/intents/{pubkey}/{d}/funding":{"post":{"tags":["funding"],"summary":"TEMPORARY, for the demo: issue a funded instruction credential to the buyer's AIR account.","description":"The service passes the claims to its AIR issuer without checking them with the payment\nenabler, so the credential is only the buyer agent's report\n(docs/decisions/0014-temporary-demo-funding.md). Only the author of an open intent can\nask, and only for an AIR email on the service's demo list. Asking twice issues twice.","operationId":"request","parameters":[{"name":"pubkey","in":"path","description":"Author public key, hex","required":true,"schema":{"type":"string"}},{"name":"d","in":"path","description":"The intent's d tag","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FundingRequest"}}},"required":true},"responses":{"201":{"description":"Issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Funded"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The intent is not open","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"502":{"description":"The issuer did not issue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"This service does not issue demo credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]}},"/v1/intents/{pubkey}/{d}/proof":{"post":{"tags":["badges"],"summary":"Prove that one of your intents is funded, and get a funded badge for it.","description":"Run AIR's verify-by-agent with the program `/.well-known/intent` lists for funded badges,\n`fieldsToDisclose` `\"*\"` and the intent's badge nonce (docs/protocol.md, \"Badge proof\"), then\nsend AIR's answer here, signed with the intent's own key. The service checks the issuer's\nsignature, the disclosures and the holder's key binding, asks the issuer whether the\ncredential was revoked, and publishes a badge signed with its key. Proving again with the\nsame credential returns 200 with the badge already published.","operationId":"prove_intent","parameters":[{"name":"pubkey","in":"path","description":"Author public key, hex","required":true,"schema":{"type":"string"}},{"name":"d","in":"path","description":"The intent's d tag","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProofSubmission"}}},"required":true},"responses":{"200":{"description":"This credential already badges the intent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Badged"}}}},"201":{"description":"Badged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Badged"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The intent is not open","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The proof did not pass the checks","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"502":{"description":"The issuer did not answer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"This service gives no funded badges, or the relay is down","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]}},"/v1/messages":{"get":{"tags":["messages"],"summary":"Gift wraps addressed to the authenticated caller.","operationId":"inbox","parameters":[{"name":"since","in":"query","description":"Return messages with a higher sequence number. Start at 0 and pass back `next`.","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"limit","in":"query","description":"At most 200; default 50.","required":false,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Inbox"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]},"post":{"tags":["messages"],"summary":"Publish a gift wrap and add it to its recipient's inbox. Posting the same\nevent again returns 200.","operationId":"publish_message","requestBody":{"description":"A signed kind 1059 gift wrap with one p tag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NostrEvent"}}},"required":true},"responses":{"200":{"description":"Already published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stored"}}}},"201":{"description":"Published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stored"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]}},"/v1/subscriptions":{"get":{"tags":["subscriptions"],"summary":"The caller's subscriptions. Secrets are not shown again.","operationId":"list_subscriptions","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscriptions"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]},"post":{"tags":["subscriptions"],"summary":"Subscribe to intents matching a filter. Deliveries go to `url`, signed with the returned secret.","operationId":"create_subscription","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubscription"}}},"required":true},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Created"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]}},"/v1/subscriptions/{id}/delete":{"post":{"tags":["subscriptions"],"summary":"Delete one of the caller's subscriptions. POST, because NIP-98 has no DELETE.","operationId":"delete","parameters":[{"name":"id","in":"path","description":"Subscription id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"security":[{"nip98":[]}]}}},"components":{"schemas":{"Badged":{"type":"object","required":["id","address","claim","assurance","expires_at","badge","seq"],"properties":{"address":{"type":"string","description":"`30468:<pubkey>:<d>`"},"assurance":{"type":"string","description":"How the service checked the proof: `holder-bound`."},"badge":{"$ref":"#/components/schemas/NostrEvent","description":"The signed kind 1985 badge, as published on the relay."},"claim":{"type":"string","description":"The claim the badge makes, e.g. `xyz.yentin.intent.funded`."},"expires_at":{"type":"integer","format":"int64","description":"Unix seconds after which the badge no longer counts.","minimum":0},"id":{"type":"string","description":"The badge event's id."},"seq":{"type":"integer","format":"int64","description":"Board sequence number of the intent, which changed with the badge."}}},"Binding":{"type":"object","required":["id","status","expires_at","partner_id","air_api","credential_api"],"properties":{"air_api":{"type":"string","description":"AIR API base URL, for agent sessions."},"credential_api":{"type":"string","description":"AIR Credential API base URL, for listing and verifying credentials."},"error":{"type":["string","null"],"description":"AIR's error code from the user's last failed attempt, while pending."},"expires_at":{"type":"integer","format":"int64","description":"Unix seconds after which the link no longer works."},"id":{"type":"string"},"key_id":{"type":["string","null"],"description":"Once bound: AIR's id for the agent key."},"partner_id":{"type":"string","description":"The partner the key is bound under; agent sessions send it as `x-partner-id`."},"status":{"type":"string","description":"`pending`, `bound` or `expired`."},"url":{"type":["string","null"],"description":"The page the user opens to log in to AIR and approve. Only while pending."},"user_id":{"type":["string","null"],"description":"Once bound: the user's AIR user id, which the agent signs into each session request."}}},"Board":{"type":"object","required":["intents","next"],"properties":{"intents":{"type":"array","items":{"$ref":"#/components/schemas/Entry"}},"next":{"type":"integer","format":"int64","description":"Pass as `since` to get the next page, or to poll for changes."}}},"CheckFlag":{"type":"object","required":["field","kind"],"properties":{"field":{"type":"string","description":"The field the problem is in, e.g. `summary` or `items[0].description`."},"kind":{"type":"string","description":"`money`, `email`, `phone`, `card_number`, `street_address`, `person_name` or `contact`."}}},"CheckRequest":{"type":"object","required":["title","summary","content"],"properties":{"content":{"type":"object","description":"The intent content object from docs/protocol.md."},"summary":{"type":"string"},"title":{"type":"string"}}},"CheckResponse":{"type":"object","required":["flags"],"properties":{"flags":{"type":"array","items":{"$ref":"#/components/schemas/CheckFlag"},"description":"Forbidden data found. Empty means the draft may be posted."},"jev":{"type":["string","null"],"description":"The Jev model that also checked the draft, or null when only the rules ran."}}},"CreateSubscription":{"type":"object","required":["url"],"properties":{"categories":{"type":"array","items":{"type":"string"},"description":"Match intents in any of these categories. Empty matches all."},"claims":{"type":"array","items":{"type":"string"},"description":"Match intents whose buyer offers to prove one of these claims."},"countries":{"type":"array","items":{"type":"string"},"description":"Match intents shipping to one of these ISO 3166-1 alpha-2 countries."},"near":{"type":"array","items":{"type":"string"},"description":"Match intents whose area has one of these geohash prefixes (2 to 5 characters)."},"profile":{"type":["string","null"],"description":"What you sell, in plain words. Used to rank matches."},"url":{"type":"string","description":"Where deliveries are POSTed. Must be https."}}},"Created":{"type":"object","required":["subscription","secret"],"properties":{"secret":{"type":"string","description":"Signs every delivery (see `Intent-Signature`). Shown only now: store it."},"subscription":{"$ref":"#/components/schemas/Subscription"}}},"Deleted":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean"}}},"Entry":{"type":"object","required":["seq","event","badges"],"properties":{"badges":{"type":"array","items":{"$ref":"#/components/schemas/NostrEvent"},"description":"The service's current badges for the intent, the newest unexpired one per claim: kind 1985\nevents signed by the service key (docs/protocol.md, \"Badge\"). Check them before trusting them."},"event":{"$ref":"#/components/schemas/NostrEvent"},"seq":{"type":"integer","format":"int64"}}},"ErrorBody":{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"}}},"ErrorDetail":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine-readable code, e.g. `INTENT_EXPIRED`."},"message":{"type":"string"}}},"Funded":{"type":"object","required":["issued","intent","vct","claims","expires_at"],"properties":{"claims":{"type":"object"},"expires_at":{"type":"integer","format":"int64","minimum":0},"intent":{"type":"string","description":"`30468:<pubkey>:<d>`"},"issued":{"type":"boolean"},"vct":{"type":"string","description":"The credential type."}}},"FundingRequest":{"type":"object","required":["email","claims","expires_at"],"properties":{"claims":{"type":"object","description":"The funded instruction credential's claims, as in docs/protocol.md."},"email":{"type":"string","description":"The email the buyer's human signs in to AIR with; the credential goes to that AIR account.\nIt is never published or logged."},"expires_at":{"type":"integer","format":"int64","description":"When the instruction ends, in Unix seconds. It becomes the credential's expiry.","minimum":0}}},"Inbox":{"type":"object","required":["messages","next"],"properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/InboxEntry"},"description":"Gift wraps addressed to the caller, oldest first."},"next":{"type":"integer","format":"int64"}}},"InboxEntry":{"type":"object","required":["seq","event"],"properties":{"event":{"$ref":"#/components/schemas/NostrEvent","description":"The gift wrap, as published."},"seq":{"type":"integer","format":"int64"}}},"NewBinding":{"type":"object","required":["public_key"],"properties":{"public_key":{"type":"string","description":"The agent's P-256 public key: base64 of its DER SubjectPublicKeyInfo."}}},"NostrEvent":{"type":"object","description":"A signed Nostr event (NIP-01).","required":["id","pubkey","created_at","kind","tags","content","sig"],"properties":{"content":{"type":"string"},"created_at":{"type":"integer","format":"int64","minimum":0},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"pubkey":{"type":"string"},"sig":{"type":"string"},"tags":{"type":"array","items":{"type":"array","items":{"type":"string"}}}}},"ProofSubmission":{"type":"object","required":["format","result"],"properties":{"format":{"type":"string","description":"How `result` is encoded: `air.verify-by-agent.v2`."},"result":{"type":"object","description":"AIR's response body from `POST /v2/credentials/verify-by-agent`, unmodified."}}},"Published":{"type":"object","required":["id","address","status","seq"],"properties":{"address":{"type":"string","description":"`30468:<pubkey>:<d>`"},"id":{"type":"string"},"seq":{"type":"integer","format":"int64","description":"Board sequence number of the intent's current version."},"status":{"type":"string"}}},"Stored":{"type":"object","required":["id","seq"],"properties":{"id":{"type":"string"},"seq":{"type":"integer","format":"int64","description":"The recipient's inbox sequence number for this message."}}},"Subscription":{"type":"object","required":["id","url","categories","near","countries","claims","created_at"],"properties":{"categories":{"type":"array","items":{"type":"string"}},"claims":{"type":"array","items":{"type":"string"}},"countries":{"type":"array","items":{"type":"string"}},"created_at":{"type":"integer","format":"int64","description":"Unix seconds."},"id":{"type":"string"},"near":{"type":"array","items":{"type":"string"}},"profile":{"type":["string","null"]},"url":{"type":"string"}}},"Subscriptions":{"type":"object","required":["subscriptions"],"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}}}}},"securitySchemes":{"nip98":{"type":"apiKey","in":"header","name":"Authorization","description":"`Nostr <base64 of a signed kind 27235 event>` (NIP-98)"}}},"tags":[{"name":"intents","description":"The public board"},{"name":"badges","description":"Funded badges, from proofs the service checks"},{"name":"funding","description":"TEMPORARY demo issuing of funded instruction credentials"},{"name":"air","description":"Binding an agent's own AIR key to its user's AIR account"},{"name":"messages","description":"Gift-wrapped private messages"},{"name":"subscriptions","description":"Signed webhooks for matching intents"},{"name":"service","description":"Health and discovery"}]}