{"openapi":"3.1.0","info":{"title":"Primitive API","version":"1.0.0","description":"Primitive is email infrastructure for AI agents. The Primitive API lets you manage domains, emails, webhook endpoints,\nfilters, and account settings programmatically.\n\n## Authentication\n\nMost endpoints require a Bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer prim_<your_api_key>\nAuthorization: Bearer prim_oat_<oauth_access_token>\n```\n\nAPI keys and OAuth access tokens are org-scoped. Create and manage them in your dashboard\nunder Settings > API Keys. CLI login plus CLI/agent signup endpoints\nexplicitly declare `security: []`; they do not require an API key because\nthey are used to create OAuth CLI sessions.\n\n## Rate Limiting\n\nThe v1 API allows **3000 requests per 60 seconds** per Developer, Power, or Platinum organization. API keys, OAuth access tokens, and connected agent keys share their organization's budget. Agent-tier organizations allow **120 requests per 60 seconds**. Requests without valid organization credentials use a separate **120 requests per 60 seconds per IP** limit. A request that exceeds its limit receives HTTP 429 with a `Retry-After` header (seconds) and, where the limiter reports them, `ratelimit-limit`, `ratelimit-remaining`, `ratelimit-reset` and `ratelimit-policy` headers describing the limiter that rejected it. Outbound sends and some resources have additional limits, each with its own limit and window.\n\n## Timestamps\n\nDate filters (`date_from` / `date_to`) and schedule times\n(`scheduled_at`) accept an ISO 8601 timestamp with `Z` or a numeric\nUTC offset, such as `2026-10-02T00:00:00Z` or\n`2026-10-02T00:00:00-04:00`. An offset is read as the instant it names\nand converted to UTC. A time with no zone, or a bare date, is rejected\nwith a `validation_error`. Timestamps the API returns are UTC.\n\n## Pagination\n\nList endpoints use cursor-based pagination. Responses include a\n`meta` object with `total`, `limit`, and `cursor` fields. Pass the\n`cursor` value as a query parameter to fetch the next page. When\n`cursor` is `null`, there are no more results.\n\n## Response Format\n\nAll responses use a consistent envelope:\n\n```json\n{\n  \"success\": true,\n  \"data\": { ... },\n  \"meta\": { \"total\": 42, \"limit\": 50, \"cursor\": \"...\" }\n}\n```\n\nErrors follow the same pattern:\n\n```json\n{\n  \"success\": false,\n  \"error\": { \"code\": \"not_found\", \"message\": \"Email not found\" }\n}\n```\n\n## Webhook signing\n\nOutbound webhook deliveries (configured via the `endpoints` API)\nare signed so receivers can verify they came from Primitive and\nhave not been tampered with in transit. The signing scheme is\ndeliberately simple so it can be reimplemented in any language\nin a few lines. The Node SDK's `verifyWebhookSignature` helper\nis the reference implementation; the wire details below let you\nwrite a verifier in Python, Go, Ruby, etc. without reading our\nsource.\n\n**Header**: `Primitive-Signature: t=<unix-seconds>,v1=<hex>`\n\nA legacy `MyMX-Signature` header is also sent on every delivery\nwith the same value, retained for back-compatibility with\nintegrations written before the rename. New code should read\n`Primitive-Signature`.\n\n**Signed string**: `${timestamp}.${rawBody}` where `timestamp`\nis the Unix-seconds integer from the `t=` parameter and\n`rawBody` is the exact bytes of the HTTP request body BEFORE\nany JSON decoding. Verify against the raw body, not a\nre-serialized parse, or you will silently mismatch on\ninsignificant whitespace.\n\n**Signature**: HMAC-SHA256 of the signed string, hex-encoded\n(lowercase). Use the account's webhook secret as the HMAC key,\nas a UTF-8 byte sequence.\n\n**Secret**: returned by `GET /account/webhook-secret`. The\nstring looks base64-shaped (e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`)\nbut is NOT base64; use it AS-IS as a UTF-8 string for the HMAC\nkey. Base64-decoding before HMAC will silently produce\nmismatched signatures.\n\n**Tolerance**: by convention, reject deliveries whose `t=`\ntimestamp is more than 5 minutes off your wall-clock to defend\nagainst replay attacks. The Node SDK's helper enforces this by\ndefault.\n\n**Verification recipe** (any language):\n\n```\n1. Read the raw HTTP body (do not parse).\n2. Read `Primitive-Signature: t=<ts>,v1=<sig>`.\n3. Reject if abs(now - ts) > 300 seconds.\n4. expected = HMAC_SHA256_hex(secret_utf8, f\"{ts}.{rawBody}\")\n5. Constant-time compare expected to sig. Reject if not equal.\n```\n\nFor Node, use `verifyWebhookSignature` from\n`@primitivedotdev/sdk/webhook` (or the higher-level\n`handleWebhook` helper if you want a one-liner). For other\nlanguages, the recipe above is everything you need.\n\nTest deliveries: `POST /endpoints/{id}/test` triggers a fake\ndelivery to your endpoint URL, signed with your real account\nsecret, so you can confirm verification end-to-end without\nneeding real inbound mail. The test response carries the exact\n`signature` header value sent on the wire so you can compare\nstrings directly.\n\n\n## Errors\n\nAPI errors use the same JSON envelope (`{ \"success\": false, \"error\": { \"code\", \"message\" } }`), served as `application/json` with HTTP status codes. This is Primitive's typed error envelope. The health endpoint instead returns its typed dependency-status payload on HTTP 503. The `error.code` is a stable machine-readable string and `error.message` is human-readable.\n\n## Authorization and roles\n\nOrganization API keys (`prim_`) act at member level, except credit code redemption (`POST /credits/redeem`), which uses the current owner or admin role of the user who created the key. OAuth access tokens (`prim_oat_`) use the authorizing user's current organization role, resolved per request. Agent connection management requires an organization owner or admin. Agent connection keys (`pconn_`) permit bound-address mail operations, organization address-note reads, own-address note writes, exact-address self-disconnect, network discovery when can_view is enabled, and recipient-bound network contact admission. Other operations are denied. Arbitrary configurable per-key scopes are not exposed. Billing and organization settings are owner/admin dashboard actions outside this API; the one exception is redeeming a credit code, which requires an owner or admin.\n\n## Versioning\n\nThe current stable API is **v1**. All endpoints are served under `/v1/` and are covered by a backward-compatibility guarantee: existing fields and status codes will not change without a deprecation notice.\n\nBreaking changes are announced at least 6 months in advance via changelog and email. Deprecated operations and fields are marked with the standard OpenAPI `deprecated: true` flag and carry a plain-English description of the replacement. A request that uses a deprecated operation or field still works and its response carries a `Deprecation` header (RFC 9745) and a `Link: <https://docs.primitive.dev/docs/versioning>; rel=\"deprecation\"` header, plus a `Sunset` header (RFC 8594) with the removal date once one is scheduled. The `v1` path prefix is guaranteed stable indefinitely; backward-compatible additions (new optional fields, new endpoints) may be made at any time without a version bump.","contact":{"name":"Primitive","url":"https://primitive.dev"},"license":{"name":"Proprietary","url":"https://primitive.dev/terms"},"x-stability-level":"stable","x-deprecation-policy":"Breaking changes are announced at least 6 months in advance. Deprecated operations and fields carry the standard deprecated: true flag, and responses to requests that use them carry Deprecation (RFC 9745) and Link rel=\"deprecation\" headers, plus Sunset (RFC 8594) once a removal date is scheduled. The current stable version is v1. Policy: https://docs.primitive.dev/docs/versioning"},"servers":[{"url":"https://api.primitive.dev/v1","description":"Canonical API host (PRIMITIVE_API_BASE_URL). Carries every public API operation."}],"tags":[{"name":"Repeating Sends","description":"A send or reply with `repeat` goes out now and then again every\n`every_minutes` in the same thread. Each message carries a\n`repeat.tick/1` interaction. The recipient stops a repeat it may stop\nthrough `/emails/{id}/repeat-stop`; its creator manages it here.\n","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/repeating-sends"},{"name":"Agent Connections","description":"Manage address-bound connections to external agent runtimes.","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/agent-connections"},{"name":"CLI","description":"Browser-assisted CLI authentication","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/cli"},{"name":"Agent","description":"Agent signup and authentication","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/agent"},{"name":"Account","description":"Manage your account settings, storage, and webhook secret","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/account"},{"name":"Credits","description":"Redeem credit codes and read the organization credit balance","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/credits"},{"name":"Domains","description":"Claim, verify, and manage email domains","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/domains"},{"name":"Inbox","description":"Check inbound email setup and processing readiness","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/inbox"},{"name":"Emails","description":"List, inspect, and manage received emails","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/emails"},{"name":"Search","description":"Semantic and hybrid search across received and sent mail","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/search"},{"name":"Sending","description":"Send outbound emails through the Primitive API","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/sending"},{"name":"Threads","description":"Conversation threads spanning received and sent emails","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/threads"},{"name":"Endpoints","description":"Manage webhook endpoints that receive email events","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/endpoints"},{"name":"Filters","description":"Manage whitelist and blocklist filter rules","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/filters"},{"name":"Routes","description":"Recipient routing: route inbound mail to a single destination per recipient\naddress. Rules bind an address pattern (exact or wildcard) to an endpoint;\n`function_id` routes an address to a function, minting its route-target\nendpoint.\n","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/routes"},{"name":"Payments","description":"Collect and pay stablecoin (USDC) payments with x402. Settlement is\nnon-custodial: funds move directly from payer to payee on-chain via an\nEIP-3009 authorization the payer signs with their own key, and Primitive\nnever holds funds. The payee registers a payout address and creates a\nchallenge; the payer signs and settles it under a configurable spend\npolicy (kill-switch, per-payment and per-day caps, payee allowlist).\n","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/payments"},{"name":"Wake","description":"Wake scheduling: schedule and send typed wake commands to your own\nfunctions over real DKIM-signed email on a cron cadence, and manage the\nper-target allowlist that authorizes which senders may wake a function.\n","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/wake"},{"name":"Webhook Deliveries","description":"View and replay webhook delivery attempts","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/webhook-deliveries"},{"name":"Functions","description":"Deploy JavaScript handlers that run on inbound mail. Each function\nis a single ESM module whose default export is an object with an\nasync `fetch(request, env)` method, in the shape of a Workers-style\nhandler. Primitive signs each delivery and forwards the\n`Primitive-Signature` header to the handler; verify the raw request\nbody with `PRIMITIVE_WEBHOOK_SECRET` before trusting the parsed event.\nThe `event` field is `email.received` for normal inbound mail, or a\nmachine-mail type (`email.bounced`, `email.tls_report`,\n`email.dmarc_report`, `email.dmarc_failure`) for bounces and reports;\nthe payload shape is otherwise identical. Code runs on\nPrimitive's edge runtime; there is no infrastructure to manage.\nSecrets land in `env` as encrypted bindings and are refreshed on\nevery redeploy.\n","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/functions"},{"name":"Memories","description":"Durable org-scoped or function-scoped JSON key-value storage for\nagents and functions. Keys are caller-defined. Function scope is\nalways addressed by the function id UUID, not by function name.\n","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/memories"},{"name":"Templates","description":"Browse approved Function templates, install deploy-mode templates, and\npoll install progress.\n","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/templates"},{"name":"Registries","description":"The Agent Registry: ownable directories of agents, addressable by a\nregistry-scoped handle. A registry's publish policy (owner_only, request,\nor open) decides whether a publish lists immediately or pends owner\napproval. An agent is defined once with a globally unique,\nreachability-verified address, then published into any registry under a\nhandle. Discovery reads (list, resolve, get) are public for public\nregistries; managing a registry and moderating requests use the owner's\nAPI key.\n","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/registries"},{"name":"Agent Networks","description":"Organization agent discovery by email address. Every organization has a\nprivate default network. Owners manage membership and visibility;\nconnected agents and organization members discover listed peers. Network membership does not grant\npermission to perform tasks, use tools, or read private information.\n","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/agent-networks"},{"name":"Contacts","description":"Organization contacts and per-address agent preferences.","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/contacts"},{"name":"Address Notes","description":"Private organization annotations and selectively public JSON notes about email addresses.","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/address-notes"},{"name":"Changes","description":"One replayable long-poll of what changed (ids and kinds) across mail, sent mail, read state, the agent directory and access, so a client can keep its snapshots current without polling every list.","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/changes"},{"name":"Demo","description":"Public, no-account sandbox operations that mirror authenticated endpoints with synthetic data so an agent can learn the request and response shapes before signing up.","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/demo"},{"name":"Discovery","description":"Unauthenticated entry point that lists the API base URL, how to obtain credentials, and the operations callable without a token.","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/discovery"},{"name":"Service","description":"Operational endpoints such as the unauthenticated health check, which reports whether the API can currently reach the dependencies it needs to serve requests.","x-openapi-spec":"https://api.primitive.dev/v1/openapi/tags/service"}],"paths":{"/cli/login/start":{"post":{"operationId":"startCliLogin","summary":"Start CLI browser login","description":"Starts a browser-assisted CLI login session. The response includes a\ndevice code for polling and a user code that the user approves in the\nbrowser. This endpoint does not require an API key.\n","tags":["CLI"],"security":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartCliLoginInput"}}}},"responses":{"201":{"description":"CLI login session created","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CliLoginStartResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/cli/login/poll":{"post":{"operationId":"pollCliLogin","summary":"Poll CLI browser login","description":"Polls a CLI login session until the browser approval either succeeds,\nis denied, expires, or is polled too quickly. The OAuth token set is\ncreated only after approval and is returned exactly once.\n","tags":["CLI"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PollCliLoginInput"}}}},"responses":{"200":{"description":"CLI login approved and OAuth token set created","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CliLoginPollResult"}}}]}}}},"400":{"description":"Invalid request, pending authorization, slow polling, expired token, or invalid device code","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before polling again when the error code is `slow_down`"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"authorization_pending":{"summary":"Awaiting browser approval","value":{"success":false,"error":{"code":"authorization_pending","message":"CLI login is still pending browser approval"}}},"expired_token":{"summary":"Login session expired","value":{"success":false,"error":{"code":"expired_token","message":"CLI login code expired; run primitive login again"}}},"invalid_device_code":{"summary":"Unknown device code","value":{"success":false,"error":{"code":"invalid_device_code","message":"Invalid CLI login device code"}}},"slow_down":{"summary":"Polling too quickly","value":{"success":false,"error":{"code":"slow_down","message":"Polling too quickly; slow down and retry later"}}}}}}},"403":{"description":"CLI login was denied in the browser","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"access_denied","message":"CLI login was denied in the browser"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/cli/signup/start":{"post":{"operationId":"startCliSignup","summary":"Start CLI account signup","description":"Starts a terminal-native CLI signup. The API creates a pending\nsignup session, sends an email verification code, and returns an opaque\nsignup token used by the resend and verify steps. This endpoint\ndoes not require an API key.\n","tags":["CLI"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartCliSignupInput"}}}},"responses":{"201":{"description":"CLI signup session created and verification email sent","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CliSignupStartResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/cli/signup/resend":{"post":{"operationId":"resendCliSignupVerification","summary":"Resend CLI signup verification code","description":"Sends a new email verification code for a pending CLI signup session.\nThis endpoint does not require an API key.\n","tags":["CLI"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendCliSignupVerificationInput"}}}},"responses":{"200":{"description":"Verification email resent","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CliSignupResendResult"}}}]}}}},"400":{"description":"Invalid token or expired token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Global rate limit exceeded or resend requested too quickly","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/cli/signup/verify":{"post":{"operationId":"verifyCliSignup","summary":"Verify CLI signup and create OAuth session","description":"Verifies the email code for a CLI signup session and creates the\naccount. An org-scoped OAuth CLI session is created and the\ntoken set is returned exactly once. This endpoint does not\nrequire an API key.\n","tags":["CLI"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyCliSignupInput"}}}},"responses":{"200":{"description":"CLI signup verified and OAuth token set created","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CliSignupVerifyResult"}}}]}}}},"400":{"description":"Invalid request, invalid verification code, expired token, rejected password, or account creation failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/agent/signup/start":{"post":{"operationId":"startAgentSignup","summary":"Start agent account signup","description":"Starts an agent-native signup session. The API creates a pending\nsignup session, sends an email verification code, and returns an opaque\nsignup token used by the resend and verify steps. This endpoint\ndoes not require an API key.\n","tags":["Agent"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartAgentSignupInput"}}}},"responses":{"201":{"description":"Agent signup session created and verification email sent","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AgentSignupStartResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/agent/signup/resend":{"post":{"operationId":"resendAgentSignupVerification","summary":"Resend agent signup verification code","description":"Sends a new email verification code for a pending agent signup session.\nThis endpoint does not require an API key.\n","tags":["Agent"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendAgentSignupVerificationInput"}}}},"responses":{"200":{"description":"Verification email resent","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AgentSignupResendResult"}}}]}}}},"400":{"description":"Invalid token or expired token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Global rate limit exceeded or resend requested too quickly","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/agent/signup/verify":{"post":{"operationId":"verifyAgentSignup","summary":"Verify agent signup and create OAuth tokens","description":"Verifies the email code for an agent signup session and creates\nthe account when needed. An org-scoped OAuth session for CLI\nauthentication is minted and the raw tokens are returned exactly\nonce. For existing users, the optional `org_id` selects which\naccessible workspace should receive the new session.\n","tags":["Agent"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAgentSignupInput"}}}},"responses":{"200":{"description":"Agent signup verified and OAuth tokens created","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AgentSignupVerifyResult"}}}]}}}},"400":{"description":"Invalid request, invalid verification code, expired token, or account creation failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"409":{"description":"Existing account is not in a usable workspace state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/agent/accounts":{"post":{"operationId":"createAgentAccount","summary":"Create an emailless agent account","description":"Creates an emailless agent account without authentication and returns a\none-time API key (prefixed `prim_`) plus a provisioned managed inbox.\nThe account is on the `agent` plan: reply-only (it can send only to\naddresses that have already sent it authenticated mail) with tight send\nlimits. Use the returned `api_key` as a Bearer token on later calls. The\naccount can be upgraded to a full developer account by confirming an\nemail through the claim flow. Upgrading raises the send cap and grants\nthe developer plan's default features (such as Functions); it does not\nunlock sending to arbitrary recipients, because the recipient rules on\nthe account still apply. `GET /send-permissions` reports those rules\n(its list of individual addresses can be partial), and\n`POST /sendability` answers for one specific recipient. This endpoint\ndoes not require an API key.\n","tags":["Agent"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentAccountInput"}}}},"responses":{"200":{"description":"Agent account created; the API key is returned once","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AgentAccountResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/agent/claim/start":{"post":{"operationId":"startAgentClaim","summary":"Start an agent account email claim","description":"Begins upgrading an emailless `agent` account into a full `developer`\naccount by confirming an email address. Authenticated by the agent's own\nAPI key (the org is taken from the credential). Sends a verification\ncode to the supplied email and returns the claim session id plus resend\ntiming. Submit the code to `/agent/claim/verify` to complete the\nupgrade. Confirming an email that already belongs to a Primitive account\nis rejected.\n","tags":["Agent"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartAgentClaimInput"}}}},"responses":{"200":{"description":"Claim started and verification email sent","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AgentClaimStartResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The email is already in use, or the account is not claimable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/agent/claim/verify":{"post":{"operationId":"verifyAgentClaim","summary":"Verify an agent account email claim","description":"Confirms the verification code emailed by `/agent/claim/start` and\nupgrades the account to the `developer` plan. The org id, API key, and\nmanaged inbox all carry over; the send cap lifts and the developer\nplan's default features (such as Functions) unlock. Upgrading does not\nunlock sending to arbitrary recipients: the recipient rules on the\naccount still apply. `GET /send-permissions` reports those rules (its\nlist of individual addresses can be partial), and `POST /sendability`\nanswers for one specific recipient. Authenticated by the agent's own\nAPI key.\n","tags":["Agent"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAgentClaimInput"}}}},"responses":{"200":{"description":"Claim verified; account upgraded to developer","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AgentClaimResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The account is already claimed, or the email is in use","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"The claim or its verification code has expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/agent/claim/link":{"post":{"operationId":"createAgentClaimLink","summary":"Create a browser claim link","description":"Mints an opaque, single-use link an agent can hand to a human to\ncomplete the email-confirmation upgrade in a browser. Authenticated by\nthe agent's own API key. `claim_url` is null when the API host cannot\nresolve a web origin to build the link.\n","tags":["Agent"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentClaimLinkInput"}}}},"responses":{"200":{"description":"Claim link created","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"Always `no-store`"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AgentClaimLinkResult"}}}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The account is not claimable (not an agent account, or already claimed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/cli/logout":{"post":{"operationId":"cliLogout","summary":"Revoke the current CLI OAuth session","description":"Revokes the OAuth grant used to authenticate the request. API-key\nauthenticated legacy logout requests succeed without deleting server API\nkeys so old local CLI state can be cleared safely.\n","tags":["CLI"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CliLogoutInput"}}}},"responses":{"200":{"description":"CLI logout completed","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CliLogoutResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/account":{"get":{"operationId":"getAccount","summary":"Get account info","tags":["Account"],"responses":{"200":{"description":"Account details","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Account"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Retrieve the authenticated organization's account: organization identity, current plan, usage, and onboarding state."},"patch":{"operationId":"updateAccount","summary":"Update account settings","tags":["Account"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAccountInput"}}}},"responses":{"200":{"description":"Updated account","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AccountUpdated"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Update the mutable account settings (such as display name and notification preferences) for the authenticated organization."}},"/account/storage":{"get":{"operationId":"getStorageStats","summary":"Get storage usage","tags":["Account"],"responses":{"200":{"description":"Storage statistics","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/StorageStats"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Return stored-email storage usage for the authenticated organization: the plan's included storage, the limit past which inbound mail is refused (null when it never is, as on paid plans), and the storage billed as overage. `quota_mb` is the included allowance, not a limit. The search index is billed as storage with mail (`search_index_bytes`) but never counts toward the limit."}},"/account/webhook-secret":{"get":{"operationId":"getWebhookSecret","summary":"Get webhook signing secret","description":"Returns the webhook signing secret for your account. If no\nsecret exists yet, one is generated automatically on first\naccess.\n\nSigning is account-scoped, not per-endpoint. Every webhook\ndelivery from any of your registered endpoints is signed\nwith this single secret. Rotate via\n`POST /account/webhook-secret/rotate`.\n\n**Secret format**: the returned string looks base64-shaped\n(e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`) but is NOT base64.\nUse it AS-IS as a UTF-8 string when computing HMAC over a\ndelivery body. Base64-decoding before HMAC will silently\nproduce mismatched signatures.\n\nSee the API-level \"Webhook signing\" section for the full\nwire format (header name, signed string shape, hash algo,\ntolerance) including a language-agnostic verification\nrecipe.\n","tags":["Account"],"responses":{"200":{"description":"Webhook secret","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSecret"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/account/webhook-secret/rotate":{"post":{"operationId":"rotateWebhookSecret","summary":"Rotate webhook signing secret","description":"Generates a new webhook signing secret, replacing the current one.\nRate limited to once per 60 minutes.\n","tags":["Account"],"responses":{"200":{"description":"New webhook secret","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSecret"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/credits/redeem":{"post":{"operationId":"redeemCreditCode","summary":"Redeem a credit code","tags":["Credits"],"description":"Redeem a credit code for the authenticated organization. The credit is added to the organization prepaid credit and shows in `GET /credits/balance` under `prepaid_credit`. Redeeming requires an organization owner or admin: an API key redeems with the authority of the user who created it, based on that user's current role, and a key without that authority gets the same `credit_code_invalid` refusal as an unknown code. The `Idempotency-Key` header is required. Retrying with the same key and code returns the original grant with `replayed: true` and grants nothing new; the same key with a different code is refused with `idempotency_key_reused`. Every refusal carries a human-readable `error.message` that can be shown to the user as is.","security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Required client-supplied key: 1 to 200 printable ASCII characters, without spaces. Use a new key for each code you redeem and reuse it only to retry the same request.","schema":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[\\x21-\\x7E]{1,200}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedeemCreditCodeInput"},"example":{"code":"LAUNCH50"}}}},"responses":{"200":{"description":"The code was redeemed, or this request replayed an earlier redemption with the same Idempotency-Key.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreditRedemption"}}}]},"example":{"success":true,"data":{"redemption_id":"6f1c2a8e-3b1d-4f7a-9c2e-1a2b3c4d5e6f","amount_micros":"50000000","currency":"usd","granted_at":"2026-09-23T17:00:00.000Z","expires_at":"2026-12-31T00:00:00.000Z","label":null,"replayed":false,"message":"$50.00 in credits added. Expires Dec 31, 2026. Applies to usage from now on."}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"`idempotency_key_required` means the Idempotency-Key header is missing. `validation_error` means the header is empty, longer than 200 characters or not printable non-space ASCII, or the body is invalid. `error.code` is one of: `idempotency_key_required`, `validation_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"idempotency_key_required","message":"an Idempotency-Key header is required"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"409":{"description":"The organization or user already redeemed this code, or already redeemed another code in the same exclusive group (a set of codes of which each organization or user may redeem only one). `error.code` is one of: `credit_code_already_redeemed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"credit_code_already_redeemed","message":"Your organization has already redeemed this promotion."}}}}},"422":{"description":"The code cannot be redeemed. `credit_code_invalid` covers unknown, expired and exhausted codes and callers without authority to redeem; `credit_code_not_eligible` means the organization does not meet the promotion terms; `credit_code_balance_cap` means the organization already holds the most promotional credit it can; `idempotency_key_reused` means this Idempotency-Key already redeemed a different code. `error.code` is one of: `credit_code_invalid`, `credit_code_not_eligible`, `credit_code_balance_cap`, `idempotency_key_reused`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"credit_code_invalid","message":"That code isn't valid or is no longer available."}}}}},"429":{"description":"Too many requests or redemption attempts. Wait for `Retry-After` when present. `error.code` is one of: `rate_limited`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limited","message":"Rate limit exceeded. Try again shortly."}}}}},"503":{"description":"The code could not be redeemed right now. Retry later with the same Idempotency-Key. `error.code` is one of: `service_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"service_unavailable","message":"We couldn't redeem that code right now. Try again in a few minutes."}}}}}}}},"/credits/balance":{"get":{"operationId":"getCreditBalance","summary":"Get credit balance","tags":["Credits"],"description":"Read the organization credit position. `prepaid_credit` is the prepaid usage credit (paid top-ups, redeemed credit codes and granted credit) that can still pay for usage. `budget` is the active agent spending budget an operator funded for top-ups, or null when there is none. Amounts are strings of integer micros (1 USD = 1000000 micros).","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"The organization credit balance.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreditBalance"}}}]},"example":{"success":true,"data":{"budget":null,"prepaid_credit":{"currency":"usd","remaining_micros":"50000000","next_expires_at":"2026-12-31T00:00:00.000Z"}}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/domains":{"get":{"operationId":"listDomains","summary":"List all domains","description":"Returns all verified and unverified domains for your organization,\nsorted by creation date (newest first). Each domain includes a\n`verified` boolean to distinguish between the two states.\n","tags":["Domains"],"responses":{"200":{"description":"List of domains","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Domain"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]},"post":{"operationId":"addDomain","summary":"Claim a new domain","description":"Creates an unverified domain claim and returns the exact\nDNS records to publish in `dns_records`. Publish those\nrecords before calling the verify endpoint. To give users\nan importable DNS file, call `downloadDomainZoneFile` or run\n`primitive domains zone-file --id <domain-id>`.\n","tags":["Domains"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddDomainInput"}}}},"responses":{"201":{"description":"Domain claim created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/UnverifiedDomain"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"409":{"description":"Domain claim conflicts with existing state. Two error codes\nare possible:\n  * `mx_conflict`: the domain's current MX records point at\n    another mailbox provider. The response includes\n    `error.details.mx_conflict` with the detected provider\n    and a suggested subdomain.\n  * `conflict`: the domain is already claimed by another\n    org, or a pending claim exists for another user.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"mx_conflict":{"summary":"Domain has MX records on another provider","value":{"success":false,"error":{"code":"mx_conflict","message":"Domain is currently receiving mail via another provider","details":{"mx_conflict":{"provider_name":"Google Workspace","suggested_subdomain":"mail"}}}}},"already_claimed":{"summary":"Domain already claimed by another org","value":{"success":false,"error":{"code":"conflict","message":"Domain is already claimed by another organization"}}}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/domains/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"patch":{"operationId":"updateDomain","summary":"Update domain settings","description":"Update a verified domain's settings. Only verified domains can be\nupdated. Per-domain spam thresholds require a Pro plan.\n","tags":["Domains"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDomainInput"}}}},"responses":{"200":{"description":"Updated domain","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/VerifiedDomain"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteDomain","summary":"Delete a domain","description":"Remove a domain from the organization. Inbound mail for its addresses stops being accepted.","tags":["Domains"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/domains/{id}/verify":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"verifyDomain","summary":"Verify domain ownership","description":"Checks DNS records required for inbound routing, ownership,\nand outbound authentication: MX, ownership TXT, SPF, DKIM,\nDMARC, and TLS-RPT.\nOn success, the domain is promoted from unverified to verified.\nOn failure, returns which checks passed and which failed,\nplus the exact DNS records still expected. To give users\nan importable DNS file for missing records, call\n`downloadDomainZoneFile` or run\n`primitive domains zone-file --id <domain-id>`.\n","tags":["Domains"],"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DomainVerifyResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/domains/{id}/dns/check":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"checkDomainDns","summary":"Run an on-demand DNS health check","description":"Re-checks the domain's DNS records and persists the result as\nthe domain's current DNS health state. This is the on-demand\ncounterpart of the scheduled background checker: the response\nmirrors what the checker records, broken down per scope\n(`ownership`, `inbound`, `outbound`) with the exact records\ninspected and each record's individual status.\n\nUnlike /domains/{id}/verify, this call never promotes an\nunverified domain; it only re-evaluates and records health for\nan existing claim. Managed (Primitive-operated) domains are\nrejected with a validation error because their DNS is not\ncustomer-published.\n\nRate limited per organization; a `Retry-After` header\naccompanies 429 responses.\n","tags":["Domains"],"responses":{"200":{"description":"DNS health check result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DomainDnsHealthCheck"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The domain's active outbound key changed while the check\nwas running. Nothing was recorded; retry the check.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"Outbound key changed during DNS verification. Please retry."}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/domains/{id}/zone-file":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"downloadDomainZoneFile","summary":"Download domain DNS zone file","description":"Downloads a BIND-format DNS zone file containing the DNS records required for a domain claim. Agents should offer this after `addDomain` when users want to import DNS records instead of copying each record manually.","tags":["Domains"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"outbound_only","in":"query","schema":{"type":"boolean"},"description":"When true, include only outbound DNS records. Verified domains default to outbound-only; pending claims default to all required records."}],"responses":{"200":{"description":"BIND-format zone file","content":{"text/plain":{"schema":{"type":"string","format":"binary"}}},"headers":{"Content-Disposition":{"schema":{"type":"string","example":"attachment; filename=\"example.com.zone\""}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/inbox/status":{"get":{"operationId":"getInboxStatus","summary":"Get inbound inbox readiness","description":"Returns one consolidated view of domain verification, webhook/function processing routes, deployed functions, and recent inbound mail. Agents should use this before guiding users through inbound email setup.","tags":["Inbox"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Consolidated inbox readiness status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxStatus"}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/emails":{"get":{"operationId":"listEmails","summary":"List inbound emails","description":"Returns a paginated list of INBOUND emails received at your\nverified domains. Outbound messages sent via /send-mail are\nnot included; this endpoint is the inbox view, not a\nunified send/receive history.\n\nSupports filtering by domain, status, date range, and\nfree-text search across subject, sender, and recipient\nfields.\n\nFor a compact text-table summary of the most recent N\ninbounds (no filters, no cursor pagination), the CLI ships\n`primitive emails:latest` as a one-line-per-email shortcut.\nIt's TTY-aware so id columns are full UUIDs when piped, and\na `--json` flag returns the same envelope this endpoint\ndoes. Use whichever fits the call site.\n","tags":["Emails"],"parameters":[{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Pagination cursor from a previous response's `meta.cursor` field.\nFormat: `{ISO-datetime}|{id}`\n"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Number of results per page"},{"name":"domain_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter by domain ID"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/EmailStatus"},"description":"Filter inbound rows by lifecycle status. See `EmailStatus`\nfor what each value means. Note that the webhook delivery\nstate is a SEPARATE lifecycle on the same row; filter by\n`webhook_status` semantics is not currently supported on\nthis endpoint.\n"},{"name":"search","in":"query","schema":{"type":"string","maxLength":500},"description":"Search subject, sender, and recipient (case-insensitive)"},{"name":"date_from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Filter emails created on or after this timestamp. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."},{"name":"date_to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Filter emails created on or before this timestamp. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."},{"name":"since","in":"query","schema":{"type":"string","maxLength":200},"description":"Forward-tail cursor. Returns rows that became visible AFTER this\ncursor, oldest-first, so a caller can stream new inbound mail by\nre-passing the cursor from each response. Mutually exclusive with\n`cursor` (which pages history newest-first). Pass the `meta.cursor`\nfrom the previous `since` response; an empty page means caught up.\n"},{"name":"wait","in":"query","schema":{"type":"integer","minimum":0,"maximum":30},"description":"Long-poll: hold the request up to this many seconds waiting for new\nmail past `since`, returning as soon as any arrives (or an empty\npage when the wait elapses). Requires `since`. Omitted means no wait\n(returns immediately); the server treats an absent value as 0. NOT\ngiven an OpenAPI `default` on purpose: a default makes some\ngenerators (e.g. openapi-python-client) send `wait=0` on every call,\nwhich then fails the `wait` requires `since` check for plain history\nlistings.\n"},{"name":"awaiting","in":"query","required":false,"schema":{"type":"string","enum":["you","them"]},"description":"Only return emails whose `awaiting` has this value. `awaiting=you`\nlists mail waiting on your reply. Combines with every other filter\nand with both `cursor` and `since`.\nThe filter (either value, here, on search and as the `awaiting:`\nsearch term) matches delivered mail only: emails whose `status` is\n`rejected` are never returned by it. A rejected email (for example\none refused because the organization is over its storage limit) was\nnever delivered, so nobody can answer it and it is awaiting no one;\nits own `awaiting` field still reports the stored value, usually\n`you`.\nWhose turn it is in this email's conversation. A send counts as a\nreply unless its status is `gate_denied`, `agent_failed`, `canceled`\n(queued and scheduled replies count: they are committed to go). For\nan email with a `thread_id`: `them` when the thread's latest counted\noutbound send (by send `created_at`, including any counted reply to\nthis email itself) is at or after the thread's latest inbound message\n(by `received_at`); otherwise `you`. For an email with no\n`thread_id`: `them` when it has at least one counted reply\n(`reply_count > 0` for an API key), otherwise `you`. It is a\nthread-level fact: every email in a thread carries the same value, and\nit reflects sends made by any credential in the organization,\nincluding other agent connections. It changes without a new inbound\nmessage, for example when a reply is sent, a queued reply fails, a\nscheduled reply is canceled, or a message is deleted, and is always\ncurrent when read.\n"},{"name":"automated","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"Only return emails whose `automated` has this value.\n`awaiting=you&automated=false` lists mail from people that is\nwaiting on your reply. Combines with every other filter and with\nboth `cursor` and `since`. An inbound email is `automated` when any of these holds, decided once\nwhen it arrives: `null_envelope_sender` (an empty MAIL FROM, a bounce);\n`no_identifiable_sender` (no address in the envelope or From);\n`own_address` (a sender address is exactly one of the addresses the\nmail was delivered to; sharing a domain with the recipient is not\nenough, so a colleague or another agent in the organization is not\nautomated);\n`mailer_daemon` (mailer-daemon@ or postmaster@ on any domain);\n`auto_submitted` (Auto-Submitted with any keyword but `no`, an\nempty one included);\n`precedence` (Precedence bulk, list, junk or auto_reply);\n`list_unsubscribe` (List-Unsubscribe present); `list_id` (List-Id\npresent); `auto_response_suppress` (X-Auto-Response-Suppress asks\nfor no automatic reply: All, OOF or AutoReply);\n`failed_recipients` (X-Failed-Recipients present, a bounce);\n`report` (a delivery, feedback or disposition report). Mail\nPrimitive's own automatic responders decline to answer is always\n`automated`. The header rules only see headers captured at ingest, so\nolder mail may lack them. This is loop and noise protection, not\nsender authentication.\n"},{"name":"recipient","in":"query","required":false,"schema":{"type":"string","format":"email","maxLength":320},"description":"Exact case-insensitive delivered recipient mailbox, applied before pagination. Combines with existing filters and cursors; does not search message text."},{"name":"exclude_fyi","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"With `true`, leave out emails whose `fyi` is true. Combines with every other filter and with both `cursor` and `since`."},{"name":"thread_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Only return emails in this thread (the `thread_id` on an email or sent email). Combines with every other filter and with both `cursor` and `since`. Returns the same emails the list would return without it, narrowed to the thread, so an agent connection key still sees only mail delivered to its address."},{"name":"exclude_muted","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"With `true`, leave out emails whose `muted` is true. Combines with every other filter and with both `cursor` and `since`."},{"name":"include_presence","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"Receiver presence mail: the presence probes sent to a connected agent and its answers. Left out by default for a connected-agent credential and listed by default for any other credential. `true` lists it, `false` leaves it out. Presence mail that inbound processing verified as presence on arrival expects no answer and does not count toward `awaiting`. Presence mail stored before that verification existed keeps the `awaiting` value it was stored with, so it can still count toward `awaiting`; `include_presence=true`, or any credential other than a connected agent's, still lists it. Combines with every other filter and with both `cursor` and `since`."}],"responses":{"200":{"description":"Paginated list of emails","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailSummary"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]}},"/emails/search":{"get":{"operationId":"searchEmails","summary":"Search inbound emails","description":"Searches inbound emails with structured filters and optional\nfull-text matching across parsed email fields. This endpoint is\noptimized for filtered inbox views and CLI polling workflows:\ncallers that only need new accepted mail can pass\n`sort=received_at_asc`, `snippet=false`, `include_facets=false`,\nand a `date_from` timestamp.\n\n`q`, `subject`, and `body` use the same English full-text index\nas the web inbox search. Structured filters such as `from`, `to`,\n`domain_id`, status, attachment presence, and spam score bounds\nare combined with the text query.\n\nThe text parameters (`q`, `from`, `to`, `subject` and `body`) must not\ncontain a NUL character; a value that does is rejected with a 400\nvalidation error.\n\nConnected-agent credentials search only mail received by their own\naddress. This applies to results, totals, facets, and every page;\nsearch filters cannot widen the credential's scope. When\n`reply_to_sent_email_id` is supplied, its parent send must belong\nto the connected address in the same organization. An unavailable\nparent returns 404. Sender filters are not authentication proof;\ninspect the email detail's authentication evidence before trusting it.\n\n\nAgent connection keys (pconn_) may search only mail received by their bound address. This restriction applies to results, totals, facets and pagination; from/to and q filters cannot widen it. When reply_to_sent_email_id is supplied, the parent must be a send from the connected address in the same organization, otherwise the response is 404. The parent filter is optional. Existing from/to filters are substring or domain filters, not proof of sender identity. Inspect getEmail authentication details before trusting a sender.","tags":["Emails"],"parameters":[{"name":"q","in":"query","schema":{"type":"string","maxLength":500},"description":"Full-text search DSL query. Supports `awaiting:you` and `awaiting:them` to filter on reply state (see the `awaiting` field; delivered mail only, never `rejected`). Supports `automated:true` and `automated:false` to filter on the `automated` field. Terms are separated by spaces and every term must match. A term is a word, a `\"quoted phrase\"`, or a `field:value` filter (`from:`, `to:`, `subject:`, `body:`, `has:attachment`, `before:`, `after:`, `domain:`, `status:`, `awaiting:`, `automated:`). An upper-case `OR` between two text terms (words, phrases, `subject:` and `body:` terms) accepts either, and binds tighter than the implicit AND: `acme invoice OR receipt` is `acme` and either `invoice` or `receipt`. `OR` cannot join the other filters, and there is no `NOT` and no parentheses. A query that uses an unsupported operator, an unknown field, an unterminated quote, or only stop words is a `validation_error` that says what to change; it is never silently reinterpreted. Alternatives chain (`invoice OR receipt OR \"past due\"`), and each one counts toward the limit of 32 terms. These are rejected with a 400 `validation_error`: `OR` next to a filter (`from:a@example.com OR from:b@example.com`; run one search per value instead); an `OR` with no term on one side (a leading, trailing or doubled `OR`); an upper-case `NOT` (a search cannot exclude a term); and an alternative with no searchable word once stop words are removed (`the OR invoice`). Only upper case is an operator: to search for the word `OR` or `NOT` itself, write it in lower case or in double quotes (`\"OR\"`). With `prefix=true`, a trailing `OR` or `NOT` is read as a word still being typed. To search for text that contains a colon, such as a URL, put it in double quotes."},{"name":"from","in":"query","schema":{"type":"string","maxLength":255},"description":"Filter by sender address or sender domain."},{"name":"to","in":"query","schema":{"type":"string","maxLength":255},"description":"Filter by recipient address or recipient domain."},{"name":"subject","in":"query","schema":{"type":"string","maxLength":500},"description":"Full-text search restricted to the subject field."},{"name":"body","in":"query","schema":{"type":"string","maxLength":2000},"description":"Full-text search restricted to the parsed text body."},{"name":"domain_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter by domain ID."},{"name":"reply_to_sent_email_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter to inbound emails that are replies to a specific\noutbound send. The value is a `sent_emails.id` (UUID). At\ninbound ingest, Primitive matches the parsed In-Reply-To\nheader (or References as a fallback) against\n`sent_emails.message_id` in the same org and records the\nresolved id on `emails.reply_to_sent_email_id`. This filter\nis the strict-threading lookup behind `primitive chat` and\nany UI that wants to show the inbound reply to a given\nsend. NULL on inbound that isn't a threaded reply to one\nof your sends, so existing emails received before this\ningestion landed will not match.\n"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/EmailStatus"},"description":"Filter by inbound email lifecycle status."},{"name":"date_from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Filter emails received on or after this timestamp. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."},{"name":"date_to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Filter emails received on or before this timestamp. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."},{"name":"has_attachment","in":"query","schema":{"type":"string","enum":["true","false"]},"description":"Filter by whether the email has one or more attachments."},{"name":"spam_score_lt","in":"query","schema":{"type":"number"},"description":"Filter to emails with spam score below this value."},{"name":"spam_score_gte","in":"query","schema":{"type":"number"},"description":"Filter to emails with spam score greater than or equal to this value."},{"name":"awaiting","in":"query","required":false,"schema":{"type":"string","enum":["you","them"]},"description":"Only return emails whose `awaiting` has this value. Also available\nin `q` as `awaiting:you` or `awaiting:them`.\nThe filter (either value, here, on search and as the `awaiting:`\nsearch term) matches delivered mail only: emails whose `status` is\n`rejected` are never returned by it. A rejected email (for example\none refused because the organization is over its storage limit) was\nnever delivered, so nobody can answer it and it is awaiting no one;\nits own `awaiting` field still reports the stored value, usually\n`you`.\nWhose turn it is in this email's conversation. A send counts as a\nreply unless its status is `gate_denied`, `agent_failed`, `canceled`\n(queued and scheduled replies count: they are committed to go). For\nan email with a `thread_id`: `them` when the thread's latest counted\noutbound send (by send `created_at`, including any counted reply to\nthis email itself) is at or after the thread's latest inbound message\n(by `received_at`); otherwise `you`. For an email with no\n`thread_id`: `them` when it has at least one counted reply\n(`reply_count > 0` for an API key), otherwise `you`. It is a\nthread-level fact: every email in a thread carries the same value, and\nit reflects sends made by any credential in the organization,\nincluding other agent connections. It changes without a new inbound\nmessage, for example when a reply is sent, a queued reply fails, a\nscheduled reply is canceled, or a message is deleted, and is always\ncurrent when read.\n"},{"name":"automated","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"Only return emails whose `automated` has this value. Also\navailable in `q` as `automated:true` or `automated:false`. An inbound email is `automated` when any of these holds, decided once\nwhen it arrives: `null_envelope_sender` (an empty MAIL FROM, a bounce);\n`no_identifiable_sender` (no address in the envelope or From);\n`own_address` (a sender address is exactly one of the addresses the\nmail was delivered to; sharing a domain with the recipient is not\nenough, so a colleague or another agent in the organization is not\nautomated);\n`mailer_daemon` (mailer-daemon@ or postmaster@ on any domain);\n`auto_submitted` (Auto-Submitted with any keyword but `no`, an\nempty one included);\n`precedence` (Precedence bulk, list, junk or auto_reply);\n`list_unsubscribe` (List-Unsubscribe present); `list_id` (List-Id\npresent); `auto_response_suppress` (X-Auto-Response-Suppress asks\nfor no automatic reply: All, OOF or AutoReply);\n`failed_recipients` (X-Failed-Recipients present, a bounce);\n`report` (a delivery, feedback or disposition report). Mail\nPrimitive's own automatic responders decline to answer is always\n`automated`. The header rules only see headers captured at ingest, so\nolder mail may lack them. This is loop and noise protection, not\nsender authentication.\n"},{"name":"sort","in":"query","schema":{"type":"string","enum":["relevance","received_at_desc","received_at_asc"]},"description":"Sort mode. Defaults to relevance when a text query is present,\notherwise `received_at_desc`.\n"},{"name":"cursor","in":"query","schema":{"type":"string","maxLength":200},"description":"Opaque pagination cursor from a previous search response."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Number of results per page"},{"name":"snippet","in":"query","schema":{"type":"string","enum":["true","false"],"default":"true"},"description":"Include subject/body highlight snippets when text search is active. This switches only the `highlights` object on each result. The `snippet` field (the body preview) is on every result either way, and a search with no text (`q`, `subject` or `body`) has no highlights to leave out, so `false` changes nothing there."},{"name":"include_facets","in":"query","schema":{"type":"string","enum":["true","false"],"default":"true"},"description":"Include facet counts for sender, domain, status, and attachment presence. When `false`, the facet aggregation is not run at all. Each of `by_sender`, `by_domain` and `by_status` lists its values by `count` descending, and values with the same count by `value` ascending in byte order of its UTF-8 encoding (upper case before lower case), a null `value` last. `by_sender` and `by_domain` keep the first 20 values in that order, so the same request over the same mail always lists the same values."},{"name":"thread_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Only return emails in this thread (the `thread_id` each result carries). Combines with `q` and every other filter, and with pagination. Visibility is unchanged; an agent connection still sees only mail received by its address."},{"name":"prefix","in":"query","required":false,"schema":{"type":"string","enum":["true","false"],"default":"false"},"description":"Search-as-you-type. When `true`, the final word of `q` is treated as\nstill being typed and matches any word that begins with it, so\n`q=quarterly invoi` finds \"invoice\" and \"invoicing\". Only the final\nword is affected, and only when it is unquoted text (a bare word or\n`subject:`/`body:` with a bare value); a quoted phrase or a field\nfilter in the final position leaves the query unchanged. The final\nword is kept even when it is a stop word, so `q=the` matches\n\"theory\". Matching is against the same stemmed index as a whole-word\nsearch, so a partial word whose spelling diverges from its stem\n(`runni` for \"running\") may not match until the word is complete.\nThe text is never interpreted as query syntax: operators such as\n`|`, `!`, `&`, `:*` and `<->` are read as ordinary characters.\nDefaults to `false`.\n"},{"name":"count","in":"query","required":false,"schema":{"type":"string","enum":["true","false"],"default":"true"},"description":"When `false`, the total match count is not computed and `meta.total`\nis null (`meta.total_capped` is false). Use `meta.cursor` to tell\nwhether another page exists. Skipping the count makes a broad query,\nsuch as a one-letter prefix, cheaper to serve. Defaults to `true`.\n"}],"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailSearchResult"}},"meta":{"$ref":"#/components/schemas/EmailSearchMeta"},"facets":{"$ref":"#/components/schemas/EmailSearchFacets"}},"required":["data","meta"]}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"504":{"description":"Search query timed out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}]}},"/emails/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"getEmail","summary":"Get inbound email by id","description":"Returns the full record for an inbound email received at one\nof your verified domains, including the parsed text and HTML\nbodies, threading metadata, SMTP envelope detail, webhook\ndelivery state, and a `replies` array for any outbound sends\nrecorded as replies to this inbound.\n\nFor listing inbound emails (with cursor pagination, status\nand date filters, and free-text search), use\n`/emails`. Outbound (sent) email records are NOT returned\nhere; use `/sent-emails/{id}` for those.\n\nThe response carries four sender-shaped fields whose\nmeanings overlap. `from_email` is the canonical \"who sent\nthis\" field for most use cases (parsed bare address from\nthe `From:` header, with a `sender` fallback). `from_header`\nis the raw header including any display name. `sender` and\n`smtp_mail_from` both carry the SMTP envelope MAIL FROM\n(return-path) and are equal by construction; `sender` is\nthe older field name retained for compatibility. See\n`primitive describe emails:get-email | jq '.responseSchema.properties'`\nfor per-field detail.\n","tags":["Emails"],"responses":{"200":{"description":"Email details","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/EmailDetail"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteEmail","summary":"Delete an email","tags":["Emails"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Permanently delete a received email and its stored content. This cannot be undone."}},"/emails/{id}/raw":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"downloadRawEmail","summary":"Download raw email","description":"Downloads the raw RFC 822 email file (.eml). Authenticates via\na signed download token (provided in webhook payloads) or a\nvalid session.\n","tags":["Emails"],"security":[{"BearerAuth":[]},{"DownloadToken":[]}],"parameters":[{"name":"token","in":"query","schema":{"type":"string"},"description":"Signed download token from webhook payload"}],"responses":{"200":{"description":"Raw email file","content":{"message/rfc822":{"schema":{"type":"string","format":"binary"}}},"headers":{"Content-Disposition":{"schema":{"type":"string","example":"attachment; filename=\"email-id.eml\""}},"X-Content-SHA256":{"schema":{"type":"string"},"description":"SHA-256 hex digest of the file"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}}}},"/emails/{id}/attachments.tar.gz":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"downloadAttachments","summary":"Download email attachments","description":"Downloads all attachments as a gzip-compressed tar archive.\nAuthenticates via a signed download token (provided in webhook\npayloads) or a valid session.\n","tags":["Emails"],"security":[{"BearerAuth":[]},{"DownloadToken":[]}],"parameters":[{"name":"token","in":"query","schema":{"type":"string"},"description":"Signed download token from webhook payload"}],"responses":{"200":{"description":"Attachments archive","content":{"application/gzip":{"schema":{"type":"string","format":"binary"}}},"headers":{"Content-Disposition":{"schema":{"type":"string","example":"attachment; filename=\"email-id_attachments.tar.gz\""}},"X-Content-SHA256":{"schema":{"type":"string"},"description":"SHA-256 hex digest of the archive"},"X-Attachment-Count":{"schema":{"type":"string"},"description":"Number of attachments in the archive"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}}}},"/emails/{id}/attachments/{part_index}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"part_index","in":"path","required":true,"schema":{"type":"integer","minimum":0,"maximum":2147483647},"description":"Exact part_index from the attachment metadata in email detail. Not an array offset or filename."}],"get":{"operationId":"downloadEmailAttachmentPart","summary":"Download one received email attachment","description":"Returns the original decoded bytes of one retained attachment. Authenticate with an organization API key, an authorized organization OAuth token, or an address-bound agent connection key. Connected agents can read only their own inbound recipient attachments. Function credentials and download query tokens are not supported. Duplicate filenames are distinguished by part_index. The part limit is 31457280 bytes; the complete archive must fit 67108864 compressed and decoded bytes and 1024 entries including metadata. Metadata headers are limited to 16384 bytes each and 262144 bytes total; resolved archive paths are limited to 4096 UTF-8 bytes. Preparation has a 15-second deadline. A small part in an oversized archive can exceed these limits. This does not parse interactions or certify sender identity. Discarded content is unavailable.","tags":["Emails"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Original decoded attachment bytes","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}},"headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"Safe attachment download filename."},"X-Content-SHA256":{"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Verified SHA-256 digest of this part."},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"attachment_changed: email attachment metadata changed during the read. Refresh email detail before retrying.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"content_discarded: email content has been discarded or discard is requested.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"attachment_limit_exceeded: the part or archive exceeds a documented resource limit. Retrying this content will not help.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"502":{"description":"attachment_integrity_failed: retained content failed archive, part digest or structure verification.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"attachment_not_ready: archive materialization is pending; or attachment_storage_unavailable: storage is temporarily unavailable. Retry-After advises when to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/sent-emails/{id}/attachments/{part_index}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"part_index","in":"path","required":true,"schema":{"type":"integer","minimum":0,"maximum":2147483647},"description":"Exact part_index from the attachment metadata in email detail. Not an array offset or filename."}],"get":{"operationId":"downloadSentAttachmentPart","summary":"Download one sent email attachment","description":"Returns the original decoded bytes of one retained attachment. Authenticate with an organization API key, an authorized organization OAuth token, or an address-bound agent connection key. Connected agents can read only their own outgoing sender attachments. Function credentials and download query tokens are not supported. Duplicate filenames are distinguished by part_index. The part limit is 31457280 bytes; the complete archive must fit 67108864 compressed and decoded bytes and 1024 entries including metadata. Metadata headers are limited to 16384 bytes each and 262144 bytes total; resolved archive paths are limited to 4096 UTF-8 bytes. Preparation has a 15-second deadline. A small part in an oversized archive can exceed these limits. This does not parse interactions or certify sender identity. Discarded content is unavailable.","tags":["Sending"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Original decoded attachment bytes","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}},"headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"Safe attachment download filename."},"X-Content-SHA256":{"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Verified SHA-256 digest of this part."},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"attachment_changed: email attachment metadata changed during the read. Refresh email detail before retrying.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"content_discarded: email content has been discarded or discard is requested.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"attachment_limit_exceeded: the part or archive exceeds a documented resource limit. Retrying this content will not help.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"502":{"description":"attachment_integrity_failed: retained content failed archive, part digest or structure verification.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"attachment_not_ready: archive materialization is pending; or attachment_storage_unavailable: storage is temporarily unavailable. Retry-After advises when to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/emails/{id}/repeat-stop":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"stopRepeatFromEmail","summary":"Stop the repeat behind a received message","description":"Called by the recipient of a repeating send: the connected agent's own\ncredential for an agent address, or the signed-in member whose personal\naddress it is. `id` is the caller's received copy of any message of the\nrepeat, or the repeat id printed in the message footer, which resolves\nto the caller's newest received copy. Stops the repeat when it lets the recipient stop it, cancels the\npending message, and replies once in the thread with a\n`repeat.stop/1` interaction so the sender sees it. A repeat call returns\nthe same result without a second reply.\n","tags":["Repeating Sends"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepeatStopRequest"}}}},"responses":{"200":{"description":"The repeat is stopped","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RepeatStopResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"`repeat_stop_not_allowed`: the caller is not the recipient, the\nrepeat does not let the recipient stop it, or the sender canceled\nit.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"422":{"description":"`not_a_repeating_send`: the email is not part of a repeating send.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/emails/{id}/reply":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"replyToEmail","summary":"Reply to an inbound email","description":"Sends an outbound reply to the inbound email identified by `id`.\nThreading headers (`In-Reply-To`, `References`), recipient\nderivation (Reply-To, then From, then bare sender), and the\n`Re:` subject prefix are all derived server-side from the\nstored inbound row. The request body carries only the message\nbody, optional From override, optional attachments, optional\n`wait` flag, and optional `reply_all` flag, which also copies\nevery other To and Cc address of the inbound (never Bcc).\nPassing any header or explicit recipient override is rejected\nby the schema (`additionalProperties: false`).\n\nForwards through the same gates as `/send-mail`: the response\nstatus, error envelope, and `idempotent_replay` flag mirror\nthe send-mail contract verbatim.\n","servers":[{"url":"https://api.primitive.dev/v1","description":"Canonical API host (recommended)"},{"url":"https://www.primitive.dev/api/v1","description":"Legacy compatibility host (Vercel body limit applies)"}],"tags":["Sending"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyInput"}}}},"responses":{"200":{"description":"Outbound relay result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SendMailResult"}}}]}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"410":{"description":"The prior send was deleted. Its idempotency key or automatic reply suppression remains reserved, so this request will not send another message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Inbound is not repliable: the row exists but lacks a\n`message_id` (no thread anchor) or a `recipient` (cannot\nderive the From address).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"inbound_not_repliable","message":"inbound has no Message-ID; cannot anchor a reply thread"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"500":{"description":"Primitive encountered an internal error When this is the stored outcome of an earlier send (same idempotency key) and not a new attempt, the response carries `Idempotent-Replayed: true`, `error.details.idempotency` and `error.details.dedup_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"internal_error","message":"Internal server error"}}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"502":{"description":"Primitive could not complete the downstream SMTP request When this is the stored outcome of an earlier send (same idempotency key) and not a new attempt, the response carries `Idempotent-Replayed: true`, `error.details.idempotency` and `error.details.dedup_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_unreachable","message":"Outbound SMTP service request failed"}}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"503":{"description":"Primitive is temporarily unable to process the request When this is the stored outcome of an earlier send (same idempotency key) and not a new attempt, the response carries `Idempotent-Replayed: true`, `error.details.idempotency` and `error.details.dedup_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_capacity_exhausted","message":"Outbound capacity is temporarily exhausted"}}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"504":{"description":"The outbound relay did not answer in time. When this is the stored outcome of an earlier send (same idempotency key) and not a new attempt, the response carries `Idempotent-Replayed: true`, `error.details.idempotency` and `error.details.dedup_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/emails/{id}/replay":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"replayEmailWebhooks","summary":"Replay email webhooks","description":"Re-delivers the webhook payload for this email to all active\nendpoints matching the email's domain. Rate limited per-email\n(short cooldown between successive replays of the same email)\nand per-org (burst + sustained windows), sharing an org-wide\nbudget with delivery replays.\n","tags":["Emails"],"responses":{"200":{"description":"Replay result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ReplayResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/emails/{id}/discard-content":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"discardEmailContent","summary":"Discard email content","description":"Permanently deletes the email's raw bytes, parsed body (text + HTML),\nand attachments while preserving metadata (sender, recipient,\nsubject, timestamps, hashes, attachment manifest) for audit logs.\nIdempotent: a second call returns success with\n`already_discarded: true` and does no work.\n\n**Gated** on the customer's discard-content opt-in (managed in the\ndashboard at Settings > Webhooks). When the toggle is off, this\nendpoint returns `403` with code `discard_not_enabled` and a\nmessage pointing the human at the dashboard. There is intentionally\nno API to flip this toggle. Opting in to a destructive,\nnon-reversible operation must be a deliberate human click in the\nUI.\n","tags":["Emails"],"responses":{"200":{"description":"Discard result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DiscardContentResult"}},"required":["data"]}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"500":{"description":"Primitive encountered an internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"internal_error","message":"Internal server error"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/emails/{id}/conversation":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID. An inbound email id; on a window read (`around`, `before` or `after`) a sent email id is accepted too."}],"get":{"operationId":"getConversation","summary":"Get the conversation an email belongs to","description":"Returns the full conversation the given inbound email belongs\nto, as ordered, ready-to-prompt turns WITH bodies. It resolves\nthe thread from the email and returns every message oldest-first,\nso an agent that received an email can pass `messages` straight\nto a chat model in one call instead of walking `/threads/{id}`\nplus `/emails/{id}` and `/sent-emails/{id}` per message.\n\nEach message carries a `direction` (`inbound` | `outbound`) and a\nderived `role`: `inbound` -> `user`, `outbound` -> `assistant`\n(your own prior replies). The role mapping assumes the caller\nowns the outbound side, which is the agent-reply case this exists\nfor. If the email has no thread yet (a brand-new message), the\nconversation is just that one message as a single user turn.\n\nThe message list is capped; check `truncated` to detect when\nolder messages were omitted. Consecutive same-role turns are not\nmerged here; that normalization is model-specific and left to the\ncaller.\n\nPass `since` for an incremental read. `since=start` returns the\nfull conversation plus a `cursor`. Sending that `cursor` back as\n`since` returns only the messages created or changed after it,\noldest first, with the same per-message shape, and a new\n`cursor`. Without `since` the response carries no `cursor` and\nno per-message `status`.\n\nTo read an older part of a long conversation, such as the message a search result names, pass `around` with that message's id: the response is a window of messages around it with `before_cursor` and `after_cursor` to page older and newer. On these window reads `{id}` may be a sent email id as well as an inbound email id.\n","tags":["Emails"],"parameters":[{"name":"since","in":"query","required":false,"schema":{"type":"string"},"description":"Incremental read position. Either the literal `start` or a\n`cursor` returned by a previous read of this endpoint. Any\nother value returns 400 `validation_error`.\n\nWith `start`, the response holds every message the read\nwithout `since` would return, plus `cursor`. With a cursor,\n`messages` holds only the messages created or changed after\nit, oldest first. A change includes an inbound message\nbecoming visible or its body being filled, withheld or\ndiscarded, and a delivery status change on an outbound\nmessage. `thread_id`, `subject`, `message_count` and\n`truncated` always describe the whole conversation, not the\nreturned messages.\n\nThe cursor is an opaque string. Send it back verbatim and do\nnot parse it or build one; its format can change. A message\ncan be returned again by a later read (for example while\nolder writes are still committing), so upsert messages by\n`id`. Deleted messages are not reported; a periodic\n`since=start` read (or a read without `since`) reconciles.\n"},{"name":"around","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Window read centred on one message: returns up to `limit` messages before it, the message itself, and up to `limit` after it, oldest first, so a client can open an older message (for example a search result) inside its conversation. The value is the id of an inbound email or a sent email in the same conversation as `{id}`. On a window read `{id}` may itself be a sent email id. Every message is scoped and withheld exactly as on the full read. A message that is not in this conversation, or that the credential cannot read, returns `404 not_found` whether or not it exists elsewhere. A malformed `around`, `before`, `after` or `limit`, or more than one of `since`, `around`, `before` and `after`, returns `400 validation_error`."},{"name":"before","in":"query","required":false,"schema":{"type":"string"},"description":"Window read of up to `limit` messages strictly older than the position named by a `before_cursor` from an earlier window read. Pass it back unchanged; its format may change, so do not parse or construct it. Cannot be combined with `since`, `around` or `after`."},{"name":"after","in":"query","required":false,"schema":{"type":"string"},"description":"Window read of up to `limit` messages strictly newer than the position named by an `after_cursor` from an earlier window read. Pass it back unchanged; its format may change, so do not parse or construct it. Cannot be combined with `since`, `around` or `before`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Window reads only: messages on each side of `around`, or per `before` / `after` page. Ignored on any other read."},{"name":"text","in":"query","required":false,"schema":{"type":"string","enum":["readable"]},"description":"Set to `readable` to get each message's `text` sized for reading: every link target is replaced by the word `[link]`, and the quoted history a reply repeats is removed, so each earlier message appears once. The response then carries `quoted_chars_removed`. Works with every read of this endpoint. For link targets, read an inbound message with `GET /emails/{id}/readable?links=all`, and an outbound one with `GET /sent-emails/{id}`, whose body has them (the readable read takes received email only). Any other value returns `400 validation_error`."}],"responses":{"200":{"description":"Conversation","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Conversation"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/endpoints":{"get":{"operationId":"listEndpoints","summary":"List webhook endpoints","description":"Returns all active (non-deleted) webhook endpoints.","tags":["Endpoints"],"responses":{"200":{"description":"List of endpoints","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Endpoint"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]},"post":{"operationId":"createEndpoint","summary":"Create a webhook endpoint","description":"Creates a new webhook endpoint. If a deactivated endpoint\nwith the same URL and domain exists, it is reactivated\ninstead. Subject to plan limits on the number of active\nendpoints.\n\n**Signing is account-scoped, not per-endpoint.** This call\ndoes not return any signing material; every endpoint on the\naccount uses the same webhook secret, fetched via\n`GET /account/webhook-secret`. See the API-level \"Webhook\nsigning\" section for the full wire format (header name,\nsigned string, hash algo, secret format, tolerance) and a\nlanguage-agnostic verification recipe.\n\nAfter creating the endpoint, fire a test delivery against\nit via `POST /endpoints/{id}/test` to confirm your verifier\naccepts the signature.\n\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types within their credential scope. Connected-agent credentials automatically create private subscriptions restricted to inbound email for their assigned address. Names are isolated per credential; revocation or replacement deletes its subscriptions. Address-scoped events contain parsed mail without raw MIME, signed download links, routing metadata, or sibling envelope recipients. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. Creating a pull destination requires a connected-agent credential; it creates a private subscription restricted to inbound email for the assigned address of that credential. Account API keys receive 403 pull_subscription_requires_agent_connection and can instead receive mail with GET /emails?since=<cursor>&wait=30 or an HTTP endpoint; account destinations created earlier keep receiving. Names are isolated per credential; revocation or replacement deletes its subscriptions. Address-scoped events contain parsed mail without raw MIME, signed download links, routing metadata, or sibling envelope recipients. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.","tags":["Endpoints"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEndpointInput"}}}},"responses":{"200":{"description":"Existing named pull destination resumed","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Endpoint"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"201":{"description":"Endpoint created (or reactivated)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Endpoint"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"503":{"description":"Local receiving is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/endpoints/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"patch":{"operationId":"updateEndpoint","summary":"Update a webhook endpoint","description":"Updates an active webhook endpoint. If the URL is changed, the old\nendpoint is deactivated and a new one is created (or an existing\ndeactivated endpoint with the new URL is reactivated).\n\n\nPull destinations cannot be assigned a URL, function or recipient-routing domain. Existing endpoint deletion disconnects the destination; creating a new destination does not backfill historical events.","tags":["Endpoints"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEndpointInput"}}}},"responses":{"200":{"description":"Updated endpoint","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Endpoint"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteEndpoint","summary":"Delete a webhook endpoint","description":"Delete a webhook endpoint. Inbound messages are no longer delivered to it.","tags":["Endpoints"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/endpoints/{id}/pull":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"pullWebhookEvent","summary":"Receive a pending webhook event without a public endpoint","description":"Wait up to 30 seconds for one existing event. The body string preserves the exact webhook serialization; headers and canonical occurrence/type metadata travel alongside it. A fixed lease protects the attempt while a short handler accepts input. Retry may redeliver an occurrence: deduplicate event_id. Empty delivery means no offer now, not proof all input was delivered; inspect backlog and persistent gap_count/last_gap_reason. Queue retention is 24 hours and does not extend source-content retention. Disconnecting preserves pending work. Same named destination shares consumption; different destinations get independent copies.","tags":["Endpoints"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PullWebhookInput"}}}},"responses":{"200":{"description":"Delivery operation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PullWebhookResponse"}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"408":{"description":"Request was canceled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"410":{"description":"Queued content is no longer available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"503":{"description":"Local receiving is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/endpoints/{id}/complete":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"completeWebhookEvent","summary":"Report the outcome of a local webhook delivery attempt","description":"Report normalized handling evidence for the queue, attempt and lease. Required JSON fields: queue_id, delivery_id, lease_token, mode, duration_ms. For mode=exec include exit_code; stdout includes write_succeeded; sdk includes accepted (and optional timeout/io transport_error); http includes status_code and optional transport_error, error_code, confirmed. Supply this discriminated request with --raw-body in the generated CLI. The server classifies success and retry. Duplicate identical completion is idempotent; a stale lease cannot finish a newer attempt. Exec success means durable input acceptance, not completion of agent reasoning. Only successful HTTP handling with recognized confirmation evidence can confirm content discard. Never send arbitrary handler output or HTTP response bodies.","tags":["Endpoints"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteWebhookInput"}}}},"responses":{"200":{"description":"Delivery operation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteWebhookResponse"}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"408":{"description":"Request was canceled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"410":{"description":"Queued content is no longer available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"503":{"description":"Local receiving is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/endpoints/{id}/test":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"testEndpoint","summary":"Send a test webhook","description":"Sends a sample `email.received` event to the endpoint. The request\nincludes SSRF protection (private IP rejection and DNS pinning).\nRate limited to 4 per minute and 30 per hour (non-exempt).\nSuccessful deliveries and verified-domain endpoints are exempt\nfrom the rate limit.\n","tags":["Endpoints"],"responses":{"200":{"description":"Test result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TestResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/endpoints/{id}/rules/test":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"testEndpointRules","summary":"Dry-run endpoint rules against a received email","description":"Evaluates the endpoint's filtering rules against an\nalready-received email WITHOUT delivering anything. The same\nshared matcher the live delivery paths use produces the\nverdict, so the response explains exactly why a webhook fired\nor was suppressed for that message.\n\nWhen delivery would be suppressed, `rule` names the failing\nrule and `reason` carries a human-readable explanation; both\nare null when the message matches. `evaluated` echoes the\nmessage metadata the matcher compared (size, attachments, and\nthe authenticated From identity versus the raw envelope\nsender), so a surprising verdict can be traced to its inputs.\n\nTwo independent gates are surfaced separately:\n`subscribed_to_event` reports the endpoint's event-type\nsubscription (checked before message matching), and\n`rules_valid` reports whether the stored rules blob parsed at\nall. Delivery fails OPEN on an invalid blob (the message is\ndelivered as if unfiltered), so `rules_valid: false` exposes a\nmisconfiguration that is otherwise silent.\n","tags":["Endpoints"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEndpointRulesInput"}}}},"responses":{"200":{"description":"Rule evaluation verdict","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TestEndpointRulesResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Endpoint or email not found. Both the endpoint id (path)\nand `email_id` (body) must belong to the caller's org; the\nendpoint must be active (not deleted).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Email not found"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/filters":{"get":{"operationId":"listFilters","summary":"List filter rules","description":"Returns all whitelist and blocklist filter rules.","tags":["Filters"],"responses":{"200":{"description":"List of filters","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Filter"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]},"post":{"operationId":"createFilter","summary":"Create a filter rule","description":"Creates a new whitelist or blocklist filter. Per-domain filters\nrequire a Pro plan. Patterns are stored as lowercase.\n","tags":["Filters"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFilterInput"}}}},"responses":{"201":{"description":"Filter created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Filter"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/filters/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"patch":{"operationId":"updateFilter","summary":"Update a filter rule","description":"Toggle a filter's enabled state.","tags":["Filters"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFilterInput"}}}},"responses":{"200":{"description":"Updated filter","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Filter"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteFilter","summary":"Delete a filter rule","tags":["Filters"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Delete an inbound routing filter rule. Messages already processed by the rule are unaffected."}},"/wake/schedules":{"get":{"operationId":"listWakeSchedules","summary":"List wake schedules","description":"Returns the org's wake.dispatch schedules.","tags":["Wake"],"responses":{"200":{"description":"List of wake schedules","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WakeSchedule"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]},"post":{"operationId":"createWakeSchedule","summary":"Create a wake schedule","description":"Create a cron schedule that sends a wake.dispatch command to one of your\nown function addresses. `from` and `to` must differ (no self-dispatch);\nthe cron expression and IANA timezone are validated and the first fire\ntime is computed without firing immediately.\n","tags":["Wake"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWakeScheduleInput"}}}},"responses":{"201":{"description":"Wake schedule created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WakeSchedule"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/wake/schedules/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"getWakeSchedule","summary":"Get a wake schedule","tags":["Wake"],"responses":{"200":{"description":"The wake schedule","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WakeSchedule"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Get a wake schedule"},"patch":{"operationId":"updateWakeSchedule","summary":"Update a wake schedule","description":"Update a schedule's command, args, cadence, addresses, note, or enabled\nstate. Changing the cadence (or re-enabling) recomputes the next fire time.\n","tags":["Wake"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWakeScheduleInput"}}}},"responses":{"200":{"description":"Updated wake schedule","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WakeSchedule"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteWakeSchedule","summary":"Delete a wake schedule","tags":["Wake"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Delete a wake schedule"}},"/wake/schedules/{id}/run":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"runWakeSchedule","summary":"Run a wake schedule now","description":"Fire the schedule immediately, sending one wake.dispatch via the same\nsigned-send path as a scheduled fire. Does not change the schedule's next\nfire time.\n","tags":["Wake"],"responses":{"200":{"description":"The wake interaction that was emitted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","additionalProperties":true}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/wake/authorizations":{"get":{"operationId":"listWakeAuthorizations","summary":"List wake authorizations","description":"Returns the per-target allowlist grants that authorize which senders may\nwake a function. Optionally filter by the target endpoint.\n","tags":["Wake"],"parameters":[{"name":"recipient_endpoint_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Only return grants for this target endpoint"}],"responses":{"200":{"description":"List of wake authorizations","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WakeAuthorization"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]},"post":{"operationId":"createWakeAuthorization","summary":"Create a wake authorization","description":"Grant a sender domain (and optionally a specific address and command set)\npermission to wake a target function. The domain must be fully-qualified.\n","tags":["Wake"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWakeAuthorizationInput"}}}},"responses":{"201":{"description":"Wake authorization created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WakeAuthorization"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/wake/authorizations/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"patch":{"operationId":"updateWakeAuthorization","summary":"Update a wake authorization","description":"Toggle a wake authorization's enabled state.","tags":["Wake"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWakeAuthorizationInput"}}}},"responses":{"200":{"description":"Updated wake authorization","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WakeAuthorization"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteWakeAuthorization","summary":"Delete a wake authorization","tags":["Wake"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Delete a wake authorization"}},"/wake/dispatches":{"get":{"operationId":"listWakeDispatches","summary":"List recent wake dispatches","description":"Read-only audit of recent wake.dispatch interactions for the org.","tags":["Wake"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Maximum number of rows to return (1-200, default 50)"}],"responses":{"200":{"description":"List of recent wake dispatches","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WakeDispatch"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]}},"/routes":{"get":{"operationId":"listRoutes","summary":"List recipient routes","description":"Returns the org's recipient routing rules in evaluation order. Each rule\nbinds a recipient address pattern to one endpoint; inbound mail resolves\nto a single destination at delivery time.\n","tags":["Routes"],"responses":{"200":{"description":"List of routes","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RecipientRoute"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]},"post":{"operationId":"createRoute","summary":"Create a recipient route","description":"Binds a recipient pattern to a destination. Provide exactly one of\n`endpoint_id` (an existing endpoint) or `function_id`. With `function_id`,\na dedicated route-target endpoint is minted for that function in the same\ntransaction, enabling per-address function routing (e.g.\n`alice@acme.com -> functionA`).\n","tags":["Routes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRouteInput"}}}},"responses":{"201":{"description":"Route created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecipientRoute"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/routes/reorder":{"post":{"operationId":"reorderRoutes","summary":"Reorder recipient routes","description":"Update the priority of one or more routes in a single call.","tags":["Routes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorderRoutesInput"}}}},"responses":{"200":{"description":"Updated route list","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RecipientRoute"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/routes/simulate":{"post":{"operationId":"simulateRoute","summary":"Simulate routing for a recipient","description":"Resolves where an inbound email to `recipient` would be delivered, with a\ntrace of every rule evaluated and why. Read-only; creates nothing.\n","tags":["Routes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulateRouteInput"}}}},"responses":{"200":{"description":"Routing decision","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SimulateRouteResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/routes/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"patch":{"operationId":"updateRoute","summary":"Update a recipient route","tags":["Routes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRouteInput"}}}},"responses":{"200":{"description":"Updated route","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecipientRoute"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}}},"security":[{"BearerAuth":[]}],"description":"Update a recipient route"},"delete":{"operationId":"deleteRoute","summary":"Delete a recipient route","tags":["Routes"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Delete a recipient route"}},"/webhooks/deliveries":{"get":{"operationId":"listDeliveries","summary":"List webhook deliveries","description":"Returns a paginated list of webhook delivery attempts. Each delivery\nincludes a nested `email` object with sender, recipient, and subject.\n","tags":["Webhook Deliveries"],"parameters":[{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Pagination cursor from a previous response's `meta.cursor` field.\nFormat: `{ISO-datetime}|{id}`\n"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Number of results per page"},{"name":"email_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter by email ID"},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","delivered","header_confirmed","failed"]},"description":"Filter by delivery status"},{"name":"date_from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Filter deliveries created on or after this timestamp. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."},{"name":"date_to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Filter deliveries created on or before this timestamp. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."}],"responses":{"200":{"description":"Paginated list of deliveries","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DeliverySummary"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]}},"/webhooks/deliveries/{id}/replay":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^\\d+$"},"description":"Delivery ID (numeric)"}],"post":{"operationId":"replayDelivery","summary":"Replay a webhook delivery","description":"Re-sends the stored webhook payload from a previous delivery attempt.\nIf the original endpoint is still active, it is targeted. If the\noriginal endpoint was deleted, the oldest active endpoint is used.\nDeactivated endpoints cannot be replayed to. Rate limited per-org,\nsharing an org-wide budget with email replays.\n","tags":["Webhook Deliveries"],"responses":{"200":{"description":"Replay result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ReplayResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/send-permissions":{"get":{"operationId":"getSendPermissions","summary":"List send-permission rules","description":"Returns a flat list of rules describing every recipient the\ncaller may send to. Each rule has a `type`, a kind-specific\npayload, and a human-readable `description`. If any rule\nmatches the recipient, /send-mail will accept the send under\nthe recipient-scope check.\n\nThe endpoint is the answer to \"where can I send\" without\nexposing internal entitlement names. Agents that don't\nrecognize a `type` can still read the `description` prose\nand act on it.\n\nRule kinds, ordered broadest-first so an agent can stop\nscanning at the first match:\n\n  1. `any_recipient` (one entry, only when the org can send\n     anywhere): every other rule below it is redundant.\n  2. `managed_zone` (always emitted, one per Primitive-managed\n     zone): sends to any address at *.primitive.email or\n     *.email.works always succeed; no entitlement required.\n  3. `your_domain` (one per active verified outbound domain\n     owned by the org): sends to that domain are approved.\n  4. `address` (one per address that has authenticated\n     inbound mail to the org, capped at `meta.address_cap`):\n     sends to that exact address are approved.\n\nThe list is informational, not an authorization check.\n/send-mail remains the source of truth on whether an\nindividual send will succeed (it also enforces the\nfrom-address and the `send_mail` entitlement, which are\nnot recipient-scope concerns and are not represented here).\n","tags":["Sending"],"responses":{"200":{"description":"Send-permission rules for the caller's org","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SendPermissionRule"}},"meta":{"$ref":"#/components/schemas/SendPermissionsMeta"}},"required":["data","meta"]}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]}},"/send-mail":{"post":{"operationId":"sendEmail","summary":"Send outbound email","description":"Sends an outbound email through Primitive's outbound relay. By default\nthe request returns once the relay accepts the message for delivery.\nSet `wait: true` to wait for the first downstream SMTP delivery outcome.\n\n**Host routing.** /send-mail is served by the canonical API host\n(`https://api.primitive.dev/v1`) so the request body can carry\ninline attachments up to ~30 MiB raw. The legacy dashboard\ncompatibility host (`https://www.primitive.dev/api/v1`) also accepts\n/send-mail, but Vercel request body limits apply before proxying.\nThe typed SDKs route /send-mail to the canonical API host\nautomatically.\n","servers":[{"url":"https://api.primitive.dev/v1","description":"Canonical API host (recommended)"},{"url":"https://www.primitive.dev/api/v1","description":"Legacy compatibility host (Vercel body limit applies)"}],"tags":["Sending"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional customer-supplied idempotency key. If omitted, Primitive\nderives one from the canonical request payload and echoes the\neffective value in the `Idempotency-Key` response header.\n"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMailInput"}}}},"responses":{"200":{"description":"Outbound relay result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SendMailResult"}}}]}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"},"Idempotency-Key":{"$ref":"#/components/headers/IdempotencyKey"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"410":{"description":"The prior send was deleted. Its idempotency key or automatic reply suppression remains reserved, so this request will not send another message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"500":{"description":"Primitive encountered an internal error When this is the stored outcome of an earlier send (same idempotency key) and not a new attempt, the response carries `Idempotent-Replayed: true`, `error.details.idempotency` and `error.details.dedup_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"internal_error","message":"Internal server error"}}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"502":{"description":"Primitive could not complete the downstream SMTP request When this is the stored outcome of an earlier send (same idempotency key) and not a new attempt, the response carries `Idempotent-Replayed: true`, `error.details.idempotency` and `error.details.dedup_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_unreachable","message":"Outbound SMTP service request failed"}}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"503":{"description":"Primitive is temporarily unable to process the request When this is the stored outcome of an earlier send (same idempotency key) and not a new attempt, the response carries `Idempotent-Replayed: true`, `error.details.idempotency` and `error.details.dedup_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_capacity_exhausted","message":"Outbound capacity is temporarily exhausted"}}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"504":{"description":"The outbound relay did not answer in time. When this is the stored outcome of an earlier send (same idempotency key) and not a new attempt, the response carries `Idempotent-Replayed: true`, `error.details.idempotency` and `error.details.dedup_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}}},"security":[{"BearerAuth":[]}]}},"/semantic-search":{"post":{"operationId":"semanticSearch","summary":"Semantic search across received and sent mail","description":"Ranked search across both received and sent mail. The `mode`\nfield selects the ranking strategy:\n\n- `keyword`: lexical full-text matching only (no embeddings).\n- `semantic`: meaning-based matching using vector embeddings.\n- `hybrid` (default): blends the semantic and keyword signals.\n\nResults are ordered by a relevance `score`. Every row reports the\nfields it matched (`matched_fields`), a match-centered excerpt per\nfield (`snippets`), and a `score_breakdown` whose components account\nfor the `score`. Page through results by passing the prior\nresponse's `meta.cursor` back as `cursor`.\n\nRequires the Pro plan and the `semantic_search_enabled`\nentitlement; callers without them receive `403`.\n","tags":["Search"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SemanticSearchInput"}}}},"responses":{"200":{"description":"Ranked search results","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SemanticSearchResult"}},"meta":{"$ref":"#/components/schemas/SemanticSearchMeta"}},"required":["data","meta"]}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"500":{"description":"Primitive encountered an internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"internal_error","message":"Internal server error"}}}}},"503":{"description":"Primitive is temporarily unable to process the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_capacity_exhausted","message":"Outbound capacity is temporarily exhausted"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/outbound/status":{"get":{"operationId":"getOutboundStatus","summary":"Get outbound sending readiness","description":"The \"what can I send From?\" bootstrap, the outbound mirror of\n/inbox/status. Returns per-domain sending readiness for every\ndomain in the caller's org, plus the flat `sendable_domains`\nlist of From-domains the org may send from right now. That\nlist is the same set echoed in a `cannot_send_from_domain`\nerror's details, so orienting here before a send and\nrecovering from that error use identical data.\n\nEach domain's `status` collapses the sending prerequisites\ninto one actionable state: `sendable`, `pending_ownership`\n(ownership TXT not verified), `pending_outbound_dns`\n(SPF/DKIM/DMARC not verified), or `inactive` (domain\ndeactivated). `next_actions` lists concrete remediation steps,\neach with a suggested CLI command where one exists.\n","tags":["Sending"],"responses":{"200":{"description":"Outbound readiness summary","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/OutboundStatus"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]}},"/repeating-sends":{"get":{"operationId":"listRepeatingSends","summary":"List repeating sends","description":"Repeating sends you created, newest first. A repeat is visible to the\nmember who created it, or to any organization API key or member for\nrepeats created with an organization API key. Agent credentials get\n403.\n","tags":["Repeating Sends"],"parameters":[{"name":"to","in":"query","required":false,"schema":{"type":"string"},"description":"Only return repeats sent to this address."},{"name":"status","in":"query","required":false,"schema":{"$ref":"#/components/schemas/RepeatingSendStatus"},"description":"Only return repeats in this status."}],"responses":{"200":{"description":"Repeating sends","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RepeatingSend"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}}},"security":[{"BearerAuth":[]}]}},"/repeating-sends/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"getRepeatingSend","summary":"Get a repeating send","tags":["Repeating Sends"],"responses":{"200":{"description":"The repeating send","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RepeatingSend"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Get a repeating send"},"patch":{"operationId":"updateRepeatingSend","summary":"Update a repeating send","description":"Pause, resume or cancel a repeat, or change its cadence, limits,\nmessage or whether the recipient may stop it. `active` resumes a paused\nrepeat or one the recipient stopped; the next message goes out within\nabout a minute. A null `only_if_recipient_idle_minutes`, `max_sends`\nor `until` clears that limit.\n","tags":["Repeating Sends"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRepeatingSendRequest"}}}},"responses":{"200":{"description":"The updated repeating send","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RepeatingSend"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteRepeatingSend","summary":"Delete a repeating send","description":"Deletes the repeat and cancels its pending message. Messages already sent are not affected.","tags":["Repeating Sends"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/sent-emails":{"get":{"operationId":"listSentEmails","summary":"List outbound sent emails","description":"Returns a paginated list of OUTBOUND emails the caller's\norg has sent via /send-mail (and /emails/{id}/reply, which\nforwards through /send-mail). Includes every recorded\nattempt, including gate-denied attempts that the agent\nnever called and rows still in `queued` state.\n\nUnder an address-bound agent connection credential, the\nlist holds this agent's own sends plus delivered sends from\nother addresses in the org that name this agent in to, cc\nor bcc and have matching inbound delivery evidence at this\nagent. Pass `from` with the agent's own address to list\nonly its own sends.\n\nFor inbound mail received at your verified domains, see\n/emails. There is no unified send/receive history endpoint;\nthe two surfaces are intentionally separate because the\nunderlying tables, statuses, and lifecycle differ.\n\nEmail bodies (`body_text`, `body_html`) are NOT included on\nlist rows so a 50-row page can't balloon into a multi-MB\nresponse when sends are near the 5MB body cap. Use\n/sent-emails/{id} to fetch a single row with bodies, or\ncross-reference by `client_idempotency_key` if the caller\nalready has the body locally.\n","tags":["Sending"],"parameters":[{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Pagination cursor from a previous response's `meta.cursor` field.\nFormat: `{ISO-datetime}|{id}`\n"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Number of results per page"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/SentEmailStatus"},"description":"Filter to rows in this status. Useful for polling\nqueued rows that haven't transitioned, auditing\ngate-denied attempts, or listing only successful\ndeliveries.\n"},{"name":"request_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter to the row matching a specific server-issued\n`request_id`. The /send-mail response surfaces\n`request_id` on every send; this lookup lets the\ncaller find the historical row for a given live call\nwithout remembering its `id`.\n"},{"name":"idempotency_key","in":"query","schema":{"type":"string","minLength":1,"maxLength":255},"description":"Filter to rows with the given `client_idempotency_key`.\nMultiple rows can share a key (a retry that hit the\nidempotent-replay path returns the same row, but a\nretry with a DIFFERENT canonical payload under the\nsame key is rejected by /send-mail before the row is\nwritten, so duplicates are bounded).\n"},{"name":"date_from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Inclusive lower bound on `created_at`. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."},{"name":"date_to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Inclusive upper bound on `created_at`. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."},{"name":"q","in":"query","schema":{"type":"string"},"description":"Literal case-insensitive substring filter over the\nsubject, the retained plain-text body, the sender, and\nthe To, CC and BCC recipients of each send. Matched\nverbatim: `%`, `_` and `\\` are ordinary characters and\ndo not act as wildcards, so a search for `10%` finds the\nliteral string `10%` rather than everything beginning\n`10`.\n\nLeading and trailing whitespace is trimmed, and the\nresult must be 3 to 500 UTF-16 code units. The\nthree-character minimum is the search index's: a shorter\npattern cannot be answered from the index and would scan\nthe whole mailbox, so it is rejected rather than served\nas a request that times out.\n\nThose bounds are deliberately NOT declared as\n`minLength`/`maxLength`. Both count Unicode code points\non the value as sent, while the server trims first and\ncounts UTF-16 code units, and the two disagree in both\ndirections: two astral characters are 2 code points but\n4 code units, so a declared minimum would reject a query\nthe server accepts, and `\"<499 chars> \"` is 500 code\npoints before trimming and 499 after, so a declared\nmaximum would reject another. Declaring a bound the\nserver does not enforce is worse than declaring none,\nbecause the client refuses locally and the caller never\nlearns why. The server validates; a violation is a `400`.\n\nRecipients are matched one address at a time rather than\nagainst the serialized recipient list, so a query\ncontaining a quote, comma or bracket matches only where\nthat character occurs inside an address. Bodies are\nsearched only where the plain-text body was retained;\nHTML-only bodies are not searched.\n\nNot served by every deployment. Where sent-mail search\nis not enabled the endpoint answers `503` with code\n`sent_mail_search_unavailable`, which callers should\ntreat as a capability answer and fall back on rather\nthan as an outage.\n"},{"name":"from","in":"query","schema":{"type":"string","format":"email","maxLength":320},"description":"Exact case-insensitive sender mailbox filter. Returns\nonly sends whose from address equals this value; it is\nnot a substring match, so a partial mailbox matches\nnothing. Use `q` to search sender text loosely.\nCombines with every other filter, and unlike `q` it is\nserved by every deployment.\n"},{"name":"since","in":"query","required":false,"schema":{"type":"string"},"description":"Forward tail. Returns only sends whose last update is strictly newer than this cursor, oldest update first, so a sender can stream delivery outcomes instead of re-reading history. Outbound rows change state after they are created (queued, submitted_to_agent, delivered, bounced, deferred), so the tail is ordered on last-update time rather than creation time: a status transition on a send you have already read re-enters the tail and is delivered again with its new status. Format is `{ISO 8601 datetime}|{UUID}` at microsecond precision. Echo back `meta.cursor` from the previous tail page verbatim; do not truncate it, and do not reuse a `cursor` value here, because `cursor` is a creation-time position and `since` is an update-time one. Start a tail from a synthetic position such as `2026-01-01T00:00:00.000000Z|00000000-0000-0000-0000-000000000000`. Cannot be combined with `cursor`. An empty page returns `meta.cursor: null`, which means nothing moved: keep the position you already hold and send it again, rather than reading the null as a position. Update positions are serialized per organization through transaction commit. This is a latest-state feed, not an immutable event log: multiple changes between reads may coalesce, and deleted sends are absent."},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":30,"default":0},"description":"Long-poll. Hold the request up to this many seconds waiting for a send to appear or change state past `since`, returning as soon as one does, or an empty page when the window elapses. Only meaningful together with `since`: a non-zero `wait` without `since` is rejected, while `wait=0` is exactly equivalent to omitting it and so is accepted anywhere and holds nothing. Maximum 30 seconds; 0 (the default) returns immediately. Keeping one request in flight and reissuing it with the position you hold is the low-latency way to follow outbound delivery status."}],"responses":{"200":{"description":"Page of sent-email summaries","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SentEmailSummary"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"503":{"description":"Sent-mail text search is not enabled in this deployment,\nso `q` cannot be served. A capability answer, not an\noutage: it will not clear on retry, and the same request\nwithout `q` succeeds. Fall back to whatever local\nmatching the caller has. Distinguished from the generic\n503 by its `sent_mail_search_unavailable` code.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"sent_mail_search_unavailable","message":"Sent-mail text search (`q`) is not enabled in this environment."}}}}}},"security":[{"BearerAuth":[]}]}},"/sent-emails/{id}":{"delete":{"operationId":"deleteSentEmail","summary":"Delete a sent email","tags":["Sending"],"description":"Permanently deletes eligible sender mailbox history and its owned attachment archive and transient payload. Allows delivered, bounced, agent_failed, gate_denied, canceled, deferred and wait_timeout; other states return 409 (cancel scheduled sends first). Preserves recipient copies, received replies and independent interaction/delivery audit. This does not recall or cancel delivery already admitted. A minimal non-content record preserves occupied idempotency keys and delivered-reply deduplication. Keys already released by cancellation, gate denial or a never-attempted failure remain reusable; deleting those records does not revoke their existing retry permission. Repeating a completed deletion returns success; unknown or foreign IDs return 404. Organization API keys, sessions and OAuth are supported; Function and connected-agent credentials are denied.","security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"responses":{"200":{"description":"Deleted or already deleted","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"],"additionalProperties":false}}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"500":{"description":"Database failure. Files may already be removed while the message record and cleanup references remain. Retry the same DELETE; failure does not restore files.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Storage cleanup failed. Some files may already be removed; the message record and cleanup references remain for an idempotent retry. A failed deletion does not restore files.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"operationId":"getSentEmail","summary":"Get a sent email by id","description":"Returns the full sent-email record by id, including\n`body_text` and `body_html` (omitted from the listing\nendpoint to keep paginated responses small). Use this when\ndiagnosing a specific send, e.g. inspecting the receiver's\nSMTP response on a `bounced` row or pulling the gate\ndenial detail on a `gate_denied` row.\n\nUnder an address-bound agent connection credential, only\nids in the same scope as /sent-emails resolve: this agent's\nown sends, and delivered sends addressed to it with\nmatching inbound delivery evidence. Other ids return 404.\n","tags":["Sending"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"responses":{"200":{"description":"Sent-email detail","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SentEmailDetail"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"patch":{"operationId":"rescheduleSentEmail","summary":"Reschedule a scheduled send","description":"Moves a STILL-SCHEDULED send (status `scheduled`) to a new\nexecution time. The new `scheduled_at` must be in the future\nand at most 30 days out, the same bounds as the create-time\nfield on /send-mail.\n\nThe update is a compare-and-swap on `status = 'scheduled'`:\nonce the scheduler has claimed the row for execution, or it\nwas already canceled or executed, the update loses and the\ncall returns a 409 `not_scheduled` conflict naming the row's\ncurrent status. A due send can therefore never be moved out\nfrom under an in-progress execution.\n\nReturns the full updated sent-email record on success.\n","tags":["Sending"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentEmailRescheduleInput"}}}},"responses":{"200":{"description":"Rescheduled sent-email detail","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SentEmailDetail"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The send exists but is no longer in `scheduled` status,\nso it cannot be rescheduled. `error.details` carries the\nrow's `sent_email_id` and current `status`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_scheduled","message":"cannot reschedule: this sent email is no longer scheduled (status: delivered)","details":{"sent_email_id":"7f9c24e8-3b1a-4b6e-9c11-0d5f8a2e6c3d","status":"delivered"}}}}}}},"security":[{"BearerAuth":[]}]}},"/agent-connections":{"post":{"operationId":"createAgentConnection","tags":["Agent Connections"],"summary":"create Agent Connection","description":"Create an address-bound agent. Without an explicit address, allocate a readable name with a bounded collision suffix. Persist create_request_id before dispatch to recover the same current connection on retry; recovered responses have invitation:null and never rotate a key. Changed choices or membership return 409 connection_create_request_conflict; a removed identity returns 409 connection_removed. A pending recovered connection can use inviteAgentConnection with pending_only:true once to obtain a fresh invitation.","security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"anyOf":[{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/AgentConnection"},"invitation":{"type":"object","properties":{"claim_url":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["claim_url","expires_at"],"additionalProperties":false}},"required":["connection","invitation"],"additionalProperties":false},{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/AgentConnection"},"recovered":{"type":"boolean","const":true},"invitation":{"type":"null"}},"required":["connection","recovered","invitation"],"additionalProperties":false}]}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"address":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"owner_address":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"ownership_kind":{"type":"string","enum":["personal","shared"]},"create_request_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["name"],"additionalProperties":false}}}}},"get":{"operationId":"listAgentConnections","tags":["Agent Connections"],"summary":"list Agent Connections","description":"Address-bound external runtime pairing. Any current human organization member may create and manage their personal agent connections. Organization owners and admins may manage all connections and explicitly create shared ones. An owner removed from the organization loses personal agent access, and rejoining does not revive the old connection. Organization API keys cannot manage connections. A current connected credential may disconnect only its own exact address. Claim is authorized only by its one-use invitation. Connected means a real challenge was received and a reply sent by the current bound credential was received back. Invitations expire after 15 minutes. Reconnection preserves the address and revokes previous credentials. Status responses contain no credentials. Runtime credentials allow address-scoped mail operations, organization note reads, own-address note writes, exact-address self-disconnect and self-rename, network discovery when can_view is enabled, and recipient-bound network contact admission. Hidden connections are left out unless include_hidden=true, which returns them with hidden, hidden_reason and hidden_at set. Hiding is display only: a hidden agent stays connected, keeps its credential, and still receives mail and wakes.","security":[{"BearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"limit for agent connections.","schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"cursor for agent connections.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},{"name":"owner","in":"query","required":false,"description":"owner for agent connections.","schema":{"type":"string","const":"self"}},{"name":"include_hidden","in":"query","required":false,"description":"include_hidden for agent connections.","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/AgentConnection"}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-connections/{address}/invitation":{"post":{"operationId":"inviteAgentConnection","tags":["Agent Connections"],"summary":"invite Agent Connection","description":"Explicitly reconnect an existing agent. Set pending_only:true for Continue setup: the locked connection must still be pending or the server returns 409 connection_already_claimed without deleting any credential. Generic reconnect revokes old credentials and returns a new single-use invitation. An uncertain response must not be automatically retried.","security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"address for agent connections.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/AgentConnection"},"invitation":{"type":"object","properties":{"claim_url":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["claim_url","expires_at"],"additionalProperties":false}},"required":["connection","invitation"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"503":{"description":"connection_cleanup_pending: the disconnected connection's old credential is still being removed. Nothing was changed and the cleanup progress is kept; retry after the Retry-After interval.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pending_only":{"type":"boolean"}},"additionalProperties":false}}}}}},"/agent-connections/{address}":{"delete":{"operationId":"revokeAgentConnection","tags":["Agent Connections"],"summary":"revoke Agent Connection","description":"Disconnect an agent and invalidate its bound credential. A current personal owner may disconnect their own agent; organization owners and admins may disconnect any connection. A connected agent may disconnect only its own exact address using its current bound credential. This preserves the connection record, mail, notes and domain. A current personal owner may permanently remove their own revoked record.","security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"address for agent connections.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/AgentConnection"}},"required":["connection"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-connections/{address}/remove":{"post":{"operationId":"removeAgentConnection","tags":["Agent Connections"],"summary":"remove Agent Connection","description":"Permanently remove a revoked connection record. A current personal owner may permanently remove their own record; organization owners and admins may remove any record. Disconnect first using revokeAgentConnection; an active connection returns 409 connection_not_revoked. Missing or already removed records return 404. Mail, address notes, domains and external runtimes are preserved. The removed address stays reserved within the organization; old credentials and invitations remain invalid.","security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"address for agent connections.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"503":{"description":"connection_cleanup_pending: the disconnected connection's old credential is still being removed. Nothing was changed and the cleanup progress is kept; retry after the Retry-After interval.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/agent-connections/claim":{"post":{"operationId":"claimAgentConnection","tags":["Agent Connections"],"summary":"claim Agent Connection","description":"Address-bound external runtime pairing. Any current human organization member may create and manage their personal agent connections. Organization owners and admins may manage all connections and explicitly create shared ones. An owner removed from the organization loses personal agent access, and rejoining does not revive the old connection. Organization API keys cannot manage connections. A current connected credential may disconnect only its own exact address. Claim is authorized only by its one-use invitation. Connected means a real challenge was received and a reply sent by the current bound credential was received back. Invitations expire after 15 minutes. Reconnection preserves the address and revokes previous credentials. Status responses contain no credentials. Runtime credentials allow address-scoped mail operations, organization note reads, own-address note writes, exact-address self-disconnect and self-rename, network discovery when can_view is enabled, and recipient-bound network contact admission.","security":[],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/AgentConnection"},"org_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"owner_address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"owner_member_address":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"api_key":{"type":"string"},"api_base_url":{"type":"string","format":"uri"},"presence_profile":{"type":"object","properties":{"protocol":{"type":"string","const":"primitive.presence"},"version":{"type":"number","const":1},"authentication_profile":{"type":"string","const":"primitive-issued-v1"},"return_address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["protocol","version","authentication_profile","return_address"],"additionalProperties":false}},"required":["connection","org_id","owner_address","owner_member_address","api_key","api_base_url"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":32,"maxLength":256},"capabilities":{"maxItems":1,"type":"array","items":{"type":"string","const":"primitive.presence/1"}}},"required":["token"],"additionalProperties":false}}}}}},"/agent-connections/setup":{"get":{"operationId":"agentConnectionSetup","tags":["Agent Connections"],"summary":"agent Connection Setup","description":"Instructions for pairing an external runtime. GET never consumes an invitation. The invitation token stays in the URL fragment and is submitted only in the claim POST body.","security":[],"parameters":[],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/sent-emails/{id}/cancel":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"cancelSentEmail","summary":"Cancel a scheduled send","description":"Cancels a STILL-SCHEDULED send (status `scheduled`), moving it\nto the terminal `canceled` status. Nothing is dispatched and\nthe row is kept for historical lookup with `canceled_at` set.\n\nUses the same compare-and-swap guard as reschedule: once the\nscheduler has claimed the row for execution, or it was already\ncanceled or executed, the call returns a 409 `not_scheduled`\nconflict naming the row's current status. Canceling can\ntherefore never race an in-progress execution; a send that\nreports `canceled` was never handed to the delivery path.\n\nReturns the full updated sent-email record on success.\n","tags":["Sending"],"responses":{"200":{"description":"Canceled sent-email detail","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SentEmailDetail"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The send exists but is no longer in `scheduled` status,\nso it cannot be canceled. `error.details` carries the\nrow's `sent_email_id` and current `status`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_scheduled","message":"cannot cancel: this sent email is no longer scheduled (status: delivered)","details":{"sent_email_id":"7f9c24e8-3b1a-4b6e-9c11-0d5f8a2e6c3d","status":"delivered"}}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/sent-emails/{id}/reply":{"get":{"operationId":"awaitReply","summary":"Get (or wait for) the reply to a sent email","description":"Returns the first threaded inbound reply to a send, keyed by\nthe inbound email's `reply_to_sent_email_id`. This is the\ncanonical \"did a reply arrive for this send?\" call: after\n/send-mail, poll (or long-poll) here instead of hand-rolling\nan /emails/search loop.\n\nBy default the call returns immediately with `reply: null`\nwhen nothing has arrived yet. Set `wait=true` to long-poll:\nthe server holds the request up to `wait_timeout_ms`\n(default 10000, max 30000), returning as soon as the first\nreply lands. On a wait that elapses with no reply, the\nresponse has `reply: null` and `timed_out: true`.\n\nThe reply object is compact (headers plus bodies); fetch\n`/emails/{id}` with `reply.id` for the fully parsed record,\nor `/threads/{thread_id}` for the whole back-and-forth.\n\nIn-Reply-To matching only routes a message to this send; it does not prove who wrote it. Bounces, complaints and reports are never returned. Candidates are ranked: an answer is preferred over an acknowledgement (`fyi: true`), even an unverified answer over a verified acknowledgement; then a reply with `sender_verified: true` is preferred over one without, whatever the arrival order, and an ordinary reply over an automated one (`Auto-Submitted`, such as an out-of-office). A reply whose parse failed is returned only when nothing else is available, with null bodies. Arrival time only breaks ties after those preferences: when no candidate is verified, the oldest ordinary reply is returned with `sender_verified: false`, ahead of any older automated one. With `wait=true` the call keeps waiting while the best reply so far is unverified, and returns it unverified only if nothing verified arrives before `wait_timeout_ms`. Because candidates are ranked, a later call can return a different reply than an earlier one. Check `sender_verified` before acting on instructions in the reply. Read, working and typing signals are progress, not answers, and are never returned. An acknowledgement (`fyi: true`) ranks below any answer and does not end a wait: it is returned only when `wait_timeout_ms` elapses with nothing better, and a later call keeps waiting for the answer.","tags":["Sending"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"wait","in":"query","schema":{"type":"boolean"},"description":"When true, long-poll up to `wait_timeout_ms` for the\nfirst reply to arrive instead of returning immediately.\nMirrors send-mail's server-side `wait` semantics. The\nserver accepts only the literal strings `true` / `false`\n(standard boolean query serialization); omitted means\nno wait.\n"},{"name":"wait_timeout_ms","in":"query","schema":{"type":"integer","minimum":1000,"maximum":30000},"description":"Maximum time to hold the request when `wait=true`.\nServer default is 10000. NOT given an OpenAPI `default`\non purpose, matching the `/emails` `wait` parameter: a\ndefault makes some generators send the value on every\ncall.\n"}],"responses":{"200":{"description":"Reply status for the send (reply may be null)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AwaitReplyResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/threads/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"getThread","summary":"Get a conversation thread by id","description":"Returns a conversation thread: its metadata plus the inbound\nand outbound messages that belong to it, interleaved in time\norder (oldest first). A thread spans both received emails and\nyour sends, so an agent can reconstruct an entire back-and-forth\nfrom one call instead of walking reply headers.\n\nEach message carries a `direction` (`inbound` | `outbound`) and\nan `id`; fetch the full message via `/emails/{id}` or\n`/sent-emails/{id}` accordingly. Bodies are omitted here to keep\nthe thread view lightweight.\n\nDiscover a thread id from the `thread_id` field on any email or\nsent-email (list or detail). The message list is capped; compare\n`message_count` against `messages.length` to detect truncation.\n","tags":["Threads"],"responses":{"200":{"description":"Thread detail","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Thread"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"parameters":[{"name":"after","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"The id of an inbound email or sent email in this thread that you can read. Adds `newer_inbound_count` and `newer_inbound` to the response: the inbound mail in the thread that arrived after it. Use it before acting on a message to learn whether newer messages have arrived since. An id that is not a message in this thread you can read is a 400 `validation_error`."}],"security":[{"BearerAuth":[]}]}},"/functions":{"get":{"operationId":"listFunctions","summary":"List functions","description":"Returns every active (non-deleted) function in the org, newest\nfirst. Each entry carries deploy status and timestamps. To\ninspect the source code or deploy errors, use `GET /functions/{id}`.\n","tags":["Functions"],"responses":{"200":{"description":"List of functions","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FunctionListItem"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]},"post":{"operationId":"createFunction","summary":"Deploy a function","description":"Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to a webhook event whose `event` field is\n`email.received` for normal inbound mail, or a machine-mail type\n(`email.bounced`, `email.tls_report`, `email.dmarc_report`,\n`email.dmarc_failure`) for bounces and reports. Code is bundled\nbefore being uploaded; ship a single self-contained file rather\nthan relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Routing.** On successful deploy, the function code is live\nin the runtime, but inbound mail will not reach it until at\nleast one route is bound. Routes are managed from the Primitive\ndashboard. A `deploy_status` of `deployed` means the script is\ninstalled, not that the function is receiving mail. The\ninternal runtime URL is not returned by the API and is not a\ncustomer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n","tags":["Functions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFunctionInput"}}}},"responses":{"201":{"description":"Function created and deployed","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreateFunctionResult"}}}]}}}},"400":{"description":"Invalid request parameters or customer-correctable deploy rejection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"409":{"description":"A function with this name already exists in the org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"424":{"description":"Function deploy could not be completed; previously deployed code remains live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"deploy_failed","message":"Function deploy failed"}}}}},"429":{"description":"Function deploy could not be completed; previously deployed code remains live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"deploy_failed","message":"Function deploy failed"}}}}},"503":{"description":"Function deploy could not be completed; previously deployed code remains live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"deploy_failed","message":"Function deploy failed"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/functions/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"getFunction","summary":"Get a function","description":"Returns the full record for a function, including its current\nsource code and the deploy status / error from the most recent\ndeploy attempt.\n","tags":["Functions"],"responses":{"200":{"description":"Function record","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FunctionDetail"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"put":{"operationId":"updateFunction","summary":"Update and redeploy a function","description":"Replaces the function's source code with the body's `code` and\ntriggers a redeploy. Same size limits as `POST /functions`.\nUse this verb to push secret writes into the running handler:\npassing the same `code` re-runs the deploy and refreshes the\nbinding set with the latest values from the secrets table.\n\nOn deploy failure, the previously-deployed code stays live; the\nruntime never serves a half-built bundle. The response uses\n`error.code` `deploy_failed`, and the function's `deploy_error`\nfield carries the latest deploy error for dashboard/API reads.\n","tags":["Functions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFunctionInput"}}}},"responses":{"200":{"description":"Updated function","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FunctionDetail"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters or customer-correctable deploy rejection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"424":{"description":"Function deploy could not be completed; previously deployed code remains live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"deploy_failed","message":"Function deploy failed"}}}}},"429":{"description":"Function deploy could not be completed; previously deployed code remains live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"deploy_failed","message":"Function deploy failed"}}}}},"503":{"description":"Function deploy could not be completed; previously deployed code remains live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"deploy_failed","message":"Function deploy failed"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteFunction","summary":"Delete a function","description":"Delete a hosted inbound function. It stops running on future inbound messages.","tags":["Functions"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"502":{"description":"Primitive could not complete the downstream SMTP request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_unreachable","message":"Outbound SMTP service request failed"}}}}}},"security":[{"BearerAuth":[]}]}},"/functions/{id}/test":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"testFunction","summary":"Send a test invocation","description":"Sends a real test email from a Primitive-controlled sender to a\nlocal-part on one of the org's verified inbound domains. By\ndefault the recipient is a synthetic\n`__primitive_function_test+<random>@<domain>` address on a\ndomain selected to route to the function. Scoped functions use\ntheir scoped domain; fallback functions use a domain that has\nno enabled domain-scoped endpoint. Pass `local_part` to\noverride and exercise routing logic that branches on a specific\nrecipient (the common pattern when one function handles multiple\ninboxes like `summarize@` and `action@`). The function fires\nthrough the normal MX delivery path, so reply / send-mail calls\nfrom inside the handler against the inbound's `email.id` work\nthe same as in production. Returns immediately after the send is\nqueued; the invocation appears on the function's invocations\nlist within a few seconds.\n\nRequires that the function is currently `deployed`. Returns 422\nif the function is in `pending` or `failed` state, or if the\norg has no verified inbound domain to receive the test mail.\nReturns 400 if `local_part` is set to a value that does not\nmatch the local-part character set.\n","tags":["Functions"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"local_part":{"type":"string","description":"Override the synthetic local-part. When set, the\ntest email is sent to `<local_part>@<picked-domain>`\ninstead of the default\n`__primitive_function_test+<random>@<picked-domain>`.\nMust start with an alphanumeric and contain only\nletters, digits, dots, plus signs, hyphens, or\nunderscores; 1-64 characters total.\n","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$","example":"summarize"}}}}}},"responses":{"200":{"description":"Test send queued","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TestInvocationResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"422":{"description":"Function not in a state that can be invoked, or no inbound domain configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Primitive could not complete the downstream SMTP request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_unreachable","message":"Outbound SMTP service request failed"}}}}},"503":{"description":"Sending agent misconfigured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/functions/{id}/test-runs/{run_id}/trace":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"run_id","in":"path","required":true,"description":"Function test run id returned by POST /functions/{id}/test.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getFunctionTestRunTrace","summary":"Get a function test run trace","description":"Returns the current end-to-end trace for a function test run.\nThe trace is intentionally partial while the test is still in\nflight: callers can poll this endpoint and watch it fill in\nfrom send -> inbound -> webhook deliveries -> outbound\nrequests, logs, and replies.\n","tags":["Functions"],"responses":{"200":{"description":"Function test run trace","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FunctionTestRunTrace"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/functions/routing-topology":{"get":{"operationId":"getOrgRoutingTopology","summary":"Get the org's function routing topology","description":"Returns a single snapshot of how inbound mail is routed across\nthis org's active domains and functions: which active domain has\nwhich function bound, the org's fallback function (if any), and\nevery deployed function with no route bound. Use this to answer\n\"which of my functions actually receive mail?\" diagnostically.\n","tags":["Functions"],"responses":{"200":{"description":"Routing topology","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RoutingTopology"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}}},"security":[{"BearerAuth":[]}]}},"/functions/{id}/routing":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"getFunctionRouting","summary":"Get a function's current route binding","description":"Returns the endpoint binding for the function, or null when no\nroute is currently bound. The binding identifies whether the\nfunction receives mail for a specific domain (scoped) or for any\nactive domain that has no scoped binding (fallback).\n","tags":["Functions"],"responses":{"200":{"description":"Function routing","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"oneOf":[{"$ref":"#/components/schemas/FunctionRouting"},{"type":"null"}]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/functions/{id}/route":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"put":{"operationId":"setFunctionRoute","summary":"Bind a route to a function","description":"Binds inbound mail to this function. The route target is either\na specific verified domain (scoped) or the org's fallback (any\nactive domain with no scoped binding). If another function is\nalready bound at the target, returns a `conflict` envelope\ndescribing the holder; re-issue with `takeover: true` to\ndeactivate that prior binding and install this one.\n","tags":["Functions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FunctionRouteBody"}}}},"responses":{"200":{"description":"Route bound, or conflict requiring takeover","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FunctionRouteResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"unsetFunctionRoute","summary":"Unbind any route from a function","description":"Deactivates every active endpoint bound to this function. The\nfunction stays deployed but stops receiving inbound mail. Safe\nto call when no route is currently bound (no-op).\n","tags":["Functions"],"responses":{"200":{"description":"Route unbound","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"unrouted":{"type":"boolean","enum":[true]}},"required":["unrouted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/functions/{id}/secrets":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"listFunctionSecrets","summary":"List a function's secrets","description":"Returns metadata for every secret bound to the function, with\nmanaged entries (provisioned by Primitive) listed first and\nuser-set entries listed alphabetically after. **Values are\nnever returned.** Secret writes are write-only.\n\nManaged entries (e.g. `PRIMITIVE_WEBHOOK_SECRET`,\n`PRIMITIVE_API_KEY`, `PRIMITIVE_API_BASE_URL`) carry a\n`description` instead of `created_at` / `updated_at`. They\ncannot be created, updated, or deleted via this API.\n","tags":["Functions"],"responses":{"200":{"description":"List of secrets (metadata only, no values)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/FunctionSecretListItem"}}},"required":["items"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"post":{"operationId":"createFunctionSecret","summary":"Create or update a secret","description":"Idempotent insert-or-update keyed on `(function_id, key)`.\nReturns 201 the first time the key is set, 200 on subsequent\nupdates. Values are encrypted at rest and only become visible\nto the running handler on the next deploy (`PUT /functions/{id}`\nwith the existing code is sufficient to refresh bindings).\n\nKeys must match `^[A-Z_][A-Z0-9_]*$` (uppercase letters,\ndigits, underscores; first character is a letter or\nunderscore). Values are at most 4096 UTF-8 bytes. System-\nmanaged keys are reserved and rejected.\n","tags":["Functions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFunctionSecretInput"}}}},"responses":{"200":{"description":"Secret updated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FunctionSecretWriteResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"201":{"description":"Secret created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FunctionSecretWriteResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/functions/{id}/secrets/{key}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"key","in":"path","required":true,"description":"Secret key. Must match `^[A-Z_][A-Z0-9_]*$`.","schema":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"}}],"put":{"operationId":"setFunctionSecret","summary":"Set a secret by key","description":"Path-keyed companion to `POST /functions/{id}/secrets`.\nIdempotent: returns 201 the first time the key is set, 200 on\nsubsequent updates. Same validation rules and same write-only\nguarantees as the POST verb; the new value lands in the running\nhandler on the next deploy.\n","tags":["Functions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetFunctionSecretInput"}}}},"responses":{"200":{"description":"Secret updated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FunctionSecretWriteResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"201":{"description":"Secret created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FunctionSecretWriteResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteFunctionSecret","summary":"Delete a secret","description":"Removes the secret. The binding stays live in the running\nhandler until the next deploy refreshes the binding set\n(`PUT /functions/{id}` with the existing code is sufficient).\nReturns 404 if the key did not exist. Managed system keys\ncannot be deleted.\n","tags":["Functions"],"responses":{"204":{"description":"Secret deleted"},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/org/secrets":{"get":{"operationId":"listOrgSecrets","summary":"List org-level (global) secrets","description":"Returns metadata for every org-level secret. Org secrets apply\nto every function in the org and are read as `env.<KEY>` in\nhandlers. **Values are never returned.** Secret writes are\nwrite-only. A function-level secret of the same name overrides\nthe org-level value for that function.\n","tags":["Functions"],"responses":{"200":{"description":"List of org secrets (metadata only, no values)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OrgSecretListItem"}}},"required":["items"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]},"post":{"operationId":"createOrgSecret","summary":"Create or update an org secret","description":"Idempotent insert-or-update keyed on `(org_id, key)`. Returns\n201 the first time the key is set, 200 on subsequent updates.\nValues are encrypted at rest. A changed value lands in a\nfunction only on that function's next deploy.\n\nKeys must match `^[A-Z_][A-Z0-9_]*$` (uppercase letters,\ndigits, underscores; first character is a letter or\nunderscore). Values are at most 4096 UTF-8 bytes. System-\nmanaged keys are reserved and rejected.\n","tags":["Functions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrgSecretInput"}}}},"responses":{"200":{"description":"Secret updated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/OrgSecretWriteResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"201":{"description":"Secret created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/OrgSecretWriteResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/org/secrets/{key}":{"parameters":[{"name":"key","in":"path","required":true,"description":"Secret key. Must match `^[A-Z_][A-Z0-9_]*$`.","schema":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"}}],"put":{"operationId":"setOrgSecret","summary":"Set an org secret by key","description":"Path-keyed companion to `POST /org/secrets`. Idempotent:\nreturns 201 the first time the key is set, 200 on subsequent\nupdates. Same validation and write-only guarantees as POST.\n","tags":["Functions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetOrgSecretInput"}}}},"responses":{"200":{"description":"Secret updated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/OrgSecretWriteResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"201":{"description":"Secret created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/OrgSecretWriteResult"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}]},"delete":{"operationId":"deleteOrgSecret","summary":"Delete an org secret","description":"Removes the org secret. Functions keep the previous value until\neach is redeployed. Returns 404 if the key did not exist.\n","tags":["Functions"],"responses":{"204":{"description":"Secret deleted"},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/functions/{id}/logs":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"listFunctionLogs","summary":"List a function's execution logs","description":"Returns the most recent `function_logs` rows for the function,\nnewest first. Each row is a single `console.log` / `console.error`\ninvocation captured from the running handler.\n\nPage through history with the opaque `cursor` returned as\n`next_cursor`; pass it back as the `cursor` query param on the\nnext call. `next_cursor` is `null` when there are no further\nrows. The cursor format is an implementation detail and should\nnot be parsed by callers.\n","tags":["Functions"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum number of rows to return. Clamped to 1..200; default\n50.\n","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","required":false,"description":"Opaque pagination cursor from a previous response's\n`next_cursor`. Omit on the first call.\n","schema":{"type":"string"}}],"responses":{"200":{"description":"List of log rows (newest first) plus pagination cursor.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/FunctionLogRow"}},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to fetch the next\npage. `null` when no further rows exist.\n"}},"required":["items","next_cursor"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/memories":{"put":{"operationId":"setMemory","summary":"Set a memory","description":"Create or update a durable JSON memory under an org or function scope. Function-authenticated requests use their own Function id and cannot override it. If no explicit scope is provided for other credentials, `x-primitive-function-id` is used next, and other requests default to org scope. Function scope uses the function id UUID, not the function name.","tags":["Memories"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"x-primitive-function-id","in":"header","required":false,"description":"Optional function id UUID used as the default scope for non-function-authenticated requests when the body does not include `scope`. Ignored when `scope` is provided.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetMemoryInput"}}}},"responses":{"200":{"description":"Existing memory updated.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MemoryRecordWithValue"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"201":{"description":"Memory created.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MemoryRecordWithValue"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"402":{"description":"Usage credits are exhausted or payment is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"get":{"operationId":"getMemory","summary":"Get a memory","description":"Fetch one active memory by key and scope. Omit scope parameters to use automatic scope resolution. Function-authenticated requests use their own Function id and cannot override it. Function scope uses a function id UUID in `scope_id`.","tags":["Memories"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"key","in":"query","required":true,"description":"Memory key. Must be at most 512 UTF-8 bytes.","schema":{"type":"string","minLength":1,"maxLength":512}},{"name":"scope_type","in":"query","required":false,"description":"Explicit scope type. Omit to use automatic scope resolution. Pass `function` with `scope_id=<function-id>`, or `org` with no `scope_id`. Function-authenticated requests cannot override their automatic Function scope.","schema":{"type":"string","enum":["org","function"]}},{"name":"scope_id","in":"query","required":false,"description":"Function id UUID when `scope_type=function`. Not valid with `scope_type=org`. Function-authenticated requests may only use their own Function id.","schema":{"type":"string","format":"uuid"}},{"name":"x-primitive-function-id","in":"header","required":false,"description":"Optional function id UUID used as the default scope for non-function-authenticated requests when query scope parameters are omitted.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Memory record with value.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MemoryRecordWithValue"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"402":{"description":"Usage credits are exhausted or payment is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"delete":{"operationId":"deleteMemory","summary":"Delete a memory","description":"Delete one active memory by key and scope. Function-authenticated requests use their own Function id and cannot override it. Deletes are idempotent without `if_version`; a stale `if_version` returns `memory_conflict`.","tags":["Memories"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"key","in":"query","required":true,"description":"Memory key. Must be at most 512 UTF-8 bytes.","schema":{"type":"string","minLength":1,"maxLength":512}},{"name":"scope_type","in":"query","required":false,"description":"Explicit scope type. Omit to use automatic scope resolution. Pass `function` with `scope_id=<function-id>`, or `org` with no `scope_id`. Function-authenticated requests cannot override their automatic Function scope.","schema":{"type":"string","enum":["org","function"]}},{"name":"scope_id","in":"query","required":false,"description":"Function id UUID when `scope_type=function`. Not valid with `scope_type=org`. Function-authenticated requests may only use their own Function id.","schema":{"type":"string","format":"uuid"}},{"name":"if_version","in":"query","required":false,"description":"Optional compare-and-delete version.","schema":{"$ref":"#/components/schemas/NumericString"}},{"name":"x-primitive-function-id","in":"header","required":false,"description":"Optional function id UUID used as the default scope for non-function-authenticated requests when query scope parameters are omitted.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Delete result.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DeleteMemoryResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"402":{"description":"Usage credits are exhausted or payment is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/memories/search":{"get":{"operationId":"searchMemories","summary":"Search memories","description":"List active memories in a scope by lexicographic key prefix. Results are ordered by key ascending. This is prefix search, not free-text search. Function-authenticated requests use their own Function id and cannot override it. Pass `include_value=false` to return metadata only.","tags":["Memories"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"prefix","in":"query","required":false,"description":"Key prefix to match. Empty string lists all active memories in the selected scope.","schema":{"type":"string","maxLength":512,"default":""}},{"name":"cursor","in":"query","required":false,"description":"Key cursor from a previous response meta.cursor.","schema":{"type":"string","minLength":1,"maxLength":512}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Number of results per page"},{"name":"include_value","in":"query","required":false,"description":"Pass `false` to omit the `value` field and return metadata only.","schema":{"type":"string","enum":["true","false"],"default":"true"}},{"name":"updated_after","in":"query","required":false,"description":"Only include memories updated at or after this timestamp.","schema":{"type":"string","format":"date-time"}},{"name":"updated_before","in":"query","required":false,"description":"Only include memories updated at or before this timestamp.","schema":{"type":"string","format":"date-time"}},{"name":"scope_type","in":"query","required":false,"description":"Explicit scope type. Omit to use automatic scope resolution. Pass `function` with `scope_id=<function-id>`, or `org` with no `scope_id`. Function-authenticated requests cannot override their automatic Function scope.","schema":{"type":"string","enum":["org","function"]}},{"name":"scope_id","in":"query","required":false,"description":"Function id UUID when `scope_type=function`. Not valid with `scope_type=org`. Function-authenticated requests may only use their own Function id.","schema":{"type":"string","format":"uuid"}},{"name":"x-primitive-function-id","in":"header","required":false,"description":"Optional function id UUID used as the default scope for non-function-authenticated requests when query scope parameters are omitted.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Paginated memory records.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MemoryRecord"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"402":{"description":"Usage credits are exhausted or payment is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/templates":{"get":{"operationId":"listTemplates","summary":"List function templates","description":"List approved Function templates available for browsing and installation. Results are cacheable and paginated with `data.next_cursor`.","tags":["Templates"],"security":[],"parameters":[{"name":"q","in":"query","required":false,"description":"Search templates by title, summary, or slug.","schema":{"type":"string","minLength":1}},{"name":"tag","in":"query","required":false,"description":"Filter templates by an exact tag.","schema":{"type":"string","minLength":1}},{"name":"cursor","in":"query","required":false,"description":"Cursor from a previous response `data.next_cursor` value.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of templates to return. The server caps this at 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Paginated template registry summaries.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TemplateRegistryPage"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/templates/{id}":{"get":{"operationId":"getTemplate","summary":"Get a function template","description":"Fetch one approved Function template by slug, including its manifest snapshot and README. The stored source files used for install are not returned.","tags":["Templates"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"description":"Template slug from the template manifest.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$","description":"Template slug."}}],"responses":{"200":{"description":"Template registry detail.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TemplateRegistryDetail"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/templates/{id}/install":{"post":{"operationId":"installTemplate","summary":"Install a function template","description":"Start a one-shot deploy of an approved deploy-mode Function template. The response returns an install record immediately; poll `GET /templates/installs/{id}` for progress.","tags":["Templates"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Template slug from the template manifest.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$","description":"Template slug."}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstallTemplateBody"}}}},"responses":{"201":{"description":"Template install started.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TemplateInstallStatus"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"422":{"description":"The request was well-formed but could not be processed. For Payments\nthis covers a missing payout address, a failed payment verification, a\nspend-policy decline, or an expired challenge; `error.code` distinguishes\nthem.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"payment_declined","message":"payment exceeds the per-payment cap"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/templates/installs/{id}":{"get":{"operationId":"getTemplateInstall","summary":"Get template install status","description":"Fetch the current state of a template install. Reads may advance the self-test phase when a reply effect has been observed.","tags":["Templates"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Template install UUID.","schema":{"type":"string","format":"uuid","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Current template install status.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TemplateInstallStatus"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/x402/payout-addresses":{"post":{"operationId":"registerPayoutAddress","summary":"Register a payout address","description":"Register (or update) the default payout address your org receives x402\npayments at, for a given network. You prove control of the address with\nan org-bound `personal_sign` signature over the message produced by the\nSDK helper `buildPayoutRegistrationMessage`. The org id is taken from your\nauthenticated key, never the body, so a captured signature can't register\nan address under another org. Exactly one default address exists per\n(org, network); registering again replaces it. A payee MUST register a\npayout address before calling `createChallenge`, because the challenge's\n`pay_to` is resolved from this directory.\n","tags":["Payments"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterPayoutAddressInput"}}}},"responses":{"201":{"description":"Payout address registered (or updated) and set as default","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/X402PayoutAddress"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"422":{"description":"The request was well-formed but could not be processed. For Payments\nthis covers a missing payout address, a failed payment verification, a\nspend-policy decline, or an expired challenge; `error.code` distinguishes\nthem.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"payment_declined","message":"payment exceeds the per-payment cap"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]},"get":{"operationId":"listPayoutAddresses","summary":"List payout addresses","description":"List your org's registered payout addresses, newest first.","tags":["Payments"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Your registered payout addresses","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/X402PayoutAddress"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/x402/email-challenges":{"post":{"operationId":"createEmailChallenge","summary":"Create an email-native payment challenge","description":"Issue an x402 payment challenge over a real email thread (the payee\nside). Unlike `createChallenge` (which mints a synthetic challenge id),\nthis sends the challenge as an email from `from` to `to` and binds the\npayment to that DKIM-authenticated thread. The `pay_to` address and the\ntoken asset are resolved server-side from your registered default payout\naddress for the network, never from the request. The response carries\nthe thread's `interaction_id` plus the `challenge` (the\n`payment_requirements`, the `nonce_binding`, and `expires_at`) the payer\nneeds to sign; the payer replies with a signed `payment` interaction\nstep. Amounts are in token base units (USDC has 6 decimals, so `\"10000\"`\nis 0.01 USDC).\n","tags":["Payments"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEmailChallengeInput"}}}},"responses":{"200":{"description":"Idempotent replay: a request with a previously-used idempotency key\nreturns the original issued challenge without sending a second email.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/X402EmailChallenge"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"201":{"description":"Email challenge issued","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/X402EmailChallenge"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"422":{"description":"The request was well-formed but could not be processed. For Payments\nthis covers a missing payout address, a failed payment verification, a\nspend-policy decline, or an expired challenge; `error.code` distinguishes\nthem.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"payment_declined","message":"payment exceeds the per-payment cap"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/x402/challenges":{"post":{"operationId":"createChallenge","summary":"Create a payment challenge","description":"Create an x402 payment challenge (the payee side of a payment). The\n`pay_to` address is resolved server-side from your registered default\npayout address for the network, never from the request. The response\ncarries the `nonce_binding` and `payment_requirements` the payer needs to\nsign; hand the whole challenge object to the payer (for example in an\nemail reply). Amounts are in token base units (USDC has 6 decimals, so\n`\"10000\"` is 0.01 USDC).\n","tags":["Payments"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateChallengeInput"}}}},"responses":{"201":{"description":"Challenge created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/X402Challenge"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"422":{"description":"The request was well-formed but could not be processed. For Payments\nthis covers a missing payout address, a failed payment verification, a\nspend-policy decline, or an expired challenge; `error.code` distinguishes\nthem.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"payment_declined","message":"payment exceeds the per-payment cap"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/x402/challenges/{id}":{"get":{"operationId":"getChallenge","summary":"Get a payment challenge","description":"Fetch a challenge you created, to poll its `status` and settlement\nreceipt (`settle_tx`). Scoped to the challenger org that created it.\n","tags":["Payments"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"responses":{"200":{"description":"The challenge","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/X402Challenge"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/x402/challenges/{id}/pay":{"post":{"operationId":"payChallenge","summary":"Pay a payment challenge","description":"Settle a challenge addressed to your org as payer. The request body\ncarries a signed x402 `PaymentPayload`: an EIP-3009\n`transferWithAuthorization` signed locally with your own key, whose nonce\nis bound to the challenge via the SDK's `deriveEip3009Nonce`. The platform\nverifies every signed field against its own record of the challenge,\napplies your spend policy, and settles on-chain through a facilitator.\nSettlement is non-custodial; Primitive never holds funds. Idempotent:\npaying an already-settled challenge returns the original receipt. Most\ncallers use the SDK `pay()` helper rather than building the payload by\nhand.\n","tags":["Payments"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayChallengeInput"}}}},"responses":{"200":{"description":"Challenge settled (or already settled)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/X402Receipt"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"422":{"description":"The request was well-formed but could not be processed. For Payments\nthis covers a missing payout address, a failed payment verification, a\nspend-policy decline, or an expired challenge; `error.code` distinguishes\nthem.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"payment_declined","message":"payment exceeds the per-payment cap"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"502":{"description":"Primitive could not complete the downstream SMTP request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_unreachable","message":"Outbound SMTP service request failed"}}}}}}}},"/x402/spend-policy":{"get":{"operationId":"getSpendPolicy","summary":"Get your spend policy","description":"Read your org's outbound spend policy: the kill-switch, per-payment and\nper-day caps, and the payee allowlist. Returns the defaults (no limits,\nnot paused) when no policy has been set.\n","tags":["Payments"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"The spend policy","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/X402SpendPolicy"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"put":{"operationId":"updateSpendPolicy","summary":"Update your spend policy","description":"Update your org's spend policy. Applied as a merge: only the fields you\ninclude change, and omitted fields keep their current value, so a partial\nupdate can't silently reset the kill-switch. Send an explicit `null` to\nclear a cap. Caps are in token base units.\n","tags":["Payments"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSpendPolicyInput"}}}},"responses":{"200":{"description":"The updated spend policy","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/X402SpendPolicy"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/x402/declined-payments":{"get":{"operationId":"listDeclinedPayments","summary":"List declined payments","description":"The 50 most recent payments your org's spend policy declined, newest\nfirst. Use this to see why an outbound payment was refused (a cap, the\npayee allowlist, or the kill-switch) instead of only reading the\ndashboard.\n","tags":["Payments"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Recently declined payments","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/X402DeclinedPayment"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-networks":{"get":{"operationId":"listAgentNetworks","tags":["Agent Networks"],"summary":"list Agent Networks","description":"List the private default organization network. Human organization members may read it. can_manage_all is true only for a current owner or admin; connected credentials require can_view for their exact address and receive false.","security":[{"BearerAuth":[]}],"parameters":[],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Network permissions and peer activity must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"kind":{"type":"string","const":"organization"},"is_default":{"type":"boolean","const":true},"name":{"type":"string"},"can_manage_all":{"type":"boolean"}},"required":["id","kind","is_default","name","can_manage_all"],"additionalProperties":false}}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-networks/default/members":{"get":{"operationId":"listDefaultNetworkMembers","tags":["Agent Networks"],"summary":"list Default Network Members","description":"Current owner or admin sees all connected addresses. A human member sees only personal addresses owned by their current membership, filtered before pagination. Rows include can_manage, current human owner identity, independent can_view and is_listed controls, durable exclusions, and last recorded API activity. Excluded rows are retained. Optional owner search matches user ID, display name or email before pagination within the authorized scope.","security":[{"BearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"limit for agent network discovery or management.","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"cursor for agent network discovery or management.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"owner","in":"query","required":false,"description":"owner for agent network discovery or management.","schema":{"type":"string","minLength":1,"maxLength":100}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Network permissions and peer activity must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":{"type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"},"can_view":{"type":"boolean"},"is_listed":{"type":"boolean"},"excluded":{"type":"boolean"},"connected":{"type":"boolean"},"can_manage":{"type":"boolean"},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"presence":{"anyOf":[{"type":"object","properties":{"last_checked_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"valid_for_ms":{"type":"integer","minimum":0,"maximum":600000}},"required":["last_checked_at","expires_at","valid_for_ms"],"additionalProperties":false},{"type":"null"}]},"ownership_kind":{"type":"string","enum":["personal","shared","legacy_unknown"]},"owner":{"anyOf":[{"type":"object","properties":{"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]}},"required":["user_id","name","email"],"additionalProperties":false},{"type":"null"}]}},"required":["address","name","can_view","is_listed","excluded","connected","can_manage","last_seen_at","ownership_kind","owner"],"additionalProperties":false}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-networks/default/members/{address}":{"parameters":[{"name":"address","in":"path","required":true,"description":"The agent's email address, URL-encoded in the path.","schema":{"type":"string","minLength":3,"maxLength":254}}],"patch":{"operationId":"updateDefaultNetworkMember","tags":["Agent Networks"],"summary":"update Default Network Member","description":"Current owner or admin may update any connected address. A human member may update only their own current, non-excluded personal address. Re-enabling a permission advances its activation time.","security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"address for agent network discovery or management.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Network permissions and peer activity must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"},"can_view":{"type":"boolean"},"is_listed":{"type":"boolean"},"excluded":{"type":"boolean"},"connected":{"type":"boolean"},"can_manage":{"type":"boolean"},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"presence":{"anyOf":[{"type":"object","properties":{"last_checked_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"valid_for_ms":{"type":"integer","minimum":0,"maximum":600000}},"required":["last_checked_at","expires_at","valid_for_ms"],"additionalProperties":false},{"type":"null"}]},"ownership_kind":{"type":"string","enum":["personal","shared","legacy_unknown"]},"owner":{"anyOf":[{"type":"object","properties":{"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]}},"required":["user_id","name","email"],"additionalProperties":false},{"type":"null"}]}},"required":["address","name","can_view","is_listed","excluded","connected","can_manage","last_seen_at","ownership_kind","owner"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"can_view":{"type":"boolean"},"is_listed":{"type":"boolean"}},"additionalProperties":false}}}}},"post":{"operationId":"addDefaultNetworkMember","tags":["Agent Networks"],"summary":"add Default Network Member","description":"Owner or admin restoration of a durably excluded connected address.","security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"address for agent network discovery or management.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Network permissions and peer activity must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"},"can_view":{"type":"boolean"},"is_listed":{"type":"boolean"},"excluded":{"type":"boolean"},"connected":{"type":"boolean"},"can_manage":{"type":"boolean"},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"presence":{"anyOf":[{"type":"object","properties":{"last_checked_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"valid_for_ms":{"type":"integer","minimum":0,"maximum":600000}},"required":["last_checked_at","expires_at","valid_for_ms"],"additionalProperties":false},{"type":"null"}]},"ownership_kind":{"type":"string","enum":["personal","shared","legacy_unknown"]},"owner":{"anyOf":[{"type":"object","properties":{"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]}},"required":["user_id","name","email"],"additionalProperties":false},{"type":"null"}]}},"required":["address","name","can_view","is_listed","excluded","connected","can_manage","last_seen_at","ownership_kind","owner"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"delete":{"operationId":"removeDefaultNetworkMember","tags":["Agent Networks"],"summary":"remove Default Network Member","description":"Owner or admin exclusion of a connected address. The exclusion survives disconnection and later reconnection at the same address.","security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"address for agent network discovery or management.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Network permissions and peer activity must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"excluded":{"type":"boolean","const":true}},"required":["excluded"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-networks/default/agents":{"get":{"operationId":"listDefaultNetworkAgents","tags":["Agent Networks"],"summary":"list Default Network Agents","description":"List connected and listed peers in address order. Human organization members may read this list. Connected credentials require can_view and do not see themselves. Personal peers expose only a stable owner ID and display name to connected credentials, never human email. Optional owner search matches ID or display name before pagination. last_seen_at is recorded API activity, not presence. receiver reports whether the peer is reading its mail now, from its own recent successful mail reads.","security":[{"BearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"limit for agent network discovery or management.","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"cursor for agent network discovery or management.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"owner","in":"query","required":false,"description":"owner for agent network discovery or management.","schema":{"type":"string","minLength":1,"maxLength":100}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Network permissions and peer activity must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":{"type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"presence":{"anyOf":[{"type":"object","properties":{"last_checked_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"valid_for_ms":{"type":"integer","minimum":0,"maximum":600000}},"required":["last_checked_at","expires_at","valid_for_ms"],"additionalProperties":false},{"type":"null"}]},"receiver":{"type":"object","properties":{"state":{"type":"string","enum":["live","down","unknown"]},"since":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"last_mail_check_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]}},"required":["state","since","last_mail_check_at"],"additionalProperties":false,"description":"Whether this peer is currently reading its mail, from the server's record of its own successful mail reads (GET /emails list reads and pull endpoint pulls made with its connection key). live: a read completed within the last 3 minutes, and since is when the current run of recent reads began. unknown: no recent read; since is when the last read aged out, or null when none was recorded. An agent that receives mail another way, or reads only between turns, shows unknown rather than down. down is reserved for an explicit report that the receiver stopped. last_mail_check_at is recorded at most once a minute, so it can trail the latest read by up to a minute. None of this says whether a model has read a particular message."},"ownership_kind":{"type":"string","enum":["personal","shared","legacy_unknown"]},"owner":{"anyOf":[{"type":"object","properties":{"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["user_id","name"],"additionalProperties":false},{"type":"null"}]}},"required":["address","name","last_seen_at","receiver","ownership_kind","owner"],"additionalProperties":false}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-networks/default/agents/{address}":{"parameters":[{"name":"address","in":"path","required":true,"description":"The agent's email address, URL-encoded in the path.","schema":{"type":"string","minLength":3,"maxLength":254}}],"get":{"operationId":"getDefaultNetworkAgent","tags":["Agent Networks"],"summary":"get Default Network Agent","description":"Read one listed peer in the same organization. An unlisted or excluded address returns 404 in this network lookup.","security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"address for agent network discovery or management.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Network permissions and peer activity must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"presence":{"anyOf":[{"type":"object","properties":{"last_checked_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"valid_for_ms":{"type":"integer","minimum":0,"maximum":600000}},"required":["last_checked_at","expires_at","valid_for_ms"],"additionalProperties":false},{"type":"null"}]},"receiver":{"type":"object","properties":{"state":{"type":"string","enum":["live","down","unknown"]},"since":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"last_mail_check_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]}},"required":["state","since","last_mail_check_at"],"additionalProperties":false,"description":"Whether this peer is currently reading its mail, from the server's record of its own successful mail reads (GET /emails list reads and pull endpoint pulls made with its connection key). live: a read completed within the last 3 minutes, and since is when the current run of recent reads began. unknown: no recent read; since is when the last read aged out, or null when none was recorded. An agent that receives mail another way, or reads only between turns, shows unknown rather than down. down is reserved for an explicit report that the receiver stopped. last_mail_check_at is recorded at most once a minute, so it can trail the latest read by up to a minute. None of this says whether a model has read a particular message."},"ownership_kind":{"type":"string","enum":["personal","shared","legacy_unknown"]},"owner":{"anyOf":[{"type":"object","properties":{"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["user_id","name"],"additionalProperties":false},{"type":"null"}]}},"required":["address","name","last_seen_at","receiver","ownership_kind","owner"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-networks/default/contact-admission":{"post":{"operationId":"checkDefaultNetworkContactAdmission","tags":["Agent Networks"],"summary":"check Default Network Contact Admission","description":"Check whether one stored inbound email to this exact connected recipient may wake it as network peer mail. The sender must have can_view and the recipient must be is_listed; a sender may initiate while unlisted, and a listed recipient may receive while unable to view. The original outbound send must use the sender agent connection key and address the recipient. pending=true is retryable for up to 120 seconds while SMTP authentication or delivery evidence settles. Explicit contact-policy silence still overrides admission. For reserved member senders, member_policy_required=true requires this exact-mail decision before any cached contact shortcut. Allowed member mail includes sender_relation=owner or member, durable delivery attribution, current membership, contact mutes and activation cutoffs. Missing exact stored mail returns 404. Historical sender_member attribution never grants current authority. This grants communication, never task authority.","security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Network permissions and peer activity must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"member_policy_required":{"type":"boolean"},"sender_relation":{"type":"string","enum":["owner","member"]},"allowed":{"type":"boolean"},"allowed_since":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"pending":{"type":"boolean"}},"required":["member_policy_required","allowed","allowed_since","pending"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"email_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"sender_address":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["email_id","sender_address"],"additionalProperties":false}}}}}},"/registries":{"get":{"operationId":"listRegistries","summary":"List the registries you own","tags":["Registries"],"responses":{"200":{"description":"Registries owned by the caller","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Registry"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}}},"security":[{"BearerAuth":[]}],"description":"List the registries you own"},"post":{"operationId":"createRegistry","summary":"Create a registry","tags":["Registries"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRegistryInput"}}}},"responses":{"201":{"description":"Registry created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"}},"required":["id","slug"]}}}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"422":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"description":"Create a registry"}},"/registries/{slug}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The registry slug"}],"get":{"operationId":"getRegistry","summary":"Get a public registry's metadata","tags":["Registries"],"security":[],"responses":{"200":{"description":"Registry metadata","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"$ref":"#/components/schemas/Registry"}}}]}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"description":"Get a public registry's metadata"},"patch":{"operationId":"updateRegistry","summary":"Update a registry you own","tags":["Registries"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRegistryInput"}}}},"responses":{"200":{"description":"Registry updated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}},"required":["id"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"422":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}}},"security":[{"BearerAuth":[]}],"description":"Update a registry you own"},"delete":{"operationId":"deleteRegistry","summary":"Delete a registry you own","description":"Removes the registry from discovery and frees its slug for re-creation.","tags":["Registries"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}]}},"/registries/{slug}/agents":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The registry slug."}],"get":{"operationId":"listRegistryAgents","summary":"List agents in a registry","tags":["Registries"],"security":[],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200},"description":"Maximum number of items to return (1-200)."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"The address of the last agent from the previous page."}],"responses":{"200":{"description":"Approved, reachable agents. Empty for an unknown or private registry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RegistryAgent"}}}}]}}}}},"description":"List agents in a registry"},"post":{"operationId":"publishAgent","summary":"Publish an agent into a registry","tags":["Registries"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishAgentInput"}}}},"responses":{"200":{"description":"Idempotent replay of an existing publication","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"$ref":"#/components/schemas/PublishAgentResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"201":{"description":"Published (approved) or request created (requested)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"$ref":"#/components/schemas/PublishAgentResult"}}}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"422":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"description":"Publish an agent into a registry"}},"/registries/{slug}/agents/{handle}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The registry slug."},{"name":"handle","in":"path","required":true,"schema":{"type":"string"},"description":"The registry-scoped handle the agent is published under."}],"get":{"operationId":"resolveRegistryHandle","summary":"Resolve a registry handle to its agent","tags":["Registries"],"security":[],"responses":{"200":{"description":"The agent listing under the handle","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"$ref":"#/components/schemas/RegistryAgent"}}}]}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"description":"Resolve a registry handle to its agent"},"delete":{"operationId":"unpublishAgent","summary":"Unpublish an agent from a registry","tags":["Registries"],"responses":{"200":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"Unpublish an agent from a registry"}},"/registries/{slug}/requests":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The registry slug."}],"get":{"operationId":"listRegistryRequests","summary":"List pending publication requests","tags":["Registries"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200},"description":"Maximum number of items to return (1-200)."}],"responses":{"200":{"description":"Pending requests for a registry you own","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RegistryRequest"}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"security":[{"BearerAuth":[]}],"description":"List pending publication requests"}},"/registries/{slug}/requests/{id}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The registry slug."},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"decideRegistryRequest","summary":"Approve or reject a publication request","tags":["Registries"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecideRegistryRequestInput"}}}},"responses":{"200":{"description":"Decision applied","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"type":"object","properties":{"status":{"type":"string","enum":["approved","rejected"]}},"required":["status"]}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"422":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"description":"Approve or reject a publication request"}},"/agents":{"post":{"operationId":"defineAgent","summary":"Define an agent identity","tags":["Registries"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DefineAgentInput"}}}},"responses":{"201":{"description":"Agent defined","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"address":{"type":"string"}},"required":["id","address"]}}}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"422":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}}},"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"description":"Define an agent identity"}},"/agents/{address}":{"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"},"description":"The agent's email address (URL-encoded)."}],"get":{"operationId":"getAgent","summary":"Get an agent's public profile by address","tags":["Registries"],"security":[],"responses":{"200":{"description":"The agent's public profile","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"$ref":"#/components/schemas/RegistryAgent"}}}]}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}}},"description":"Get an agent's public profile by address"}},"/contacts":{"get":{"operationId":"listContacts","summary":"list Contacts","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Organization directory and agent preferences; no profiles, message history or runtime presence. Organization credentials retain tenant directory permissions. Human organization members may read and change agent notification contacts only for personal connections bound to their current membership incarnation; owners and admins may manage all. Connected keys may read the directory, create address-only missing contacts using if_absent:true, and access only their own agent memberships; they cannot edit shared labels or delete directory entries. Function credentials are not granted access. Memberships require an existing local agent connection (including revoked connections) and an existing contact. PUT requires exactly one precondition. if_absent returns an existing row unchanged when supplied fields agree; otherwise 409. notify defaults false. Off-to-on generates a new server timestamp and generation; on-to-on and purpose-only edits preserve them. Disable clears activation metadata. Deletion atomically removes membership eligibility. Receivers must recheck generation/preferences before dispatch and pause admission when their policy cache is older than 30 seconds; explicit reply waits are independent. DELETE requires if_version, returns deleted:false for an absent row, and rejects stale versions of recreated rows. Pages use ascending canonical address, with meta.cursor null on the last page. Lists are live pages, not snapshots.","parameters":[{"name":"cursor","in":"query","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":false,"description":"cursor for contacts."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"required":false,"description":"limit for contacts."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Always fresh policy metadata."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: write precondition did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/contacts/{address}":{"get":{"operationId":"getContact","summary":"get Contact","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Organization directory and agent preferences; no profiles, message history or runtime presence. Organization credentials retain tenant directory permissions. Human organization members may read and change agent notification contacts only for personal connections bound to their current membership incarnation; owners and admins may manage all. Connected keys may read the directory, create address-only missing contacts using if_absent:true, and access only their own agent memberships; they cannot edit shared labels or delete directory entries. Function credentials are not granted access. Memberships require an existing local agent connection (including revoked connections) and an existing contact. PUT requires exactly one precondition. if_absent returns an existing row unchanged when supplied fields agree; otherwise 409. notify defaults false. Off-to-on generates a new server timestamp and generation; on-to-on and purpose-only edits preserve them. Disable clears activation metadata. Deletion atomically removes membership eligibility. Receivers must recheck generation/preferences before dispatch and pause admission when their policy cache is older than 30 seconds; explicit reply waits are independent. DELETE requires if_version, returns deleted:false for an absent row, and rejects stale versions of recreated rows. Pages use ascending canonical address, with meta.cursor null on the last page. Lists are live pages, not snapshots.","parameters":[{"name":"address","in":"path","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":true,"description":"address for contacts."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Always fresh policy metadata."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: write precondition did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"put":{"operationId":"putContact","summary":"put Contact","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Organization directory and agent preferences; no profiles, message history or runtime presence. Organization credentials retain tenant directory permissions. Human organization members may read and change agent notification contacts only for personal connections bound to their current membership incarnation; owners and admins may manage all. Connected keys may read the directory, create address-only missing contacts using if_absent:true, and access only their own agent memberships; they cannot edit shared labels or delete directory entries. Function credentials are not granted access. Memberships require an existing local agent connection (including revoked connections) and an existing contact. PUT requires exactly one precondition. if_absent returns an existing row unchanged when supplied fields agree; otherwise 409. notify defaults false. Off-to-on generates a new server timestamp and generation; on-to-on and purpose-only edits preserve them. Disable clears activation metadata. Deletion atomically removes membership eligibility. Receivers must recheck generation/preferences before dispatch and pause admission when their policy cache is older than 30 seconds; explicit reply waits are independent. DELETE requires if_version, returns deleted:false for an absent row, and rejects stale versions of recreated rows. Pages use ascending canonical address, with meta.cursor null on the last page. Lists are live pages, not snapshots.","parameters":[{"name":"address","in":"path","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":true,"description":"address for contacts."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutContactRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Always fresh policy metadata."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: write precondition did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"delete":{"operationId":"deleteContact","summary":"delete Contact","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Organization directory and agent preferences; no profiles, message history or runtime presence. Organization credentials retain tenant directory permissions. Human organization members may read and change agent notification contacts only for personal connections bound to their current membership incarnation; owners and admins may manage all. Connected keys may read the directory, create address-only missing contacts using if_absent:true, and access only their own agent memberships; they cannot edit shared labels or delete directory entries. Function credentials are not granted access. Memberships require an existing local agent connection (including revoked connections) and an existing contact. PUT requires exactly one precondition. if_absent returns an existing row unchanged when supplied fields agree; otherwise 409. notify defaults false. Off-to-on generates a new server timestamp and generation; on-to-on and purpose-only edits preserve them. Disable clears activation metadata. Deletion atomically removes membership eligibility. Receivers must recheck generation/preferences before dispatch and pause admission when their policy cache is older than 30 seconds; explicit reply waits are independent. DELETE requires if_version, returns deleted:false for an absent row, and rejects stale versions of recreated rows. Pages use ascending canonical address, with meta.cursor null on the last page. Lists are live pages, not snapshots.","parameters":[{"name":"address","in":"path","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":true,"description":"address for contacts."},{"name":"if_version","in":"query","schema":{"type":"string","format":"uuid","description":"Opaque CAS token. Changes on mutations and cannot be reused after deletion/recreation."},"required":true,"description":"if_version for contacts."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","additionalProperties":false,"properties":{"deleted":{"type":"boolean"}},"required":["deleted"]}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Always fresh policy metadata."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: write precondition did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-contacts/{agent_address}":{"get":{"operationId":"listAgentContacts","summary":"list Agent Contacts","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Organization directory and agent preferences; no profiles, message history or runtime presence. Organization credentials retain tenant directory permissions. Human organization members may read and change agent notification contacts only for personal connections bound to their current membership incarnation; owners and admins may manage all. Connected keys may read the directory, create address-only missing contacts using if_absent:true, and access only their own agent memberships; they cannot edit shared labels or delete directory entries. Function credentials are not granted access. Memberships require an existing local agent connection (including revoked connections) and an existing contact. PUT requires exactly one precondition. if_absent returns an existing row unchanged when supplied fields agree; otherwise 409. notify defaults false. Off-to-on generates a new server timestamp and generation; on-to-on and purpose-only edits preserve them. Disable clears activation metadata. Deletion atomically removes membership eligibility. Receivers must recheck generation/preferences before dispatch and pause admission when their policy cache is older than 30 seconds; explicit reply waits are independent. DELETE requires if_version, returns deleted:false for an absent row, and rejects stale versions of recreated rows. Pages use ascending canonical address, with meta.cursor null on the last page. Lists are live pages, not snapshots.","parameters":[{"name":"agent_address","in":"path","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":true,"description":"agent_address for contacts."},{"name":"cursor","in":"query","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":false,"description":"cursor for contacts."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"required":false,"description":"limit for contacts."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AgentContact"}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Always fresh policy metadata."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: write precondition did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-contacts/{agent_address}/{contact_address}":{"put":{"operationId":"putAgentContact","summary":"put Agent Contact","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Organization directory and agent preferences; no profiles, message history or runtime presence. Organization credentials retain tenant directory permissions. Human organization members may read and change agent notification contacts only for personal connections bound to their current membership incarnation; owners and admins may manage all. Connected keys may read the directory, create address-only missing contacts using if_absent:true, and access only their own agent memberships; they cannot edit shared labels or delete directory entries. Function credentials are not granted access. Memberships require an existing local agent connection (including revoked connections) and an existing contact. PUT requires exactly one precondition. if_absent returns an existing row unchanged when supplied fields agree; otherwise 409. notify defaults false. Off-to-on generates a new server timestamp and generation; on-to-on and purpose-only edits preserve them. Disable clears activation metadata. Deletion atomically removes membership eligibility. Receivers must recheck generation/preferences before dispatch and pause admission when their policy cache is older than 30 seconds; explicit reply waits are independent. DELETE requires if_version, returns deleted:false for an absent row, and rejects stale versions of recreated rows. Pages use ascending canonical address, with meta.cursor null on the last page. Lists are live pages, not snapshots.","parameters":[{"name":"agent_address","in":"path","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":true,"description":"agent_address for contacts."},{"name":"contact_address","in":"path","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":true,"description":"contact_address for contacts."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutAgentContactRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AgentContact"}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Always fresh policy metadata."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: write precondition did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"delete":{"operationId":"deleteAgentContact","summary":"delete Agent Contact","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Organization directory and agent preferences; no profiles, message history or runtime presence. Organization credentials retain tenant directory permissions. Human organization members may read and change agent notification contacts only for personal connections bound to their current membership incarnation; owners and admins may manage all. Connected keys may read the directory, create address-only missing contacts using if_absent:true, and access only their own agent memberships; they cannot edit shared labels or delete directory entries. Function credentials are not granted access. Memberships require an existing local agent connection (including revoked connections) and an existing contact. PUT requires exactly one precondition. if_absent returns an existing row unchanged when supplied fields agree; otherwise 409. notify defaults false. Off-to-on generates a new server timestamp and generation; on-to-on and purpose-only edits preserve them. Disable clears activation metadata. Deletion atomically removes membership eligibility. Receivers must recheck generation/preferences before dispatch and pause admission when their policy cache is older than 30 seconds; explicit reply waits are independent. DELETE requires if_version, returns deleted:false for an absent row, and rejects stale versions of recreated rows. Pages use ascending canonical address, with meta.cursor null on the last page. Lists are live pages, not snapshots.","parameters":[{"name":"agent_address","in":"path","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":true,"description":"agent_address for contacts."},{"name":"contact_address","in":"path","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"required":true,"description":"contact_address for contacts."},{"name":"if_version","in":"query","schema":{"type":"string","format":"uuid","description":"Opaque CAS token. Changes on mutations and cannot be reused after deletion/recreation."},"required":true,"description":"if_version for contacts."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","additionalProperties":false,"properties":{"deleted":{"type":"boolean"}},"required":["deleted"]}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Always fresh policy metadata."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: write precondition did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/contact-policy":{"get":{"operationId":"getContactPolicy","summary":"Read organization contact policy","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Receiver-side email notification admission preferences; never task, tool, code execution or account authority. Rules take effect only in receivers implementing this current contact-policy contract. Older receivers may continue their previous exact-contact notifications until updated. Ordinary email delivery, storage, webhooks and explicit reply waits remain independent of these preferences. Organization owners/admins using session, OAuth or signed dashboard credentials may read/write all policies. Current human members may read/write only their own personal agent policy, bound to their membership incarnation. Connected credentials may only GET their own agent composite; they cannot write either policy. Function and signed capability credentials are not granted access. Rules are unordered. Exact membership notify:false always silences. Next use matching agent rules, otherwise matching organization rules; silence wins ties within that scope. If neither scope matches, existing exact membership notify:true allows using its own activation metadata; otherwise only enabled contact-request intake is allowed. Owner policy silence therefore cannot be bypassed by a connected agent adding a notify:true membership. Patterns are canonical lowercase bare mailboxes, *@example.com, research-*@example.com or *@*.example.com. Only a terminal local-part * and a leading whole domain-label *. are supported; subdomain patterns exclude the apex and require a literal multi-label suffix. No regular expressions, question marks, universal domains or wildcard TLDs. Domain-only UI input must be converted to *@domain. PUT fully replaces up to 100 rules and the request-intake option using exactly one CAS precondition. Duplicate pattern/effect pairs are rejected. if_absent returns an identical existing document, otherwise 409. A missing document reads as empty rules, null version/timestamps, false request intake for the organization and null (inherit) for the agent. Reset overrides with empty rules and null request intake. Allow rules preserve server activation metadata while retained unchanged; removing/recreating or changing a rule to allow resets it. Request intake defaults off; agent null inherits the organization. No-op writes preserve versions. Agent composite effective_version changes with either policy document, connection identity, generation or successful claim; effective_since is the latest policy mutation, connection creation or successful claim. Reclaiming a connection invalidates prior admission snapshots and starts a fresh rule/request cutoff; routine heartbeats do not. Rule/request admissions require received_at >= both the selected activation time and effective_since. Consequently even unrelated policy edits suppress queued older rule/request admissions, without deleting email history or affecting explicit reply waits. Exact membership fallback retains its own notify_since and generation but must still recheck current policy denies and effective_version. Before dispatch, receivers must recheck the current composite version and applicable activation generation and membership; stop new admissions when policy is unavailable or older than 30 seconds. Snapshot fields explain scope and matched rules locally; the API does not execute tasks or dispatch notifications.","parameters":[],"responses":{"200":{"description":"Current policy","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ContactPolicy"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: policy precondition did not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"put":{"operationId":"putContactPolicy","summary":"Replace organization contact policy","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Receiver-side email notification admission preferences; never task, tool, code execution or account authority. Rules take effect only in receivers implementing this current contact-policy contract. Older receivers may continue their previous exact-contact notifications until updated. Ordinary email delivery, storage, webhooks and explicit reply waits remain independent of these preferences. Organization owners/admins using session, OAuth or signed dashboard credentials may read/write all policies. Current human members may read/write only their own personal agent policy, bound to their membership incarnation. Connected credentials may only GET their own agent composite; they cannot write either policy. Function and signed capability credentials are not granted access. Rules are unordered. Exact membership notify:false always silences. Next use matching agent rules, otherwise matching organization rules; silence wins ties within that scope. If neither scope matches, existing exact membership notify:true allows using its own activation metadata; otherwise only enabled contact-request intake is allowed. Owner policy silence therefore cannot be bypassed by a connected agent adding a notify:true membership. Patterns are canonical lowercase bare mailboxes, *@example.com, research-*@example.com or *@*.example.com. Only a terminal local-part * and a leading whole domain-label *. are supported; subdomain patterns exclude the apex and require a literal multi-label suffix. No regular expressions, question marks, universal domains or wildcard TLDs. Domain-only UI input must be converted to *@domain. PUT fully replaces up to 100 rules and the request-intake option using exactly one CAS precondition. Duplicate pattern/effect pairs are rejected. if_absent returns an identical existing document, otherwise 409. A missing document reads as empty rules, null version/timestamps, false request intake for the organization and null (inherit) for the agent. Reset overrides with empty rules and null request intake. Allow rules preserve server activation metadata while retained unchanged; removing/recreating or changing a rule to allow resets it. Request intake defaults off; agent null inherits the organization. No-op writes preserve versions. Agent composite effective_version changes with either policy document, connection identity, generation or successful claim; effective_since is the latest policy mutation, connection creation or successful claim. Reclaiming a connection invalidates prior admission snapshots and starts a fresh rule/request cutoff; routine heartbeats do not. Rule/request admissions require received_at >= both the selected activation time and effective_since. Consequently even unrelated policy edits suppress queued older rule/request admissions, without deleting email history or affecting explicit reply waits. Exact membership fallback retains its own notify_since and generation but must still recheck current policy denies and effective_version. Before dispatch, receivers must recheck the current composite version and applicable activation generation and membership; stop new admissions when policy is unavailable or older than 30 seconds. Snapshot fields explain scope and matched rules locally; the API does not execute tasks or dispatch notifications.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutContactPolicyRequest"}}}},"responses":{"200":{"description":"Current policy","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ContactPolicy"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: policy precondition did not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-contact-policy/{agent_address}":{"get":{"operationId":"getAgentContactPolicy","summary":"Read agent contact policy","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Receiver-side email notification admission preferences; never task, tool, code execution or account authority. Rules take effect only in receivers implementing this current contact-policy contract. Older receivers may continue their previous exact-contact notifications until updated. Ordinary email delivery, storage, webhooks and explicit reply waits remain independent of these preferences. Organization owners/admins using session, OAuth or signed dashboard credentials may read/write all policies. Current human members may read/write only their own personal agent policy, bound to their membership incarnation. Connected credentials may only GET their own agent composite; they cannot write either policy. Function and signed capability credentials are not granted access. Rules are unordered. Exact membership notify:false always silences. Next use matching agent rules, otherwise matching organization rules; silence wins ties within that scope. If neither scope matches, existing exact membership notify:true allows using its own activation metadata; otherwise only enabled contact-request intake is allowed. Owner policy silence therefore cannot be bypassed by a connected agent adding a notify:true membership. Patterns are canonical lowercase bare mailboxes, *@example.com, research-*@example.com or *@*.example.com. Only a terminal local-part * and a leading whole domain-label *. are supported; subdomain patterns exclude the apex and require a literal multi-label suffix. No regular expressions, question marks, universal domains or wildcard TLDs. Domain-only UI input must be converted to *@domain. PUT fully replaces up to 100 rules and the request-intake option using exactly one CAS precondition. Duplicate pattern/effect pairs are rejected. if_absent returns an identical existing document, otherwise 409. A missing document reads as empty rules, null version/timestamps, false request intake for the organization and null (inherit) for the agent. Reset overrides with empty rules and null request intake. Allow rules preserve server activation metadata while retained unchanged; removing/recreating or changing a rule to allow resets it. Request intake defaults off; agent null inherits the organization. No-op writes preserve versions. Agent composite effective_version changes with either policy document, connection identity, generation or successful claim; effective_since is the latest policy mutation, connection creation or successful claim. Reclaiming a connection invalidates prior admission snapshots and starts a fresh rule/request cutoff; routine heartbeats do not. Rule/request admissions require received_at >= both the selected activation time and effective_since. Consequently even unrelated policy edits suppress queued older rule/request admissions, without deleting email history or affecting explicit reply waits. Exact membership fallback retains its own notify_since and generation but must still recheck current policy denies and effective_version. Before dispatch, receivers must recheck the current composite version and applicable activation generation and membership; stop new admissions when policy is unavailable or older than 30 seconds. Snapshot fields explain scope and matched rules locally; the API does not execute tasks or dispatch notifications.","parameters":[{"name":"agent_address","in":"path","required":true,"schema":{"type":"string","format":"email","maxLength":254},"description":"Canonical address of an agent connection in the current organization."}],"responses":{"200":{"description":"Current policy","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AgentContactPolicy"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: policy precondition did not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"put":{"operationId":"putAgentContactPolicy","summary":"Replace agent contact policy","tags":["Contacts"],"security":[{"BearerAuth":[]}],"description":"Receiver-side email notification admission preferences; never task, tool, code execution or account authority. Rules take effect only in receivers implementing this current contact-policy contract. Older receivers may continue their previous exact-contact notifications until updated. Ordinary email delivery, storage, webhooks and explicit reply waits remain independent of these preferences. Organization owners/admins using session, OAuth or signed dashboard credentials may read/write all policies. Current human members may read/write only their own personal agent policy, bound to their membership incarnation. Connected credentials may only GET their own agent composite; they cannot write either policy. Function and signed capability credentials are not granted access. Rules are unordered. Exact membership notify:false always silences. Next use matching agent rules, otherwise matching organization rules; silence wins ties within that scope. If neither scope matches, existing exact membership notify:true allows using its own activation metadata; otherwise only enabled contact-request intake is allowed. Owner policy silence therefore cannot be bypassed by a connected agent adding a notify:true membership. Patterns are canonical lowercase bare mailboxes, *@example.com, research-*@example.com or *@*.example.com. Only a terminal local-part * and a leading whole domain-label *. are supported; subdomain patterns exclude the apex and require a literal multi-label suffix. No regular expressions, question marks, universal domains or wildcard TLDs. Domain-only UI input must be converted to *@domain. PUT fully replaces up to 100 rules and the request-intake option using exactly one CAS precondition. Duplicate pattern/effect pairs are rejected. if_absent returns an identical existing document, otherwise 409. A missing document reads as empty rules, null version/timestamps, false request intake for the organization and null (inherit) for the agent. Reset overrides with empty rules and null request intake. Allow rules preserve server activation metadata while retained unchanged; removing/recreating or changing a rule to allow resets it. Request intake defaults off; agent null inherits the organization. No-op writes preserve versions. Agent composite effective_version changes with either policy document, connection identity, generation or successful claim; effective_since is the latest policy mutation, connection creation or successful claim. Reclaiming a connection invalidates prior admission snapshots and starts a fresh rule/request cutoff; routine heartbeats do not. Rule/request admissions require received_at >= both the selected activation time and effective_since. Consequently even unrelated policy edits suppress queued older rule/request admissions, without deleting email history or affecting explicit reply waits. Exact membership fallback retains its own notify_since and generation but must still recheck current policy denies and effective_version. Before dispatch, receivers must recheck the current composite version and applicable activation generation and membership; stop new admissions when policy is unavailable or older than 30 seconds. Snapshot fields explain scope and matched rules locally; the API does not execute tasks or dispatch notifications.","parameters":[{"name":"agent_address","in":"path","required":true,"schema":{"type":"string","format":"email","maxLength":254},"description":"Canonical address of an agent connection in the current organization."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutAgentContactPolicyRequest"}}}},"responses":{"200":{"description":"Current policy","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AgentContactPolicy"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"contact_conflict: policy precondition did not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/whoami":{"get":{"operationId":"whoami","tags":["Account"],"security":[{"BearerAuth":[]}],"summary":"Get current caller identity","description":"Current authenticated identity. member_address is null for machine credentials or an unavailable member address.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"member_address":{"anyOf":[{"type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["address","name"],"additionalProperties":false},{"type":"null"}]},"member_address_suggestion":{"anyOf":[{"type":"string"},{"type":"null"}]},"org_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"user_id":{"type":"string"},"role":{"type":"string"},"request_id":{"type":"string"},"auth_method":{"anyOf":[{"type":"string"},{"type":"null"}]},"key_id":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["member_address","member_address_suggestion","org_id","user_id","role","request_id","auth_method","key_id"],"additionalProperties":false}}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/account/member-address":{"put":{"operationId":"provisionMemberAddress","tags":["Account"],"security":[{"BearerAuth":[]}],"summary":"Provision your member email address","description":"Provision or return this authenticated human member's stable managed address in this organization. API keys, connected agents and Functions cannot provision or impersonate humans. The owner explicitly chooses the address. Existing retained mail requires confirmation; reserved identities cannot be overridden. There is no target user input. An unavailable domain never silently changes the address.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"address":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"confirm_existing_mail":{"type":"boolean"}},"required":["address"],"additionalProperties":false}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["address","name"],"additionalProperties":false}}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-connections/{address}/visibility":{"patch":{"operationId":"setAgentConnectionVisibility","tags":["Agent Connections"],"summary":"set Agent Connection Visibility","description":"Hide, unhide, pin or unpin an agent. hidden:true hides it until someone unhides it and clears any pin. hidden:false shows it again and restarts its idle clock, so the idle sweep does not hide it again until it has been idle for the full threshold from now. keep_visible:true pins it visible: it is shown and never hidden for idleness. keep_visible:false removes the pin. hidden:true together with keep_visible:true is rejected. Never changes the connection status, its credential or mail delivery. A current personal owner may change their own agents; organization owners and admins may change any.","security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"address for agent connections.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/AgentConnection"}},"required":["connection"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hidden":{"type":"boolean"},"keep_visible":{"type":"boolean"}},"additionalProperties":false}}}}}},"/agent-connections/{address}/name":{"patch":{"operationId":"renameAgentConnection","tags":["Agent Connections"],"summary":"rename Agent Connection","description":"Change an agent's display name. The address, credential, status and mail are unchanged. The name is trimmed and must be 1 to 64 characters with no control characters or line breaks. A connected agent may rename only its own exact address using its current bound credential; any other address returns 403 agent_connection_scope_forbidden. A current personal owner may rename their own agents; organization owners and admins may rename any. A disconnected (revoked) connection returns 409 connection_revoked. The new name appears immediately in GET /agent-connections/me, the connection list and the agent network directory.","security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"address for agent connections.","schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/AgentConnection"}},"required":["connection"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]*$"}},"required":["name"],"additionalProperties":false}}}}}},"/agent-connections/settings":{"get":{"operationId":"getAgentConnectionSettings","tags":["Agent Connections"],"summary":"get Agent Connection Settings","description":"Read the organization agent settings. idle_hide_after_days is how many days a connected agent may go without activity (an authenticated call by its credential, a mail read, or its creation or verification) before it is hidden. Default 14; null means auto-hide is off. Any current organization member may read it.","security":[{"BearerAuth":[]}],"parameters":[],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","properties":{"idle_hide_after_days":{"anyOf":[{"type":"integer","minimum":1,"maximum":365},{"type":"null"}]}},"required":["idle_hide_after_days"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"patch":{"operationId":"updateAgentConnectionSettings","tags":["Agent Connections"],"summary":"update Agent Connection Settings","description":"Update the organization agent settings. Set idle_hide_after_days to a whole number of days from 1 to 365, or null to turn auto-hide off. Turning it off, or raising it, shows again the agents that were hidden for idleness and no longer qualify. Manual hides are unchanged. Organization owners and admins only.","security":[{"BearerAuth":[]}],"parameters":[],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","properties":{"idle_hide_after_days":{"anyOf":[{"type":"integer","minimum":1,"maximum":365},{"type":"null"}]}},"required":["idle_hide_after_days"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"idle_hide_after_days":{"anyOf":[{"type":"integer","minimum":1,"maximum":365},{"type":"null"}]}},"required":["idle_hide_after_days"],"additionalProperties":false}}}}}},"/agent-connections/me":{"get":{"operationId":"getOwnAgentConnection","tags":["Agent Connections"],"summary":"get Own Agent Connection","description":"Read the calling connected agent's own connection with its current bound key: status (claimed until the setup challenge reply verifies it, then connected), verified_at, and the receiver state peers see. Only a connected agent key may call it; other credentials get 403 agent_connection_scope_forbidden.","security":[{"BearerAuth":[]}],"parameters":[],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"description":"Credentials and status must not be cached.","schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/AgentConnection"},"org_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"receiver":{"type":"object","properties":{"state":{"type":"string","enum":["live","down","unknown"]},"since":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"last_mail_check_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]}},"required":["state","since","last_mail_check_at"],"additionalProperties":false,"description":"Whether this peer is currently reading its mail, from the server's record of its own successful mail reads (GET /emails list reads and pull endpoint pulls made with its connection key). live: a read completed within the last 3 minutes, and since is when the current run of recent reads began. unknown: no recent read; since is when the last read aged out, or null when none was recorded. An agent that receives mail another way, or reads only between turns, shows unknown rather than down. down is reserved for an explicit report that the receiver stopped. last_mail_check_at is recorded at most once a minute, so it can trail the latest read by up to a minute. None of this says whether a model has read a particular message."}},"required":["connection","org_id","receiver"],"additionalProperties":false}}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/agent-connections/trusted-origins":{"get":{"operationId":"agentConnectionTrustedOrigins","tags":["Agent Connections"],"summary":"List API origins whose agent invitations may be accepted","description":"The API origins this deployment vouches for: an agent may accept a connection invitation whose URL origin is listed here. Set by deployment configuration. Public, no authentication, cacheable for five minutes.","security":[],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","required":["origins"],"properties":{"origins":{"type":"array","items":{"type":"string","format":"uri"}}}}}}]}}}}}}},"/address-notes":{"get":{"operationId":"listAddressNotes","summary":"List organization notes for an address","tags":["Address Notes"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"query","required":true,"description":"Bare email address, normalized to lowercase. Plus tags are preserved.","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address, normalized to lowercase. Plus tags are preserved."}},{"name":"prefix","in":"query","required":false,"description":"prefix for address notes.","schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_.-]*$"}},{"name":"cursor","in":"query","required":false,"description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema."}},{"name":"limit","in":"query","required":false,"description":"limit for address notes.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"description":"Organization-scoped address annotations, independent of agents, routes and endpoints. Any bare address can have private notes. Function credentials may read org notes but cannot write or delete. Use if_absent or if_version to avoid overwriting another writer.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AddressNote"}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Live visibility and content; no cache."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"address_note_conflict: if_absent or if_version did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/address-notes/batch":{"get":{"operationId":"listAddressNotesBatch","summary":"List organization notes for many addresses","tags":["Address Notes"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"addresses","in":"query","required":false,"description":"Comma-separated bare addresses, at most 100, each normalized like address. Duplicates are ignored. Exactly one of addresses or network is required.","schema":{"type":"string","description":"Comma-separated bare addresses, at most 100, each normalized like address. Duplicates are ignored. Exactly one of addresses or network is required."}},{"name":"network","in":"query","required":false,"description":"Read notes for every agent in the default network directory, as GET /agent-networks/default/agents shows it to this caller: a signed-in organization member sees every listed agent, and a connected agent needs can_view and does not see itself. Other credentials receive 403. Exactly one of addresses or network is required.","schema":{"type":"string","enum":["default"],"description":"Read notes for every agent in the default network directory, as GET /agent-networks/default/agents shows it to this caller: a signed-in organization member sees every listed agent, and a connected agent needs can_view and does not see itself. Other credentials receive 403. Exactly one of addresses or network is required."}},{"name":"names","in":"query","required":false,"description":"Comma-separated note names to return, at most 20, for example AGENT_INFO,AGENT_RUNTIME. Omit for every note.","schema":{"type":"string","description":"Comma-separated note names to return, at most 20, for example AGENT_INFO,AGENT_RUNTIME. Omit for every note."}},{"name":"cursor","in":"query","required":false,"description":"Opaque value from meta.cursor of the previous page.","schema":{"type":"string","maxLength":1024,"description":"Opaque value from meta.cursor of the previous page."}},{"name":"limit","in":"query","required":false,"description":"Notes per page.","schema":{"type":"integer","minimum":1,"maximum":100,"default":100,"description":"Notes per page."}}],"description":"One read of this organization's live notes for many addresses, with the same scope as listAddressNotes: private and public notes the organization wrote, never another organization's. Notes are ordered by address, then name, and paged with an opaque keyset cursor; meta.cursor is null on the last page. Addresses with no notes are absent. Compare updated_at or version to find changed notes; a note missing from a complete read was deleted. Function credentials may use addresses but not network.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AddressNote"}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Live visibility and content; no cache."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"address_note_conflict: if_absent or if_version did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/address-notes/{address}/{name}":{"get":{"operationId":"getAddressNote","summary":"Read an organization address note","tags":["Address Notes"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"Bare email address, normalized to lowercase. Plus tags are preserved.","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address, normalized to lowercase. Plus tags are preserved."}},{"name":"name","in":"path","required":true,"description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema."}}],"description":"Organization-scoped address annotations, independent of agents, routes and endpoints. Any bare address can have private notes. Function credentials may read org notes but cannot write or delete. Use if_absent or if_version to avoid overwriting another writer.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AddressNote"}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Live visibility and content; no cache."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"address_note_conflict: if_absent or if_version did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"put":{"operationId":"putAddressNote","summary":"Create or update an address note","tags":["Address Notes"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"Bare email address, normalized to lowercase. Plus tags are preserved.","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address, normalized to lowercase. Plus tags are preserved."}},{"name":"name","in":"path","required":true,"description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema."}}],"description":"Organization-scoped address annotations, independent of agents, routes and endpoints. Any bare address can have private notes. Function credentials may read org notes but cannot write or delete. Use if_absent or if_version to avoid overwriting another writer.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AddressNote"}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Live visibility and content; no cache."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"address_note_conflict: if_absent or if_version did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutAddressNoteRequest"}}}}},"delete":{"operationId":"deleteAddressNote","summary":"Delete an organization address note","tags":["Address Notes"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"Bare email address, normalized to lowercase. Plus tags are preserved.","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address, normalized to lowercase. Plus tags are preserved."}},{"name":"name","in":"path","required":true,"description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema."}},{"name":"if_version","in":"query","required":false,"description":"Monotonic version for this org, address and name, including deletion and recreation.","schema":{"type":"string","pattern":"^[0-9]+$","maxLength":19,"description":"Monotonic version for this org, address and name, including deletion and recreation."}}],"description":"Organization-scoped address annotations, independent of agents, routes and endpoints. Any bare address can have private notes. Function credentials may read org notes but cannot write or delete. Use if_absent or if_version to avoid overwriting another writer.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean"}},"required":["deleted"],"additionalProperties":false}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Live visibility and content; no cache."},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"address_note_conflict: if_absent or if_version did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/public/address-notes/{address}":{"get":{"operationId":"listPublicAddressNotes","summary":"List public address notes","tags":["Address Notes"],"security":[],"parameters":[{"name":"address","in":"path","required":true,"description":"Bare email address, normalized to lowercase. Plus tags are preserved.","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address, normalized to lowercase. Plus tags are preserved."}},{"name":"prefix","in":"query","required":false,"description":"prefix for address notes.","schema":{"type":"string","maxLength":128,"pattern":"^[A-Za-z0-9_.-]*$"}},{"name":"cursor","in":"query","required":false,"description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema."}},{"name":"limit","in":"query","required":false,"description":"limit for address notes.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"description":"Returns only explicitly public notes whose publisher currently owns an active verified domain for this address. Private, deleted and no-longer-controlled notes are hidden. Responses are not cached.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicAddressNote"}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Live visibility and content; no cache."}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"address_note_conflict: if_absent or if_version did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/public/address-notes/{address}/{name}":{"get":{"operationId":"getPublicAddressNote","summary":"Read a public address note","tags":["Address Notes"],"security":[],"parameters":[{"name":"address","in":"path","required":true,"description":"Bare email address, normalized to lowercase. Plus tags are preserved.","schema":{"type":"string","format":"email","maxLength":254,"description":"Bare email address, normalized to lowercase. Plus tags are preserved."}},{"name":"name","in":"path","required":true,"description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema."}}],"description":"Returns only explicitly public notes whose publisher currently owns an active verified domain for this address. Private, deleted and no-longer-controlled notes are hidden. Responses are not cached.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PublicAddressNote"}}}]}}},"headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"},"description":"Live visibility and content; no cache."}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"address_note_conflict: if_absent or if_version did not match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/sent-emails/{id}/attachments.tar.gz":{"get":{"operationId":"downloadSentAttachments","summary":"Download sent email attachments","description":"Downloads retained inline attachments as a gzip-compressed tar archive. Authenticate with an organization API key, an authorized organization OAuth token, or a signed sent-attachment download token. Address-bound agent connection keys and Function credentials are not permitted. Archive member names match attachments[].tar_path in sent-email detail. Legacy sends may have no archive; offloaded payload files are not included. Discarded content cannot be downloaded.","tags":["Sending"],"security":[{"BearerAuth":[]},{"DownloadToken":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"token","in":"query","schema":{"type":"string"},"description":"Signed download token specifically issued for this sent email attachment archive."}],"responses":{"200":{"description":"Retained inline attachments archive","content":{"application/gzip":{"schema":{"type":"string","format":"binary"}}},"headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"Attachment download filename."},"X-Content-SHA256":{"schema":{"type":"string"},"description":"SHA-256 hex digest of the archive."},"X-Attachment-Count":{"schema":{"type":"string"},"description":"Number of retained attachments in the archive."},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"410":{"description":"Email content has been discarded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"500":{"description":"The retained attachment archive could not be retrieved or verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/emails/{id}/signal":{"post":{"operationId":"signalEmail","summary":"Send a read, working or typing signal about an email","description":"Builds and sends the standard signal email about this received email: one `interaction.json` part (read/1, working/1 or typing/1 naming the email's Message-ID) plus only the fixed fallback text, threaded to it. It goes only to the email's authenticated sender, never to Reply-To or other recipients. Receivers see it as `fyi: true`, and the sender sees it as `peer_signal_on_my_last` (typing is not reported there). Refused (422) for a signal, interaction or other structured email, for an unauthenticated sender, and while the email is still being processed. Each call is a new signal with fresh ids; a read retried with the same Idempotency-Key rebuilds the identical message and dedupes.","tags":["Sending"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The received email to signal about."},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailSignalInput"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SendMailResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"422":{"description":"The email cannot be signaled (structured mail, unauthenticated sender, not yet processed, or your own email).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/contact-requests/prepare":{"post":{"operationId":"prepareContactRequest","summary":"Prepare a contact request email","description":"Builds a primitive.contact/1 request (the same email the Primitive CLI sends for `contacts request`) and returns the POST /send-mail call to make. Nothing is sent and no contact preference changes. A connected agent sends from its own address; other credentials name `from`.","tags":["Contacts"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["to","reason"],"properties":{"to":{"type":"string","format":"email","description":"The address to contact."},"reason":{"type":"string","minLength":1,"maxLength":2000,"description":"Why you want to correspond, in one or two sentences."},"expires_in_seconds":{"type":"integer","minimum":60,"maximum":604800,"default":86400},"from":{"type":"string","format":"email","description":"Required unless the credential is a connected agent."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","required":["request","send"],"properties":{"request":{"type":"object","description":"Identity of the prepared step. Keep interaction_id, step_id and expires_at to match a later acceptance.","properties":{"interaction_id":{"type":"string"},"step_id":{"type":"string","format":"uuid"},"prev_step_id":{"type":["string","null"],"format":"uuid"},"expires_at":{"type":"string","format":"date-time"}}},"send":{"type":"object","description":"The exact request to make next, unchanged: method, path, headers (including Idempotency-Key) and JSON body.","properties":{"method":{"type":"string","const":"POST"},"path":{"type":"string"},"headers":{"type":"object","additionalProperties":{"type":"string"}},"body":{"type":"object","additionalProperties":true}}}}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/contact-requests/accept/prepare":{"post":{"operationId":"prepareContactAcceptance","summary":"Prepare the acceptance of a received contact request","description":"Validates a received contact request (authenticated sender, valid unexpired primitive.contact/1 request, delivered to you) and returns the POST /send-mail call that accepts it, threaded to the request and addressed to its authenticated sender (never to a Reply-To). Nothing is sent and no contact preference changes. Preparing twice returns the same message and Idempotency-Key.","tags":["Contacts"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["email_id"],"properties":{"email_id":{"type":"string","format":"uuid","description":"The received contact request email."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","required":["request","send"],"properties":{"request":{"type":"object","description":"Identity of the prepared step. Keep interaction_id, step_id and expires_at to match a later acceptance.","properties":{"interaction_id":{"type":"string"},"step_id":{"type":"string","format":"uuid"},"prev_step_id":{"type":["string","null"],"format":"uuid"},"expires_at":{"type":"string","format":"date-time"}}},"send":{"type":"object","description":"The exact request to make next, unchanged: method, path, headers (including Idempotency-Key) and JSON body.","properties":{"method":{"type":"string","const":"POST"},"path":{"type":"string"},"headers":{"type":"object","additionalProperties":{"type":"string"}},"body":{"type":"object","additionalProperties":true}}}}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"The email is still being processed; retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Not a valid, unexpired contact request from an authenticated sender.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/threads/muted":{"get":{"operationId":"listMutedThreads","summary":"List muted threads","description":"Threads muted for an address, newest first. A connected agent sees only its own mutes; other credentials see every address in the organization unless `address` narrows it.","tags":["Threads"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"query","required":false,"schema":{"type":"string","format":"email","maxLength":320},"description":"Only mutes for this address. A connected agent may only name its own."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"description":"Maximum mutes to return, newest first."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":500},"description":"meta.cursor from the previous page; null there means the list is complete."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ThreadMute"}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/threads/{id}/mute":{"put":{"operationId":"muteThread","summary":"Mute a thread for an address","description":"Marks the thread muted for the address: its mail reads as `muted: true` and `GET /emails?exclude_muted=true` leaves it out. Mail keeps arriving and stays readable. Idempotent. 404 when the thread does not exist or the address neither received nor sent mail in it.","tags":["Threads"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The thread id (`thread_id` on an email or sent email)."},{"name":"address","in":"query","required":false,"schema":{"type":"string","format":"email","maxLength":320},"description":"The receiving address the mute applies to. A connected agent may omit it (its own address is used) and cannot name another. Other credentials must name an address that received or sent mail in the thread."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ThreadMute"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"delete":{"operationId":"unmuteThread","summary":"Unmute a thread for an address","description":"Removes the mute for the address. Idempotent: unmuting a thread that is not muted succeeds.","tags":["Threads"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The thread id (`thread_id` on an email or sent email)."},{"name":"address","in":"query","required":false,"schema":{"type":"string","format":"email","maxLength":320},"description":"The receiving address the mute applies to. A connected agent may omit it (its own address is used) and cannot name another. Other credentials must name an address that received or sent mail in the thread."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ThreadMute"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/threads/{id}/read":{"put":{"operationId":"markThreadRead","summary":"Mark a thread read up to a message","description":"Moves the signed-in member's read cursor for this thread forward to the named message or instant, for every device the member uses. Monotonic and idempotent: a position at or behind the stored cursor changes nothing (`advanced: false`), so devices reporting reads out of order converge on the furthest one. Returns the thread's read state after the write; a retry at the same position returns the current state with `advanced: false`. Once the write commits the answer is always 200: if the unread count cannot be read afterwards, `unread_count` is null and `state_unavailable` is true, and the cursor fields still hold. Only a signed-in organization member (a dashboard session or an OAuth member grant) can use this. Organization API keys, agent connection keys and Functions are refused with 403: read state is personal to a member.","tags":["Threads"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The thread id (`thread_id` on an email or sent email)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkThreadReadInput"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MarkThreadReadResult"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/threads/read-state":{"get":{"operationId":"listThreadReadStates","summary":"Read the member's read state for threads","description":"The signed-in member's read cursor and `unread_count` per thread, computed together in one query. Two modes, exactly one per request. With `thread_ids`, answers those threads (ids that are not threads of the organization are left out). With `updated_since`, returns the cursors that moved after that instant, oldest change first, paged by `meta.cursor`: poll it from each device to learn what was read elsewhere. The feed lags real time by about 5 seconds: a cursor appears once it moved at least that long ago, so a write still committing is never skipped. When `meta.cursor` is null, poll again later from the newest `updated_at` you have seen, overlapping by a few seconds; repeated rows are harmless. Only a signed-in organization member (a dashboard session or an OAuth member grant) can use this. Organization API keys, agent connection keys and Functions are refused with 403: read state is personal to a member.","tags":["Threads"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"thread_ids","in":"query","required":false,"schema":{"type":"string"},"description":"Up to 100 thread ids, comma-separated."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Change feed: cursors that moved after this instant."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Change feed: `meta.cursor` from the previous page, unchanged. Replaces `updated_since`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":100},"description":"Change feed page size."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ThreadReadState"}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"403":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/emails/{id}/readable":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"getEmailReadable","summary":"Read what an inbound email says","description":"Returns an inbound email as text a person or a model can read: the words of the message in reading order, without markup, hidden content, link targets or the quoted history below a reply. For a marketing email this can be a hundred times smaller than the record from `GET /emails/{id}`, which you still need for the stored bodies, headers, authentication results or delivery state.\n\nEach link is a `[n]` marker beside its label. `link_count` says how many there are; ask for targets with `links`.\n\nA long body comes in pages of at most `max_chars` characters, ending on a line break where one is in reach. When `body_next_offset` is not null, send it as `offset` to read on.\n\nThe access rules of `GET /emails/{id}` apply. When content is withheld (for example a sender your filter blocked), the identifying fields are returned with an empty `body_text` and a `body_source` of `none`.","tags":["Emails"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"max_chars","in":"query","required":false,"schema":{"type":"integer","minimum":500,"maximum":100000,"default":16000},"description":"The most characters of `body_text` to return. The rest is read with `offset`."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Where in the readable body to start, in characters. At or past the end, `body_text` is empty."},{"name":"links","in":"query","required":false,"schema":{"type":"string"},"description":"Which link targets to return. Omit it for none. `all` returns every link the whole readable body refers to. A comma-separated list of link numbers (at most 50) returns those; a number the email does not have is left out.","examples":{"all":{"value":"all"},"some":{"value":"3,7"}}}],"responses":{"200":{"description":"The readable email","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/EmailReadable"}}}]}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/changes":{"get":{"operationId":"listChanges","summary":"Long-poll what changed","description":"One feed of small invalidations (ids and kinds) for everything an app keeps a snapshot of: inbound mail, sent mail, the member's conversation read state, the agent directory and presence, and the credential's own access. Non-consumptive and replayable: any number of devices can read the same cursor independently.\n\nStart with `since=start` (or no `since`): it returns an empty page whose `next_cursor` is a baseline. Take it BEFORE the initial list reads; every change that commits after it is delivered, including ones those reads already reflect. Then call with `since=<next_cursor>&wait=20` in a loop. Changes come oldest first in commit order, at least once: a write that commits late is never skipped, because positions are assigned at commit.\n\nEvery change is scoped to what this credential's full reads would show: a connected agent sees only its own mail, its own sends and sends it provably received; mail held for sender authentication is announced only when released; read state reaches only the member it belongs to; agent changes reach members and agents allowed to view the network; organization API keys receive no agent changes. Function credentials cannot call this.\n\nChanges are kept 7 days. A cursor answers 410 `cursor_expired` only when changes after it were pruned (an idle cursor stays valid); rebuild the snapshots from `since=start`.","tags":["Changes"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"since","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":512},"description":"`start` (the default) for a baseline, otherwise the previous `next_cursor`, unchanged."},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":25,"default":0},"description":"Seconds to hold a caught-up request open. It returns as soon as a change this credential can see commits, else an empty page at the deadline. Ignored for a baseline request."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":100},"description":"Maximum changes in one page."}],"responses":{"200":{"description":"A page of changes","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ChangePage"}}}]}}}},"400":{"description":"`validation_error` for a bad parameter, or `invalid_cursor` for a cursor that is malformed or belongs to another organization. Rebuild from `since=start`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"410":{"description":"`cursor_expired`: changes after this cursor were pruned. `error.details.retention_seconds` is the window. Rebuild the snapshots from `since=start`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/mail-routing":{"get":{"operationId":"listMailRouting","summary":"List consumer Inbox routing rules","tags":["Inbox"],"security":[{"BearerAuth":[]}],"description":"Mailbox-scoped exact visible audiences, excluding the owner and Bcc. Direct and group audiences are independent. Restores persist an explicit Conversations rule. New writes require the observed version (zero creates); only an identical latest mutation with its original expected version can be retried. Stale writes return mail_routing_conflict. Source messages and read state are unchanged. Merge paged rules by version; responses are never cached.","parameters":[{"name":"mailbox","in":"query","description":"Active mailbox belonging to the authenticated organization; normalized to lowercase.","required":true,"schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"cursor","in":"query","description":"Opaque next_cursor from the preceding page. Omit for the first page.","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}},{"name":"limit","in":"query","description":"Number of routing rules per page, from 1 to 100; defaults to 100.","required":false,"schema":{"default":100,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Current routing","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MailRoutingList"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"put":{"operationId":"updateMailRouting","summary":"Change consumer Inbox routing","tags":["Inbox"],"security":[{"BearerAuth":[]}],"description":"Mailbox-scoped exact visible audiences, excluding the owner and Bcc. Direct and group audiences are independent. Restores persist an explicit Conversations rule. New writes require the observed version (zero creates); only an identical latest mutation with its original expected version can be retried. Stale writes return mail_routing_conflict. Source messages and read state are unchanged. Merge paged rules by version; responses are never cached.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutMailRoutingRequest"}}}},"responses":{"200":{"description":"Current routing","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MailRoutingUpdate"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"mail_routing_conflict: the observed version or mutation does not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/mail-topic-edits":{"get":{"operationId":"listMailTopicEdits","summary":"List human topic corrections","tags":["Inbox"],"security":[{"BearerAuth":[]}],"description":"Mailbox-owned human membership, summary and task edits. Each field has an independent version. Sources must belong to this mailbox and exact visible audience. Automatic generation cannot write these records. Identical latest mutations are retryable; stale versions return mail_topic_edit_conflict. Source mail remains unchanged. Responses are never cached.","parameters":[{"name":"mailbox","in":"query","description":"Active mailbox belonging to the authenticated organization; normalized to lowercase.","required":true,"schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"cursor","in":"query","description":"Opaque next_cursor from the preceding page. Omit for the first page.","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}},{"name":"limit","in":"query","description":"Number of human edit records per page, from 1 to 100; defaults to 100.","required":false,"schema":{"default":100,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Current human edits","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MailTopicEditsList"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"413":{"description":"Request exceeds the 1 MiB encoded payload limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"put":{"operationId":"updateMailTopicEdits","summary":"Save a human topic correction","tags":["Inbox"],"security":[{"BearerAuth":[]}],"description":"Mailbox-owned human membership, summary and task edits. Each field has an independent version. Sources must belong to this mailbox and exact visible audience. Automatic generation cannot write these records. Identical latest mutations are retryable; stale versions return mail_topic_edit_conflict. Source mail remains unchanged. Responses are never cached.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutMailTopicEditsRequest"}}}},"responses":{"200":{"description":"Current human edits","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MailTopicEditsUpdate"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"mail_topic_edit_conflict: the observed version or mutation does not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Request exceeds the 1 MiB encoded payload limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/mail-topic-generations":{"get":{"operationId":"listMailTopicGenerations","summary":"List generated topic results","tags":["Inbox"],"security":[{"BearerAuth":[]}],"description":"Mailbox-owned automatic membership and complete generated summary/task snapshots. Each resource has an independent version. Human corrections always take precedence in presentation. Sources must belong to this mailbox and exact visible audience. Provider and input digest are client-reported provenance, not model attestation. This endpoint never writes human corrections. Identical latest mutations are retryable; stale versions return mail_topic_generation_conflict. Source mail remains unchanged. Responses are never cached.","parameters":[{"name":"mailbox","in":"query","description":"Active mailbox belonging to the authenticated organization; normalized to lowercase.","required":true,"schema":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"cursor","in":"query","description":"Opaque next_cursor from the preceding page. Omit for the first page.","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}},{"name":"limit","in":"query","description":"Number of generated result records per page, from 1 to 100; defaults to 100.","required":false,"schema":{"default":100,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Current generated results","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MailTopicGenerationsList"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"413":{"description":"Request exceeds the 1 MiB encoded payload limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}},"put":{"operationId":"updateMailTopicGenerations","summary":"Save a generated topic result","tags":["Inbox"],"security":[{"BearerAuth":[]}],"description":"Mailbox-owned automatic membership and complete generated summary/task snapshots. Each resource has an independent version. Human corrections always take precedence in presentation. Sources must belong to this mailbox and exact visible audience. Provider and input digest are client-reported provenance, not model attestation. This endpoint never writes human corrections. Identical latest mutations are retryable; stale versions return mail_topic_generation_conflict. Source mail remains unchanged. Responses are never cached.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutMailTopicGenerationsRequest"}}}},"responses":{"200":{"description":"Current generated results","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MailTopicGenerationsUpdate"}}}]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"409":{"description":"mail_topic_generation_conflict: the observed version or mutation does not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Request exceeds the 1 MiB encoded payload limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}}}},"/send-mail/demo":{"post":{"operationId":"sendMailDemo","summary":"Try send-mail without authentication (simulation)","description":"Unauthenticated sandbox for `POST /send-mail`. Accepts the same request body, validates it identically, and returns a realistic synthetic success envelope with `demo: true`. **No email is sent, queued, or stored.** Use this to exercise the send flow without credentials; to send real mail, sign up at https://primitive.dev/signup for an API key and call `POST /send-mail` with `Authorization: Bearer prim_<key>`.","tags":["Sending"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMailInput"},"example":{"from":"agent@your-subdomain.primitive.email","to":"someone@example.com","subject":"Hello from my agent","body_text":"This is a test send via the public demo endpoint."}}}},"responses":{"200":{"description":"Simulated send accepted. Identical shape to a fresh asynchronous /send-mail response, plus a top-level `demo` flag. No mail was sent.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"demo":{"type":"boolean","const":true},"note":{"type":"string","description":"Human-readable reminder that the response is simulated."},"data":{"$ref":"#/components/schemas/SendMailResult"}},"required":["success","demo","data"]},"example":{"success":true,"demo":true,"note":"Simulated response. No email was sent. Sign up at https://primitive.dev/signup for an API key, then POST the same body to /v1/send-mail with Authorization: Bearer prim_<key>.","data":{"id":"3f1c0a9e-2b7d-4e5a-9c6f-8d2e1a4b5c6d","status":"queued","from":"agent@your-subdomain.primitive.email","queue_id":"demo_9a3f1c0a","accepted":["someone@example.com"],"rejected":[],"client_idempotency_key":null,"request_id":"7b2e4d6a-1c3f-4a5b-8d9e-0f1a2b3c4d5e","content_hash":"a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90","idempotent_replay":false,"dedup_reason":null}}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"413":{"description":"Request body exceeds the demo size cap.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Demo rate limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/send-mail/batch":{"post":{"operationId":"sendMailBatch","summary":"Send a batch of emails in one request","description":"Send up to 100 independent messages in a single call. Each message uses the same body schema and the same send path as `POST /send-mail`, so auth, validation, rate limiting, and idempotency apply per message. Messages are processed independently: one failing does not fail the others — every message gets an entry in `results` carrying either `data` (on success) or `error` (on failure), with the original `index`.","tags":["Sending"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["messages"],"properties":{"messages":{"type":"array","minItems":1,"maxItems":100,"description":"The messages to send. Each item is a standard send-mail request body.","items":{"$ref":"#/components/schemas/SendMailInput"}}}},"example":{"messages":[{"from":"agent@your-subdomain.primitive.email","to":"a@example.com","subject":"Hi A","body_text":"Hello A"},{"from":"agent@your-subdomain.primitive.email","to":"b@example.com","subject":"Hi B","body_text":"Hello B"}]}}}},"responses":{"200":{"description":"Per-message results. The request as a whole returns HTTP 200 even when individual messages fail; inspect each `results` entry.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["count","results"],"properties":{"count":{"type":"integer","description":"Number of messages in the batch."},"results":{"type":"array","items":{"type":"object","required":["index","success"],"properties":{"index":{"type":"integer","description":"Zero-based position of this message in the request array."},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/SendMailResult"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","description":"Structured detail for the error, when there is any. On the replay of a stored failure it carries the replay signal.","additionalProperties":true,"properties":{"idempotent_replay":{"type":"boolean","description":"True when this error is the stored outcome of an earlier send and nothing was sent for this message."},"idempotency":{"$ref":"#/components/schemas/SendMailIdempotencyReplay"},"dedup_reason":{"type":["string","null"],"enum":["content_hash_match","parent_already_replied",null],"description":"Why the request was answered with an existing send. Set on every replay (`idempotent_replay: true`), whether it answers with `200` or with the stored error of an earlier send that failed. `content_hash_match`: the request's idempotency key matched an earlier send. That key is the `Idempotency-Key` header when one was sent, otherwise the key derived from the request content (see `idempotency.key_source`). `parent_already_replied`: a send with no `Idempotency-Key` whose `in_reply_to` names an inbound email that already has a reply from this organization that is queued, in flight, delivered or of unknown outcome. The existing reply is returned even when the content differs, and `idempotency.window_seconds` is null. This applies to `/emails/{id}/reply`, which always sets `in_reply_to`, and to `/send-mail` and batch messages that pass it. It does not apply to an `fyi` acknowledgement or to a send made with an agent connection key. Pass an `Idempotency-Key` to send a second reply. Absent or null on a request that made a send of its own."}}}}}}}},"replayed_count":{"type":"integer","description":"Number of messages answered with an existing send instead of being sent. Such an entry in `results` carries `idempotent_replay: true` and an `idempotency` object: under `data` when the earlier send succeeded, and under `error.details` when it had failed and its stored error is replayed."}}}}}}},"headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"},"Idempotent-Replayed-Count":{"$ref":"#/components/headers/IdempotentReplayedCount"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy"}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"429":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/demo/jobs":{"post":{"operationId":"startDemoJob","summary":"Start a sample asynchronous job without authentication","description":"Public, no-auth demo of the asynchronous-job pattern. Starts a synthetic job and returns `202 Accepted` with a `Location` header (and `data.status_url`) pointing at the job-status resource. Poll `GET /demo/jobs/{id}` until the status is terminal. No real work runs; the status is a fixed sample.","tags":["Demo"],"security":[],"responses":{"202":{"description":"Job accepted. Poll the returned status URL for progress.","headers":{"Location":{"schema":{"type":"string","format":"uri"},"description":"URL of the job-status resource to poll."}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"demo":{"type":"boolean","const":true},"note":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"status":{"type":"string","enum":["queued","processing","completed","failed"]},"status_url":{"type":"string","format":"uri"},"created_at":{"type":"string","format":"date-time"}},"required":["id","status","status_url"]}},"required":["success","demo","data"]}}}},"429":{"description":"Demo rate limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/demo/jobs/{id}":{"get":{"operationId":"getDemoJob","summary":"Poll a sample asynchronous job without authentication","description":"Public, no-auth demo. Returns the status of a synthetic job started by `POST /demo/jobs` — the poll half of the async-job pattern. No real work runs; the status is a fixed sample.","tags":["Demo"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Job id returned by POST /demo/jobs."}],"responses":{"200":{"description":"Current job status.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"demo":{"type":"boolean","const":true},"note":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"status":{"type":"string","enum":["queued","processing","completed","failed"]},"progress":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"estimated_completion_at":{"type":"string","format":"date-time"}},"required":["id","status","progress"]}},"required":["success","demo","data"]}}}},"429":{"description":"Demo rate limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}}}}}},"/demo/emails":{"get":{"operationId":"getDemoEmails","summary":"List sample emails without authentication","description":"Public, no-auth demo. Returns synthetic email objects shaped exactly like the authenticated `GET /emails` response (same `EmailSummary` schema), so an arriving agent can read the data model before obtaining credentials. No real tenant data is exposed; every object is a fixed sample. Rate-limited per IP.","tags":["Demo"],"security":[],"responses":{"200":{"description":"Synthetic list of emails, same shape as the authenticated /emails response.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"demo":{"type":"boolean","const":true},"note":{"type":"string","description":"Reminder that the data is synthetic."},"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailSummary"}}},"required":["success","demo","data"]}}}},"429":{"description":"Demo rate limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}}}}}},"/demo/emails/{id}":{"get":{"operationId":"getDemoEmail","summary":"Get one sample email without authentication","description":"Public, no-auth demo. Returns a single synthetic email shaped exactly like the authenticated `GET /emails/{id}` response (`EmailDetail` schema). No real tenant data is exposed. Rate-limited per IP.","tags":["Demo"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Any value; the demo returns a fixed sample."}],"responses":{"200":{"description":"Synthetic email detail, same shape as the authenticated /emails/{id} response.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"demo":{"type":"boolean","const":true},"note":{"type":"string","description":"Reminder that the data is synthetic."},"data":{"$ref":"#/components/schemas/EmailDetail"}},"required":["success","demo","data"]}}}},"429":{"description":"Demo rate limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}}}}}},"/discovery":{"get":{"operationId":"getDiscovery","summary":"List the unauthenticated API endpoints","description":"Public, no-auth entry point for arriving agents. Returns the API base URL, the authentication lifecycle (register/claim/revoke), and the list of operations callable without credentials — the agent/CLI signup + login flows and the no-account `POST /send-mail/demo`. Fetch this first to discover how to obtain credentials, then call the authenticated operations with `Authorization: Bearer prim_<key>`.","tags":["Discovery"],"security":[],"responses":{"200":{"description":"Discovery document listing the unauthenticated endpoints and how to obtain credentials.","content":{"application/json":{"schema":{"type":"object","properties":{"service":{"type":"string"},"description":{"type":"string"},"base_url":{"type":"string","format":"uri"},"documentation":{"type":"string","format":"uri"},"openapi":{"type":"string","format":"uri"},"authentication":{"type":"object","properties":{"guide":{"type":"string","format":"uri"},"register_uri":{"type":"string","format":"uri"},"claim_uri":{"type":"string","format":"uri"},"revocation_uri":{"type":"string","format":"uri"}},"required":["guide","register_uri","claim_uri","revocation_uri"]},"public_endpoints":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"path":{"type":"string"},"url":{"type":"string","format":"uri"},"summary":{"type":"string"},"authentication":{"type":"string","const":"none"}},"required":["method","path","url","summary","authentication"]}}},"required":["service","base_url","authentication","public_endpoints"]},"example":{"service":"Primitive — email infrastructure for AI agents","description":"These endpoints are callable without authentication. Use the signup flows to register an agent identity and obtain credentials, or the demo to exercise the API before you have one. Every other endpoint requires `Authorization: Bearer <token>`.","base_url":"https://api.primitive.dev/v1","documentation":"https://docs.primitive.dev/docs","openapi":"https://www.primitive.dev/openapi.json","authentication":{"guide":"https://www.primitive.dev/auth.md","register_uri":"https://api.primitive.dev/v1/agent/signup/start","claim_uri":"https://api.primitive.dev/v1/agent/signup/verify","revocation_uri":"https://www.primitive.dev/oauth/revoke"},"public_endpoints":[{"method":"POST","path":"/agent/signup/start","url":"https://api.primitive.dev/v1/agent/signup/start","summary":"Start agent account signup","authentication":"none"},{"method":"POST","path":"/send-mail/demo","url":"https://api.primitive.dev/v1/send-mail/demo","summary":"Try send-mail without authentication (simulation — no mail is sent)","authentication":"none"}]}}}}}}},"/ask":{"servers":[{"url":"https://www.primitive.dev","description":"Web origin — NLWeb endpoint host (not the v1 API base)."}],"get":{"operationId":"askGet","summary":"Ask about Primitive — NLWeb query (no authentication)","description":"Microsoft NLWeb natural-language query endpoint. Returns structured JSON describing Primitive. **No credentials required.** Pass the question as `?q=...`; send `Accept: text/event-stream` for an SSE stream.","tags":["Discovery"],"security":[],"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":500},"description":"Natural-language question about Primitive."}],"responses":{"200":{"description":"NLWeb result list describing Primitive capabilities.","content":{"application/json":{"schema":{"type":"object","properties":{"_meta":{"type":"object","properties":{"response_type":{"type":"string"},"version":{"type":"string"},"query":{"type":"string"}}},"results":{"type":"array","items":{"type":"object","properties":{"@context":{"type":"string"},"@type":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"description":{"type":"string"}}}}},"required":["_meta","results"]}}}}}},"post":{"operationId":"ask","summary":"Ask about Primitive — NLWeb query (no authentication)","description":"Microsoft NLWeb natural-language query endpoint. POST `{ \"q\": \"...\" }`. **No credentials required.** Set `prefer.streaming: true` or `Accept: text/event-stream` for an SSE stream.","tags":["Discovery"],"security":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"q":{"type":"string","maxLength":500,"description":"Natural-language question about Primitive."},"prefer":{"type":"object","properties":{"streaming":{"type":"boolean"}}}}},"example":{"q":"What does Primitive cost?"}}}},"responses":{"200":{"description":"NLWeb result list describing Primitive capabilities.","content":{"application/json":{"schema":{"type":"object","properties":{"_meta":{"type":"object","properties":{"response_type":{"type":"string"},"version":{"type":"string"},"query":{"type":"string"}}},"results":{"type":"array","items":{"type":"object","properties":{"@context":{"type":"string"},"@type":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"description":{"type":"string"}}}}},"required":["_meta","results"]}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"/health":{"get":{"operationId":"getHealth","summary":"Service health check","description":"Health check for the API. Returns 200 only when every dependency the service cannot work without (today, its database) has been proven reachable within the last few seconds; otherwise 503 with the same payload, `status` of `degraded`, and a `dependencies.checks` entry naming what failed and which category of failure it was. Public, with no authentication required, so agents, uptime monitors, and load balancers can reach it without a credential. Treat any non-200 as the API being unable to serve authenticated requests. The result is cached briefly and shared across callers, so polling it frequently costs the service nothing.","tags":["Service"],"security":[],"responses":{"200":{"description":"The service is healthy: every required dependency was proven reachable.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"status":{"type":"string","enum":["ok"],"description":"`ok` when every required dependency was proven reachable, `degraded` otherwise."},"env":{"type":"string","description":"Deployment environment identifier."},"build_sha":{"type":"string","description":"Git commit SHA of the running build."},"ts":{"type":"string","format":"date-time","description":"Server timestamp (ISO 8601)."},"capabilities":{"type":"object","additionalProperties":false,"description":"Operational rollout capabilities of the running API build.","properties":{"webhook_secret_rotation":{"type":"string","enum":["armed","disabled"],"description":"Whether this core-api deployment is locally armed for webhook-secret rotation. Operators must independently confirm all Function-delivery producers have shipped dual signing before arming it."}},"required":["webhook_secret_rotation"]},"dependencies":{"type":"object","additionalProperties":false,"description":"The verdict on every dependency the API cannot serve requests without. Only dependencies whose absence stops the service working are listed; a dependency that merely degrades a subset of routes is deliberately excluded, so any failure here means the API cannot serve authenticated requests.","properties":{"ok":{"type":"boolean","description":"True only when every check below passed. A check that could not be performed at all counts as a failure."},"checked_at":{"type":"string","format":"date-time","description":"When the underlying probe ran, which may be a few seconds before this response was produced."},"cached":{"type":"boolean","description":"Whether this verdict was served from the short-lived memo rather than freshly probed."},"checks":{"type":"array","description":"One entry per required dependency.","items":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","enum":["database"],"description":"Stable identifier for the dependency."},"ok":{"type":"boolean","description":"Whether the dependency was proven reachable."},"latency_ms":{"type":"integer","description":"How long the probe took, or the timeout budget when it ran out."},"reason":{"type":"string","enum":["timeout","unreachable","auth_failed","not_accepting_queries","unavailable"],"description":"Present only on failure. A stable category to alert on. This endpoint is unauthenticated, so the set is closed and the underlying driver error is never included; the full detail is written to the service log instead. `unavailable` means the failure did not match a known category."},"error":{"type":"string","description":"Present only on failure. A one-line description of `reason`, safe to display."}},"required":["name","ok","latency_ms"]}}},"required":["ok","checked_at","cached","checks"]}},"required":["status","env","build_sha","ts","capabilities","dependencies"]},"example":{"status":"ok","env":"production","build_sha":"0c0f184","ts":"2026-06-10T10:00:00.000Z","capabilities":{"webhook_secret_rotation":"armed"},"dependencies":{"ok":true,"checked_at":"2026-06-10T10:00:00.000Z","cached":false,"checks":[{"name":"database","ok":true,"latency_ms":3}]}}}}},"503":{"$ref":"#/components/responses/HealthDegraded","description":"A dependency the service cannot work without is unreachable, or could not be checked at all. The API is not able to serve authenticated requests; `dependencies.checks` names which one and what the failure was."}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Use `Authorization: Bearer <token>` with an organization API key (`prim_`), OAuth access token (`prim_oat_`), or address-bound agent connection key (`pconn_`). Organization API keys act at member level, except credit code redemption (`POST /credits/redeem`), which uses the current owner or admin role of the user who created the key; OAuth tokens use the authorizing user's current organization role, resolved per request. Agent connection keys permit their documented address-scoped mail and notes operations, exact-address self-disconnect, network discovery when can_view is enabled, and recipient-bound network contact admission. Other operations are denied. Billing and organization administration are owner/admin dashboard actions outside this API, apart from redeeming a credit code."},"DownloadToken":{"type":"apiKey","in":"query","name":"token","description":"Signed download token provided in webhook payloads"}},"parameters":{"AgentNetworkAddress":{"name":"address","in":"path","required":true,"description":"The agent's email address, URL-encoded in the path.","schema":{"type":"string","minLength":3,"maxLength":254}},"AgentNetworkLimit":{"name":"limit","in":"query","required":false,"description":"Maximum number of addresses to return.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"AgentNetworkCursor":{"name":"cursor","in":"query","required":false,"description":"Continue after this address from the previous page.","schema":{"type":"string","minLength":3,"maxLength":254}},"AgentNetworkOwner":{"name":"owner","in":"query","required":false,"description":"Filter by exact owner user ID or case-insensitive owner name substring. The manager roster also matches owner email; peer discovery does not expose or match email.","schema":{"type":"string","minLength":1,"maxLength":100}},"AttachmentPartIndex":{"name":"part_index","in":"path","required":true,"description":"The attachment metadata `part_index`, not its offset in the attachments array","schema":{"type":"integer","format":"int32","minimum":0,"maximum":2147483647}},"ResourceId":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.","schema":{"type":"string","minLength":1,"maxLength":255}},"Cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Pagination cursor from a previous response's `meta.cursor` field.\nFormat: `{ISO-datetime}|{id}`\n"},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Number of results per page"},"MemoryKeyQuery":{"name":"key","in":"query","required":true,"description":"Memory key. Must be at most 512 UTF-8 bytes.","schema":{"type":"string","minLength":1,"maxLength":512}},"MemoryScopeQueryType":{"name":"scope_type","in":"query","required":false,"description":"Explicit scope type. Omit to use automatic scope resolution. Pass\n`function` with `scope_id=<function-id>`, or `org` with no `scope_id`.\n","schema":{"type":"string","enum":["org","function"]}},"MemoryScopeId":{"name":"scope_id","in":"query","required":false,"description":"Function id UUID when `scope_type=function`. Not valid with\n`scope_type=org`.\n","schema":{"type":"string","format":"uuid"}}},"responses":{"AttachmentPart":{"description":"Original attachment bytes; never a JSON envelope or a base64 string","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}},"headers":{"X-Content-SHA256":{"description":"SHA-256 hex digest of the original bytes","schema":{"type":"string","pattern":"^[a-fA-F0-9]{64}$"}},"Content-Disposition":{"description":"Safe attachment disposition with a sanitized filename","schema":{"type":"string"}},"Cache-Control":{"description":"Attachment responses are private and must not be stored by caches","schema":{"type":"string","example":"private, no-store"}}}},"PullContentGone":{"description":"Queued content is no longer available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RequestCanceled":{"description":"Request was canceled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"PullUnavailable":{"description":"Local receiving is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Unauthorized":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"unauthorized","message":"Invalid or missing API key"}}}},"headers":{"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-unauthenticated"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-unauthenticated"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset-unauthenticated"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-unauthenticated"}}},"Forbidden":{"description":"Authenticated caller lacks permission for the operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"forbidden","message":"Insufficient permissions"}}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource not found"}}}}},"ValidationError":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Invalid domain format"}}}}},"PaymentRequired":{"description":"Usage credits are exhausted or payment is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"DeployFailed":{"description":"Function deploy could not be completed; previously deployed code remains live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"deploy_failed","message":"Function deploy failed"}}}}},"RateLimited":{"description":"Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"},"ratelimit-limit":{"$ref":"#/components/headers/ratelimit-limit-429"},"ratelimit-remaining":{"$ref":"#/components/headers/ratelimit-remaining-429"},"ratelimit-reset":{"$ref":"#/components/headers/ratelimit-reset"},"ratelimit-policy":{"$ref":"#/components/headers/ratelimit-policy-429"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded"}}}}},"BadGateway":{"description":"Primitive could not complete the downstream SMTP request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_unreachable","message":"Outbound SMTP service request failed"}}}}},"ServiceUnavailable":{"description":"Primitive is temporarily unable to process the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"outbound_capacity_exhausted","message":"Outbound capacity is temporarily exhausted"}}}}},"InternalError":{"description":"Primitive encountered an internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"internal_error","message":"Internal server error"}}}}},"Conflict":{"description":"The request conflicts with the current state of the resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"conflict","message":"settlement already in progress"}}}}},"UnprocessableEntity":{"description":"The request was well-formed but could not be processed. For Payments\nthis covers a missing payout address, a failed payment verification, a\nspend-policy decline, or an expired challenge; `error.code` distinguishes\nthem.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"payment_declined","message":"payment exceeds the per-payment cap"}}}}},"Deleted":{"description":"Resource deleted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}}}]}}}},"Gone":{"description":"Resource is no longer available.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"not_found","message":"Resource is no longer available"}}}}},"PayloadTooLarge":{"description":"Request body is too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Request body is too large"}}}}},"UnsupportedMediaType":{"description":"Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Content-Type must be application/json"}}}}},"FailedDependency":{"description":"The operation depends on another service or step that failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"deploy_failed","message":"Function deploy failed"}}}}},"GatewayTimeout":{"description":"Primitive timed out while waiting for a downstream operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"internal_error","message":"Downstream operation timed out"}}}}},"HealthDegraded":{"description":"A dependency the service cannot work without is unreachable, or could not be checked at all. The API is not able to serve authenticated requests; `dependencies.checks` names which one and what the failure was.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"status":{"type":"string","enum":["degraded"],"description":"`ok` when every required dependency was proven reachable, `degraded` otherwise."},"env":{"type":"string","description":"Deployment environment identifier."},"build_sha":{"type":"string","description":"Git commit SHA of the running build."},"ts":{"type":"string","format":"date-time","description":"Server timestamp (ISO 8601)."},"capabilities":{"type":"object","additionalProperties":false,"description":"Operational rollout capabilities of the running API build.","properties":{"webhook_secret_rotation":{"type":"string","enum":["armed","disabled"],"description":"Whether this core-api deployment is locally armed for webhook-secret rotation. Operators must independently confirm all Function-delivery producers have shipped dual signing before arming it."}},"required":["webhook_secret_rotation"]},"dependencies":{"type":"object","additionalProperties":false,"description":"The verdict on every dependency the API cannot serve requests without. Only dependencies whose absence stops the service working are listed; a dependency that merely degrades a subset of routes is deliberately excluded, so any failure here means the API cannot serve authenticated requests.","properties":{"ok":{"type":"boolean","description":"True only when every check below passed. A check that could not be performed at all counts as a failure."},"checked_at":{"type":"string","format":"date-time","description":"When the underlying probe ran, which may be a few seconds before this response was produced."},"cached":{"type":"boolean","description":"Whether this verdict was served from the short-lived memo rather than freshly probed."},"checks":{"type":"array","description":"One entry per required dependency.","items":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","enum":["database"],"description":"Stable identifier for the dependency."},"ok":{"type":"boolean","description":"Whether the dependency was proven reachable."},"latency_ms":{"type":"integer","description":"How long the probe took, or the timeout budget when it ran out."},"reason":{"type":"string","enum":["timeout","unreachable","auth_failed","not_accepting_queries","unavailable"],"description":"Present only on failure. A stable category to alert on. This endpoint is unauthenticated, so the set is closed and the underlying driver error is never included; the full detail is written to the service log instead. `unavailable` means the failure did not match a known category."},"error":{"type":"string","description":"Present only on failure. A one-line description of `reason`, safe to display."}},"required":["name","ok","latency_ms"]}}},"required":["ok","checked_at","cached","checks"]}},"required":["status","env","build_sha","ts","capabilities","dependencies"]},"example":{"status":"degraded","env":"production","build_sha":"0c0f184","ts":"2026-06-10T10:00:00.000Z","capabilities":{"webhook_secret_rotation":"armed"},"dependencies":{"ok":false,"checked_at":"2026-06-10T10:00:00.000Z","cached":false,"checks":[{"name":"database","ok":false,"latency_ms":2000,"reason":"unreachable","error":"the database could not be reached over the network"}]}}}}}},"schemas":{"ContactPolicyRuleInput":{"type":"object","additionalProperties":false,"properties":{"pattern":{"type":"string","maxLength":254,"pattern":"^[\\x09-\\x0d\\x20   -     　﻿]*[\\x00-\\x7f]*[\\x09-\\x0d\\x20   -     　﻿]*$","description":"Trim outer whitespace, then require ASCII before lowercase normalization. Exact mailbox or restricted mailbox glob. See operation description."},"effect":{"type":"string","enum":["allow","silence"]}},"required":["pattern","effect"]},"ContactPolicyRule":{"type":"object","additionalProperties":false,"properties":{"pattern":{"type":"string","maxLength":254,"pattern":"^[\\x09-\\x0d\\x20   -     　﻿]*[\\x00-\\x7f]*[\\x09-\\x0d\\x20   -     　﻿]*$","description":"Trim outer whitespace, then require ASCII before lowercase normalization. Exact mailbox or restricted mailbox glob. See operation description."},"effect":{"type":"string","enum":["allow","silence"]},"notify_since":{"type":["string","null"],"format":"date-time"},"notification_generation":{"type":["string","null"],"format":"uuid"}},"required":["pattern","effect","notify_since","notification_generation"]},"ContactPolicy":{"type":"object","additionalProperties":false,"properties":{"rules":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/ContactPolicyRule"}},"contact_request_since":{"type":["string","null"],"format":"date-time"},"contact_request_generation":{"type":["string","null"],"format":"uuid"},"version":{"type":["string","null"],"format":"uuid"},"updated_at":{"type":["string","null"],"format":"date-time"},"allow_contact_requests":{"type":"boolean"}},"required":["rules","contact_request_since","contact_request_generation","version","updated_at","allow_contact_requests"]},"AgentContactPolicyOverride":{"type":"object","additionalProperties":false,"properties":{"rules":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/ContactPolicyRule"}},"contact_request_since":{"type":["string","null"],"format":"date-time"},"contact_request_generation":{"type":["string","null"],"format":"uuid"},"version":{"type":["string","null"],"format":"uuid"},"updated_at":{"type":["string","null"],"format":"date-time"},"allow_contact_requests":{"type":["boolean","null"]}},"required":["rules","contact_request_since","contact_request_generation","version","updated_at","allow_contact_requests"]},"AgentContactPolicy":{"type":"object","additionalProperties":false,"properties":{"agent_address":{"type":"string","format":"email","maxLength":254},"org_policy":{"$ref":"#/components/schemas/ContactPolicy"},"agent_policy":{"$ref":"#/components/schemas/AgentContactPolicyOverride"},"effective_version":{"type":"string","pattern":"^[a-f0-9]{64}$"},"effective_since":{"type":"string","format":"date-time"},"allow_contact_requests":{"type":"boolean"},"contact_request_since":{"type":["string","null"],"format":"date-time"},"contact_request_generation":{"type":["string","null"],"pattern":"^[a-f0-9]{64}$"}},"required":["agent_address","org_policy","agent_policy","effective_version","effective_since","allow_contact_requests","contact_request_since","contact_request_generation"]},"PutContactPolicyRequest":{"oneOf":[{"$ref":"#/components/schemas/PutContactPolicyCreate"},{"$ref":"#/components/schemas/PutContactPolicyReplace"}]},"PutAgentContactPolicyRequest":{"oneOf":[{"$ref":"#/components/schemas/PutAgentContactPolicyCreate"},{"$ref":"#/components/schemas/PutAgentContactPolicyReplace"}]},"PutContactPolicyCreate":{"type":"object","additionalProperties":false,"properties":{"rules":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/ContactPolicyRuleInput"}},"allow_contact_requests":{"type":"boolean"},"if_absent":{"type":"boolean","const":true}},"required":["rules","allow_contact_requests","if_absent"]},"PutContactPolicyReplace":{"type":"object","additionalProperties":false,"properties":{"rules":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/ContactPolicyRuleInput"}},"allow_contact_requests":{"type":"boolean"},"if_version":{"type":"string","format":"uuid"}},"required":["rules","allow_contact_requests","if_version"]},"PutAgentContactPolicyCreate":{"type":"object","additionalProperties":false,"properties":{"rules":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/ContactPolicyRuleInput"}},"allow_contact_requests":{"type":["boolean","null"]},"if_absent":{"type":"boolean","const":true}},"required":["rules","allow_contact_requests","if_absent"]},"PutAgentContactPolicyReplace":{"type":"object","additionalProperties":false,"properties":{"rules":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/ContactPolicyRuleInput"}},"allow_contact_requests":{"type":["boolean","null"]},"if_version":{"type":"string","format":"uuid"}},"required":["rules","allow_contact_requests","if_version"]},"AgentNetwork":{"type":"object","description":"An organization-owned network. The default network cannot be deleted.","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","const":"organization"},"is_default":{"type":"boolean","const":true},"name":{"type":"string"},"can_manage_all":{"type":"boolean","description":"Whether the current requester may manage every membership in this network."}},"required":["id","kind","is_default","name","can_manage_all"]},"AgentNetworkMember":{"type":"object","description":"One address in the default organization network, visible to the current requester.","properties":{"address":{"type":"string","format":"email"},"name":{"type":"string"},"can_view":{"type":"boolean","description":"Whether this agent can read listed peers and initiate network-driven mail wake to listed recipients."},"is_listed":{"type":"boolean","description":"Whether peers can discover and network-wake this agent. Known-address email is separate."},"excluded":{"type":"boolean","description":"Explicit removal from the network; synchronization does not re-add it."},"connected":{"type":"boolean"},"can_manage":{"type":"boolean","description":"Whether the current requester may change visibility for this address."},"last_seen_at":{"type":["string","null"],"format":"date-time","description":"Last recorded activity, not a presence or receiving guarantee."},"ownership_kind":{"type":"string","enum":["personal","shared","legacy_unknown"]},"owner":{"type":["object","null"],"properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"email":{"type":["string","null"],"format":"email"}},"required":["user_id","name","email"]},"presence":{"$ref":"#/components/schemas/AgentPresence"}},"required":["address","name","can_view","is_listed","excluded","connected","can_manage","last_seen_at","ownership_kind","owner"]},"AgentNetworkPeer":{"type":"object","description":"Listed peer profile. The email address is the identity.","properties":{"address":{"type":"string","format":"email"},"name":{"type":"string"},"last_seen_at":{"type":["string","null"],"format":"date-time","description":"Last recorded API activity, not a presence or receiving guarantee."},"ownership_kind":{"type":"string","enum":["personal","shared","legacy_unknown"]},"owner":{"type":["object","null"],"properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}},"required":["user_id","name"]},"presence":{"$ref":"#/components/schemas/AgentPresence"}},"required":["address","name","last_seen_at","ownership_kind","owner"]},"UpdateAgentNetworkMemberInput":{"type":"object","description":"Set one or both independent discovery permissions.","properties":{"can_view":{"type":"boolean"},"is_listed":{"type":"boolean"}},"minProperties":1,"additionalProperties":false},"AgentNetworkContactAdmissionInput":{"type":"object","description":"Check only a received email already stored for the bound recipient. The server verifies its sender against delivery evidence.","properties":{"email_id":{"type":"string","format":"uuid"},"sender_address":{"type":"string","format":"email"}},"required":["email_id","sender_address"],"additionalProperties":false},"AgentNetworkContactAdmission":{"type":"object","description":"Recipient-bound admission for authenticated network mail. Pending means delivery proof is still settling and the same email should be retried.","properties":{"allowed":{"type":"boolean"},"pending":{"type":"boolean","description":"True only while authenticated inbound mail awaits settled delivery evidence, for at most 120 seconds after receipt."},"allowed_since":{"type":["string","null"],"format":"date-time","description":"Earliest received_at eligible under the current membership and connection state."},"member_policy_required":{"type":"boolean","description":"Reserved human sender policy applies, not authorship proof. If true, allowed/pending is final and contact permission cannot bypass it. Check before contact shortcuts."},"sender_relation":{"type":"string","enum":["owner","member"],"description":"Recipient-relative relation derived only from current exact delivered-email proof. Historical sender_member metadata does not establish this relation."}},"required":["allowed","allowed_since","pending","member_policy_required"]},"PublishPolicy":{"type":"string","enum":["owner_only","request","open"],"description":"Who may publish into a registry. owner_only: only the registry owner.\nrequest: anyone may request and the owner approves. open: anyone may\npublish and it lists immediately (no approval step).\n"},"Registry":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"is_public":{"type":"boolean"},"publish_policy":{"$ref":"#/components/schemas/PublishPolicy"}},"required":["id","slug","name","description","is_public","publish_policy"]},"RegistryAgent":{"type":"object","description":"An agent's public directory profile.","properties":{"address":{"type":"string"},"display_name":{"type":"string"},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"handle":{"type":["string","null"],"description":"The registry-scoped name. Null on the global by-address read."},"last_reachable_at":{"type":["string","null"],"format":"date-time","description":"When the agent's address was last confirmed to still route to its endpoint. A freshness signal for ranking listings; null until the first check."}},"required":["address","display_name","title","description","tags","handle","last_reachable_at"]},"RegistryRequest":{"type":"object","description":"A pending publication request, as the registry owner sees it.","properties":{"id":{"type":"string","format":"uuid"},"address":{"type":"string"},"display_name":{"type":"string"},"handle":{"type":["string","null"]},"requested_at":{"type":"string","format":"date-time"}},"required":["id","address","display_name","handle","requested_at"]},"CreateRegistryInput":{"type":"object","properties":{"slug":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$","description":"Lowercase slug, unique across registries."},"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":["string","null"],"maxLength":2000},"is_public":{"type":"boolean"},"publish_policy":{"$ref":"#/components/schemas/PublishPolicy"}},"required":["slug","name"]},"UpdateRegistryInput":{"type":"object","minProperties":1,"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":["string","null"],"maxLength":2000},"is_public":{"type":"boolean"},"publish_policy":{"$ref":"#/components/schemas/PublishPolicy"}}},"DefineAgentInput":{"type":"object","properties":{"address":{"type":"string","description":"The agent's globally unique email address; mail to it must route to an endpoint the account controls."},"endpoint_id":{"type":"string","format":"uuid","description":"Optional. The endpoint the agent runs on. Omit it to resolve the endpoint from the address's routing automatically; supply it to pin a specific endpoint, which is then validated against the address's route."},"display_name":{"type":"string","minLength":1,"maxLength":120},"title":{"type":["string","null"],"maxLength":120},"description":{"type":["string","null"],"maxLength":2000},"tags":{"type":"array","items":{"type":"string","maxLength":40},"maxItems":20}},"required":["address","display_name"]},"PublishAgentInput":{"type":"object","description":"Publish an agent into a registry. When display_name is present the agent identity is defined (create-or-get by address) in the same call before publishing; omit the define fields to publish an already-defined agent.","properties":{"address":{"type":"string"},"handle":{"type":"string","description":"The registry-scoped name to list the agent under."},"display_name":{"type":"string","minLength":1,"maxLength":120,"description":"Present to define the agent identity before publishing (define + publish in one call)."},"endpoint_id":{"type":"string","format":"uuid","description":"Optional, only used when defining. Omit to resolve the endpoint from the address's routing."},"title":{"type":["string","null"],"maxLength":120},"description":{"type":["string","null"],"maxLength":2000},"tags":{"type":"array","items":{"type":"string","maxLength":40},"maxItems":20}},"required":["address","handle"],"dependentRequired":{"endpoint_id":["display_name"],"title":["display_name"],"description":["display_name"],"tags":["display_name"]}},"PublishAgentResult":{"type":"object","properties":{"status":{"type":"string","enum":["approved","requested"],"description":"approved lists immediately; requested pends owner approval."},"handle":{"type":"string"},"idempotent_replay":{"type":"boolean","description":"True when the publish matched an existing identical publication."}},"required":["status","handle","idempotent_replay"]},"DecideRegistryRequestInput":{"type":"object","properties":{"decision":{"type":"string","enum":["approved","rejected"]}},"required":["decision"]},"RegisterPayoutAddressInput":{"type":"object","additionalProperties":false,"properties":{"address":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"The payout address (your signer's own EVM address), 0x-prefixed."},"network":{"type":"string","enum":["base","base-sepolia"],"description":"The chain the address receives on."},"signature":{"type":"string","pattern":"^0x[0-9a-fA-F]+$","description":"A `personal_sign` signature over the org-bound message produced by\nthe SDK helper `buildPayoutRegistrationMessage`. Recovered and\nchecked against `address`; the org id is bound into the signed bytes.\n"},"issued_at":{"type":"string","format":"date-time","description":"ISO-8601 timestamp embedded in the signed message. Must be within a\nshort freshness window (about 10 minutes) of server time.\n"},"label":{"type":"string","maxLength":80,"description":"Optional human-readable label."}},"required":["address","network","signature","issued_at"]},"X402PayoutAddress":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"address":{"type":"string","description":"The checksummed payout address."},"network":{"type":"string","enum":["base","base-sepolia"]},"label":{"type":["string","null"]},"is_default":{"type":"boolean","description":"Exactly one address per (org, network) is the default."},"verified_at":{"type":"string","format":"date-time","description":"When ownership of the address was last proven."},"created_at":{"type":"string","format":"date-time"}},"required":["id","address","network","label","is_default","verified_at"]},"CreateChallengeInput":{"type":"object","additionalProperties":false,"properties":{"amount":{"type":"string","pattern":"^[1-9][0-9]{0,38}$","description":"Amount to collect, in token base units. USDC has 6 decimals, so\n`\"10000\"` is 0.01 USDC.\n"},"network":{"type":"string","enum":["base","base-sepolia"]},"payer_org":{"type":"string","format":"uuid","description":"The org id allowed to pay this challenge (on-net binding). Optional.\n"},"expires_in":{"type":"integer","minimum":60,"maximum":86400,"description":"Seconds until the challenge expires. Defaults to 3600."},"resource":{"type":"string","format":"uri","maxLength":2048,"description":"Optional URL identifying what is being paid for. Defaults to a\nsynthetic `x402:challenge:<id>` identifier.\n"},"description":{"type":"string","maxLength":512,"description":"Optional human-readable description of the payment."}},"required":["amount","network"]},"X402NonceBinding":{"type":"object","description":"The interaction binding the payer hashes into the EIP-3009 nonce\n(`deriveEip3009Nonce`). Pinning the nonce to this binding is what lets an\nx402 payment ride asynchronous transports safely: a replayed challenge\ncan't redirect funds and a signed payment can't settle twice.\n","properties":{"interaction_id":{"type":"string","description":"Interaction id, including its `@domain` part."},"challenge_step_id":{"type":"string","format":"uuid"},"challenge_nonce":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"32 random bytes as 64 lowercase hex chars."}},"required":["interaction_id","challenge_step_id","challenge_nonce"]},"X402PaymentRequirements":{"type":"object","description":"The x402 `PaymentRequirements` the payer signs over. Field names are\nx402's native camelCase, preserved byte-for-byte.\n","properties":{"scheme":{"type":"string","description":"The x402 settlement scheme. Always `exact` for v1.","example":"exact"},"network":{"type":"string","enum":["base","base-sepolia"]},"maxAmountRequired":{"type":"string","description":"Amount in token base units."},"payTo":{"type":"string","description":"The payee's resolved payout address (checksummed)."},"asset":{"type":"string","description":"The token contract address (checksummed). USDC."},"resource":{"type":"string"},"description":{"type":"string"},"maxTimeoutSeconds":{"type":"integer"},"extra":{"type":"object","description":"The token's load-bearing EIP-712 domain params. `name` differs by\nchain (Base mainnet USDC is `USD Coin`, Base Sepolia is `USDC`); a\nwrong value produces a signature the verifier rejects.\n","properties":{"name":{"type":"string"},"version":{"type":"string"}},"required":["name","version"]}},"required":["scheme","network","maxAmountRequired","payTo","asset","extra"]},"X402Challenge":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","settling","settled","failed","expired"]},"network":{"type":"string","enum":["base","base-sepolia"]},"asset":{"type":"string","description":"Token contract address (checksummed)."},"amount":{"type":"string","description":"Amount in token base units."},"pay_to":{"type":"string","description":"The payee's resolved payout address (checksummed)."},"payer_org":{"type":["string","null"],"description":"The org id bound as payer, if one was set at creation."},"resource":{"type":["string","null"]},"description":{"type":["string","null"]},"nonce_binding":{"$ref":"#/components/schemas/X402NonceBinding"},"settle_tx":{"type":["string","null"],"description":"On-chain settlement transaction hash once settled."},"settled_at":{"type":["string","null"],"format":"date-time"},"failure_reason":{"type":["string","null"]},"expires_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"payment_requirements":{"description":"Present on the create response. Hand the whole challenge (including\nthis) to the payer; `getChallenge` omits it (it is for status polling\nby the challenger).\n","allOf":[{"$ref":"#/components/schemas/X402PaymentRequirements"}]}},"required":["id","status","network","asset","amount","pay_to","nonce_binding","expires_at"]},"CreateEmailChallengeInput":{"type":"object","additionalProperties":false,"description":"Issue a payment challenge over an email thread. `from` is your sending\naddress (the funds receiver; ownership is enforced at send, exactly as\nfor outbound mail) and `to` is the payer's address. The `pay_to` payout\nwallet and the token asset are resolved server-side, never taken from\nthe request.\n","properties":{"from":{"type":"string","format":"email","description":"Your sending address (the payee / funds receiver). Must be an\naddress your org is allowed to send from.\n"},"to":{"type":"string","format":"email","description":"The payer's email address the challenge is sent to."},"amount":{"type":"string","pattern":"^[1-9][0-9]{0,38}$","description":"Amount to collect, in token base units (unlike the `charge` CLI\ncommand, which also accepts `--amount-usdc`, this field takes base\nunits only). USDC has 6 decimals, so `\"10000\"` is 0.01 USDC:\nmultiply a human USDC amount by 1,000,000 (0.01 USDC -> `\"10000\"`).\n"},"network":{"type":"string","enum":["base","base-sepolia"]},"expires_in":{"type":"integer","minimum":60,"maximum":86400,"description":"Seconds until the challenge expires. Defaults to 300."},"resource":{"type":"string","format":"uri","maxLength":2048,"description":"Optional URL identifying what is being paid for."},"description":{"type":"string","maxLength":512,"description":"Optional human-readable description of the payment."}},"required":["from","to","amount","network"]},"X402EmailChallengeDetails":{"type":"object","description":"The challenge the payer needs to sign and pay, carried inside an\nemail-native challenge response.\n","properties":{"payment_requirements":{"$ref":"#/components/schemas/X402PaymentRequirements"},"nonce_binding":{"$ref":"#/components/schemas/X402NonceBinding"},"expires_at":{"type":"string","format":"date-time","description":"ISO-8601 expiry of the challenge."}},"required":["payment_requirements","nonce_binding","expires_at"]},"X402EmailChallenge":{"type":"object","description":"The result of issuing an email-native payment challenge. `interaction_id`\nis the real email thread id (`uuid@domain`) the payment is bound to;\n`challenge_id` is the underlying challenge record. Hand the `challenge`\nto the payer, who replies with a signed `payment` interaction step (the\nSDK `payEmailChallenge` helper builds it).\n","properties":{"interaction_id":{"type":"string","description":"The email thread id (`uuid@domain`) the payment is bound to."},"challenge_id":{"type":"string","format":"uuid","description":"The underlying challenge record id."},"challenge":{"$ref":"#/components/schemas/X402EmailChallengeDetails"}},"required":["interaction_id","challenge_id","challenge"]},"X402PaymentPayload":{"type":"object","description":"A signed x402 v1 `PaymentPayload`. The SDK `pay()` helper builds this;\ncallers rarely construct it by hand. Field names are x402-native.\n","properties":{"x402Version":{"type":"integer","const":1},"scheme":{"type":"string","const":"exact"},"network":{"type":"string","enum":["base","base-sepolia"]},"payload":{"type":"object","properties":{"signature":{"type":"string","pattern":"^0x[0-9a-fA-F]+$","description":"The EIP-712 signature over the authorization."},"authorization":{"type":"object","description":"The EIP-3009 `transferWithAuthorization` fields, as strings.","properties":{"from":{"type":"string"},"to":{"type":"string"},"value":{"type":"string"},"validAfter":{"type":"string"},"validBefore":{"type":"string"},"nonce":{"type":"string"}},"required":["from","to","value","validAfter","validBefore","nonce"]}},"required":["signature","authorization"]}},"required":["x402Version","scheme","network","payload"]},"PayChallengeInput":{"type":"object","additionalProperties":false,"properties":{"payment":{"$ref":"#/components/schemas/X402PaymentPayload"}},"required":["payment"]},"X402Receipt":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["settled"]},"settle_tx":{"type":["string","null"],"description":"On-chain settlement transaction hash."}},"required":["id","status","settle_tx"]},"X402SpendPolicy":{"type":"object","description":"The payer's outbound spend policy. Returned with defaults (not paused,\nno caps, any on-net payee) when none is set.\n","properties":{"paused":{"type":"boolean","description":"Kill-switch. When true, all outbound payments are refused."},"max_per_payment":{"type":["string","null"],"description":"Per-payment cap in token base units, or null for no cap."},"max_per_day":{"type":["string","null"],"description":"Rolling-day cap in token base units, or null for no cap."},"allowlist":{"type":["array","null"],"items":{"type":"string","format":"uuid"},"description":"Allowed payee org ids. `null` allows any on-net payee; `[]` denies\nall.\n"}},"required":["paused","max_per_payment","max_per_day","allowlist"]},"UpdateSpendPolicyInput":{"type":"object","additionalProperties":false,"description":"Merge update: only the fields you include change; omit a field to keep\nits current value; send `null` to clear a cap.\n","properties":{"paused":{"type":"boolean"},"max_per_payment":{"type":["string","null"],"pattern":"^[1-9][0-9]{0,38}$"},"max_per_day":{"type":["string","null"],"pattern":"^[1-9][0-9]{0,38}$"},"allowlist":{"type":["array","null"],"maxItems":1000,"items":{"type":"string","format":"uuid"}}}},"X402DeclinedPayment":{"type":"object","description":"A payment the org's spend policy refused.","properties":{"id":{"type":"string","format":"uuid"},"challenge_id":{"type":["string","null"],"format":"uuid","description":"The challenge that was declined, if still present."},"counterparty_org":{"type":["string","null"],"format":"uuid","description":"The payee (challenger) org, when known."},"network":{"type":"string","enum":["base","base-sepolia"]},"amount":{"type":"string","description":"Amount in token base units."},"reason":{"type":"string","description":"Why the payment was declined (cap, allowlist, paused)."},"declined_at":{"type":"string","format":"date-time"}},"required":["id","network","amount","reason","declined_at"]},"SuccessEnvelope":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success","data"]},"ListEnvelope":{"type":"object","properties":{"success":{"type":"boolean","const":true},"meta":{"$ref":"#/components/schemas/PaginationMeta"}},"required":["success","data","meta"]},"PaginationMeta":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of matching records"},"limit":{"type":"integer","description":"Page size used for this request"},"cursor":{"type":["string","null"],"description":"Cursor for the next page, or null if no more results"}},"required":["total","limit","cursor"]},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","const":false},"error":{"type":"object","properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","validation_error","rate_limit_exceeded","internal_error","conflict","mx_conflict","not_scheduled","sent_email_deleted","sent_email_not_settled","sent_email_changed","sent_email_cleanup_failed","connection_not_revoked","attachment_changed","content_discarded","attachment_limit_exceeded","attachment_integrity_failed","attachment_not_ready","attachment_storage_unavailable","outbound_disabled","cannot_send_from_domain","recipient_not_allowed","outbound_key_missing","outbound_unreachable","outbound_key_invalid","outbound_capacity_exhausted","outbound_response_malformed","outbound_relay_failed","discard_not_enabled","inbound_not_repliable","search_timeout","authorization_pending","slow_down","access_denied","expired_token","invalid_device_code","invalid_signup_token","invalid_verification_code","email_delivery_failed","clerk_signup_failed","no_orgs_for_user","org_not_accessible","feature_disabled","memory_conflict","template_not_installable","scaffold_only","invalid_variables","unknown_secrets","missing_secrets","no_inbound_domain","domain_cannot_send","address_taken","route_cap_reached","name_exhausted","developer_usage_credit_exhausted","no_payout_address","ownership_proof_failed","payment_verification_failed","payment_declined","challenge_expired","settlement_failed","pull_unavailable","subscription_conflict","subscription_limit","subscription_disabled","request_aborted","event_content_unavailable","event_preparation_failed","subscription_unavailable","stale_delivery","idempotency_key_required","repeat_unsupported","repeat_recipient_external","repeat_idle_requires_internal_recipient","not_a_repeating_send","repeat_stop_not_allowed","idempotency_key_reused","credit_code_invalid","credit_code_already_redeemed","credit_code_not_eligible","credit_code_balance_cap","rate_limited","service_unavailable","connection_domain_unavailable","connection_address_unavailable","connection_owner_address_invalid","connection_invitation_unavailable","agent_connection_scope_forbidden","address_note_conflict","address_not_controlled","contact_conflict","connection_removed","connection_create_request_conflict","connection_already_claimed","connection_revoked","connection_cleanup_pending","member_address_unavailable","member_address_history_confirmation_required","member_address_immutable","member_address_domain_unavailable","email_admission_unavailable","pull_subscription_requires_agent_connection"]},"message":{"type":"string"},"details":{"type":"object","description":"Optional structured data that callers can inspect to recover\nfrom the error. The fields present depend on `code`. Additional\nkeys may be added over time without a major-version bump.\n","additionalProperties":true,"properties":{"mx_conflict":{"type":"object","description":"Present when `code == mx_conflict`.","required":["provider_name","suggested_subdomain"],"properties":{"provider_name":{"type":"string","description":"Human-readable name of the detected mailbox provider (e.g. \"Google Workspace\")."},"suggested_subdomain":{"type":"string","description":"Subdomain to try instead (e.g. \"mail\" for `mail.example.com`)."}}},"required_entitlements":{"type":"array","items":{"type":"string"},"description":"Entitlements that would allow a denied send when no recipient-scope gate was granted."},"sent_email_id":{"type":"string","description":"ID of the persisted sent-email attempt associated with the error."},"content_hash":{"type":"string","description":"Content hash of the original request on idempotency cache-hit errors."},"client_idempotency_key":{"type":"string","description":"Effective idempotency key associated with the original request."}}},"gates":{"type":"array","items":{"$ref":"#/components/schemas/GateDenial"},"description":"Structured per-gate denial detail for recipient-scope send-mail failures."},"request_id":{"type":"string","description":"Server-issued request identifier for support and tracing."}},"required":["code","message"]}},"required":["success","error"]},"GateDenial":{"type":"object","properties":{"name":{"type":"string","enum":["send_to_confirmed_domains","send_to_known_addresses"],"description":"Public recipient-scope gate name that denied the send."},"reason":{"type":"string","enum":["domain_not_confirmed","recipient_unauthenticated","recipient_not_known"],"description":"Stable machine-readable denial reason."},"message":{"type":"string","description":"Human-readable explanation of the gate denial."},"subject":{"type":"string","description":"Domain or address the gate evaluated."},"fix":{"$ref":"#/components/schemas/GateFix"},"docs_url":{"type":"string","description":"Public docs URL with more context."}},"required":["name","reason","message","subject"]},"GateFix":{"type":"object","properties":{"action":{"type":"string","enum":["confirm_domain","sender_must_fix_authentication","wait_for_inbound"],"description":"Suggested next action for the caller."},"subject":{"type":"string","description":"Entity the action applies to."}},"required":["action","subject"]},"StartCliLoginInput":{"type":"object","additionalProperties":false,"properties":{"device_name":{"type":"string","minLength":1,"maxLength":80,"description":"Human-readable device name shown during browser approval"},"metadata":{"type":"object","additionalProperties":true,"description":"Optional client metadata stored with the login session; serialized JSON must be 2048 bytes or fewer"}}},"CliLoginStartResult":{"type":"object","properties":{"device_code":{"type":"string","description":"Opaque code used by the CLI to poll for approval"},"user_code":{"type":"string","pattern":"^[BCDFGHJKLMNPQRSTVWXZ]{4}-[BCDFGHJKLMNPQRSTVWXZ]{4}$","description":"Short code the user confirms in the browser"},"verification_uri":{"type":"string","description":"Browser URL where the user approves the login"},"verification_uri_complete":{"type":"string","description":"Browser URL with the user code prefilled"},"expires_in":{"type":"integer","description":"Seconds until the login session expires"},"interval":{"type":"integer","description":"Minimum seconds between poll requests"}},"required":["device_code","user_code","verification_uri","verification_uri_complete","expires_in","interval"]},"PollCliLoginInput":{"type":"object","additionalProperties":false,"properties":{"device_code":{"type":"string","minLength":1}},"required":["device_code"]},"CliLoginPollResult":{"type":"object","properties":{"api_key":{"type":"string","description":"Legacy alias for access_token. New CLI builds should persist access_token and refresh_token."},"key_id":{"type":"string","format":"uuid","description":"Legacy alias for oauth_grant_id"},"key_prefix":{"type":"string","description":"Legacy display prefix derived from access_token"},"access_token":{"type":"string","description":"OAuth access token for CLI API authentication"},"refresh_token":{"type":"string","description":"OAuth refresh token used by the CLI to renew access"},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer","description":"Seconds until access_token expires"},"auth_method":{"type":"string","enum":["oauth"]},"oauth_grant_id":{"type":"string","format":"uuid"},"oauth_client_id":{"type":"string"},"org_id":{"type":"string","format":"uuid"},"org_name":{"type":["string","null"]}},"required":["api_key","key_id","key_prefix","access_token","refresh_token","token_type","expires_in","auth_method","oauth_grant_id","oauth_client_id","org_id","org_name"]},"StartCliSignupInput":{"type":"object","additionalProperties":false,"properties":{"email":{"type":"string","format":"email","maxLength":254},"terms_accepted":{"type":"boolean","const":true,"description":"Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy"},"device_name":{"type":"string","minLength":1,"maxLength":80,"description":"Human-readable device name used for the created CLI OAuth grant"},"metadata":{"type":"object","additionalProperties":true,"description":"Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"}},"required":["email","terms_accepted"]},"CliSignupStartResult":{"type":"object","properties":{"signup_token":{"type":"string","description":"Opaque token used to verify or resend the pending CLI signup"},"email":{"type":"string","format":"email"},"expires_in":{"type":"integer","description":"Seconds until the pending signup expires"},"resend_after":{"type":"integer","description":"Minimum seconds before requesting another verification email"},"verification_code_length":{"type":"integer","description":"Number of digits in the emailed verification code"}},"required":["signup_token","email","expires_in","resend_after","verification_code_length"]},"ResendCliSignupVerificationInput":{"type":"object","additionalProperties":false,"properties":{"signup_token":{"type":"string","minLength":1}},"required":["signup_token"]},"CliSignupResendResult":{"type":"object","properties":{"email":{"type":"string","format":"email"},"expires_in":{"type":"integer","description":"Seconds until the pending signup expires"},"resend_after":{"type":"integer","description":"Minimum seconds before requesting another verification email"},"verification_code_length":{"type":"integer","description":"Number of digits in the emailed verification code"}},"required":["email","expires_in","resend_after","verification_code_length"]},"VerifyCliSignupInput":{"type":"object","additionalProperties":false,"properties":{"signup_token":{"type":"string","minLength":1},"verification_code":{"type":"string","minLength":1,"maxLength":32},"password":{"type":"string","minLength":1,"maxLength":1024}},"required":["signup_token","verification_code"]},"CliSignupVerifyResult":{"type":"object","properties":{"api_key":{"type":"string","description":"Legacy alias for access_token. New CLI builds should persist access_token and refresh_token."},"key_id":{"type":"string","format":"uuid","description":"Legacy alias for oauth_grant_id"},"key_prefix":{"type":"string","description":"Legacy display prefix derived from access_token"},"access_token":{"type":"string","description":"OAuth access token for CLI API authentication"},"refresh_token":{"type":"string","description":"OAuth refresh token used by the CLI to renew access"},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer","description":"Seconds until access_token expires"},"auth_method":{"type":"string","enum":["oauth"]},"oauth_grant_id":{"type":"string","format":"uuid"},"oauth_client_id":{"type":"string"},"org_id":{"type":"string","format":"uuid"},"org_name":{"type":["string","null"]}},"required":["api_key","key_id","key_prefix","access_token","refresh_token","token_type","expires_in","auth_method","oauth_grant_id","oauth_client_id","org_id","org_name"]},"StartAgentSignupInput":{"type":"object","additionalProperties":false,"properties":{"email":{"type":"string","format":"email","maxLength":254},"terms_accepted":{"type":"boolean","const":true,"description":"Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy"},"device_name":{"type":"string","minLength":1,"maxLength":80,"description":"Human-readable device name used for the created agent OAuth session"},"metadata":{"type":"object","additionalProperties":true,"description":"Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"}},"required":["email","terms_accepted"]},"AgentSignupStartResult":{"type":"object","properties":{"signup_token":{"type":"string","description":"Opaque token used to verify or resend the pending agent signup"},"email":{"type":"string","format":"email"},"expires_in":{"type":"integer","description":"Seconds until the pending signup expires"},"resend_after":{"type":"integer","description":"Minimum seconds before requesting another verification email"},"verification_code_length":{"type":"integer","description":"Number of digits in the emailed verification code"}},"required":["signup_token","email","expires_in","resend_after","verification_code_length"]},"ResendAgentSignupVerificationInput":{"type":"object","additionalProperties":false,"properties":{"signup_token":{"type":"string","minLength":1}},"required":["signup_token"]},"AgentSignupResendResult":{"type":"object","properties":{"email":{"type":"string","format":"email"},"expires_in":{"type":"integer","description":"Seconds until the pending signup expires"},"resend_after":{"type":"integer","description":"Minimum seconds before requesting another verification email"},"verification_code_length":{"type":"integer","description":"Number of digits in the emailed verification code"}},"required":["email","expires_in","resend_after","verification_code_length"]},"VerifyAgentSignupInput":{"type":"object","additionalProperties":false,"properties":{"signup_token":{"type":"string","minLength":1},"verification_code":{"type":"string","minLength":1,"maxLength":32},"org_id":{"type":"string","format":"uuid","description":"Optional workspace id to target when the verified email already belongs to multiple workspaces"}},"required":["signup_token","verification_code"]},"AgentOrgRef":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}},"required":["id","name"]},"AgentSignupVerifyResult":{"type":"object","properties":{"api_key":{"type":"string","description":"Legacy alias for access_token. New CLI builds should persist access_token and refresh_token."},"key_id":{"type":"string","format":"uuid","description":"Legacy alias for oauth_grant_id"},"key_prefix":{"type":"string","description":"Legacy display prefix derived from access_token"},"access_token":{"type":"string","description":"OAuth access token for CLI API authentication"},"refresh_token":{"type":"string","description":"OAuth refresh token used by the CLI to renew access"},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer","description":"Seconds until access_token expires"},"auth_method":{"type":"string","enum":["oauth"]},"oauth_grant_id":{"type":"string","format":"uuid"},"oauth_client_id":{"type":"string"},"org_id":{"type":"string","format":"uuid"},"org_name":{"type":["string","null"]},"orgs":{"type":"array","items":{"$ref":"#/components/schemas/AgentOrgRef"},"description":"Workspaces available to the verified email. The minted session targets `org_id`."}},"required":["api_key","key_id","key_prefix","access_token","refresh_token","token_type","expires_in","auth_method","oauth_grant_id","oauth_client_id","org_id","org_name","orgs"]},"PlanLimits":{"type":"object","description":"Plan-derived quota limits for an account.","properties":{"storage_mb":{"type":"number"},"send_per_hour":{"type":"number"},"send_per_day":{"type":"number"},"api_per_minute":{"type":"number"},"webhooks_max_global":{"type":["number","null"]},"webhooks_per_domain":{"type":"boolean"},"filters_per_domain":{"type":"boolean"},"spam_thresholds_per_domain":{"type":"boolean"}},"required":["storage_mb","send_per_hour","send_per_day","api_per_minute","webhooks_max_global","webhooks_per_domain","filters_per_domain","spam_thresholds_per_domain"]},"CreateAgentAccountInput":{"type":"object","additionalProperties":false,"properties":{"terms_accepted":{"type":"boolean","enum":[true],"description":"Must be true to accept the Terms of Service and Privacy Policy."},"device_name":{"type":"string","minLength":1,"maxLength":80,"description":"Optional label for the device or agent creating the account."}},"required":["terms_accepted"]},"AgentAccountUpgradeHint":{"type":"object","description":"In-band pointer to the upgrade path for an agent account.","properties":{"plan":{"type":"string","enum":["developer"]},"description":{"type":"string","description":"What upgrading grants, in words meant to be repeated to a user: a higher send cap and the developer plan's default features. It does not unlock sending to arbitrary recipients; the account's recipient rules still apply (see `GET /send-permissions` and `POST /sendability`)."},"claim_path":{"type":"string"}},"required":["plan","description","claim_path"]},"AgentAccountResult":{"type":"object","properties":{"api_key":{"type":"string","description":"One-time API key (prefixed `prim_`). Shown once; store it securely."},"org_id":{"type":"string","format":"uuid"},"address":{"type":["string","null"],"description":"Provisioned managed inbox FQDN, or null if the inbox publish was deferred."},"plan":{"type":"string","enum":["agent"]},"limits":{"$ref":"#/components/schemas/PlanLimits"},"upgrade":{"$ref":"#/components/schemas/AgentAccountUpgradeHint"}},"required":["api_key","org_id","address","plan","limits","upgrade"]},"StartAgentClaimInput":{"type":"object","additionalProperties":false,"properties":{"email":{"type":"string","format":"email","maxLength":254,"description":"Email to confirm. Must not already belong to a Primitive account."}},"required":["email"]},"AgentClaimStartResult":{"type":"object","properties":{"claim_session_id":{"type":"string"},"resend_after_seconds":{"type":"integer"},"expires_in_seconds":{"type":"integer"}},"required":["claim_session_id","resend_after_seconds","expires_in_seconds"]},"VerifyAgentClaimInput":{"type":"object","additionalProperties":false,"properties":{"verification_code":{"type":"string","minLength":1,"maxLength":32,"description":"The verification code emailed by the claim start step."}},"required":["verification_code"]},"AgentClaimResult":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid"},"plan":{"type":"string","enum":["developer"]},"email":{"type":"string","format":"email"},"limits":{"$ref":"#/components/schemas/PlanLimits"}},"required":["org_id","plan","email","limits"]},"CreateAgentClaimLinkInput":{"type":"object","additionalProperties":false,"description":"No fields; an empty object is accepted.","properties":{}},"AgentClaimLinkResult":{"type":"object","properties":{"claim_token":{"type":"string"},"claim_url":{"type":["string","null"],"description":"Browser URL to hand to a human, or null if no web origin is configured."},"expires_in_seconds":{"type":"integer"}},"required":["claim_token","claim_url","expires_in_seconds"]},"CliLogoutInput":{"type":"object","additionalProperties":false,"properties":{"key_id":{"type":"string","format":"uuid","description":"Optional id guard; when provided it must match the authenticated OAuth grant id or API key id"}}},"CliLogoutResult":{"type":"object","properties":{"revoked":{"type":"boolean","description":"True when an OAuth grant was revoked. False for API-key-authenticated legacy logout, which only clears local CLI state."},"key_id":{"type":"string","format":"uuid","description":"API key id for API-key-authenticated legacy logout"},"oauth_grant_id":{"type":"string","format":"uuid","description":"OAuth grant id revoked by OAuth-authenticated logout"}},"required":["revoked"]},"Account":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"plan":{"type":"string"},"limits":{"$ref":"#/components/schemas/PlanLimits"},"entitlements":{"type":"array","items":{"type":"string"},"description":"Granted org entitlement keys (sorted). A headless caller reads its\ncapabilities here - e.g. an emailless agent seeing only\n[\"send_mail\", \"send_to_known_addresses\"] knows it is reply-only.\n"},"managed_inbox_address":{"type":["string","null"],"description":"The managed inbox FQDN to reply as, or null if the org has no managed inbox."},"created_at":{"type":"string","format":"date-time"},"onboarding_completed":{"type":"boolean"},"onboarding_step":{"type":["string","null"]},"stripe_subscription_status":{"type":["string","null"]},"subscription_current_period_end":{"type":["string","null"],"format":"date-time"},"subscription_cancel_at_period_end":{"type":["boolean","null"]},"spam_threshold":{"type":["number","null"],"minimum":0,"maximum":15},"discard_content_on_webhook_confirmed":{"type":"boolean"},"webhook_secret_rotated_at":{"type":["string","null"],"format":"date-time"}},"required":["id","email","plan","limits","entitlements","managed_inbox_address","created_at","discard_content_on_webhook_confirmed"]},"AccountUpdated":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"plan":{"type":"string"},"spam_threshold":{"type":["number","null"],"minimum":0,"maximum":15},"discard_content_on_webhook_confirmed":{"type":"boolean"}},"required":["id","email","plan","discard_content_on_webhook_confirmed"]},"UpdateAccountInput":{"type":"object","additionalProperties":false,"properties":{"spam_threshold":{"type":["number","null"],"minimum":0,"maximum":15,"description":"Global spam score threshold (0-15). Emails scoring above this are rejected. Set to null to disable."},"discard_content_on_webhook_confirmed":{"type":"boolean","description":"Whether to discard email content after the webhook endpoint confirms receipt."}},"minProperties":1},"StorageStats":{"type":"object","properties":{"used_bytes":{"type":"integer","description":"Total storage used in bytes"},"used_kb":{"type":"number","description":"Total storage used in kilobytes (1 decimal)"},"used_mb":{"type":"number","description":"Total storage used in megabytes (2 decimals)"},"quota_mb":{"type":"number","description":"The plan's included storage in megabytes, the same value as included_mb. It is an allowance, not a limit; see limit_mb."},"percentage":{"type":"number","description":"Storage used as a percentage of the included storage (1 decimal). Can exceed 100."},"included_mb":{"type":"number","description":"Storage included with the plan, in megabytes."},"limit_mb":{"type":["number","null"],"description":"Storage in megabytes past which inbound email is refused. Null when inbound email is never refused for storage."},"overage_mb":{"type":"number","description":"Storage past included_mb, in megabytes, counting stored email and the search index."},"search_index_bytes":{"type":"integer","description":"Storage attributed to the semantic search index, in bytes. Counted toward included_mb and overage_mb, never toward limit_mb."},"emails_count":{"type":"integer","description":"Number of stored emails"}},"required":["used_bytes","used_kb","used_mb","quota_mb","percentage","emails_count"]},"WebhookSecret":{"type":"object","properties":{"secret":{"type":"string","description":"The webhook signing secret value"}},"required":["secret"]},"RedeemCreditCodeInput":{"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","pattern":"^\\s*\\S(?:[\\s\\S]{0,254}\\S)?\\s*$","description":"The credit code to redeem. Surrounding whitespace is ignored; the rest must be 1 to 256 characters."}}},"CreditRedemption":{"type":"object","additionalProperties":false,"required":["redemption_id","amount_micros","currency","granted_at","expires_at","label","replayed","message"],"properties":{"redemption_id":{"type":"string","description":"Identifier of the redemption."},"amount_micros":{"type":"string","pattern":"^[0-9]+$","description":"Credit granted, in micros of `currency` (1 USD = 1000000 micros)."},"currency":{"type":"string","description":"Currency of the granted credit, for example `usd`."},"granted_at":{"type":"string","format":"date-time","description":"When the credit was granted."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the granted credit expires, or null when it does not expire."},"label":{"type":["string","null"],"description":"Customer-facing label of the promotion, when it has one."},"replayed":{"type":"boolean","description":"True when this Idempotency-Key already redeemed this code and the original grant is returned. No second grant is made."},"message":{"type":"string","description":"Human-readable summary of the grant, suitable to show as is."}}},"CreditBalance":{"type":"object","additionalProperties":false,"required":["budget"],"properties":{"budget":{"type":["object","null"],"description":"The active agent spending budget an operator funded for top-ups, or null when there is none.","additionalProperties":false,"required":["currency","max_amount_micros","spent_micros","remaining_micros","per_topup_cap_micros","expires_at","status"],"properties":{"currency":{"type":"string","description":"Currency of the budget, for example `usd`."},"max_amount_micros":{"type":"string","pattern":"^[0-9]+$","description":"Total allowance the operator funded, in micros."},"spent_micros":{"type":"string","pattern":"^[0-9]+$","description":"How much of the allowance has been drawn down, in micros."},"remaining_micros":{"type":"string","pattern":"^[0-9]+$","description":"`max_amount_micros - spent_micros`, floored at zero."},"per_topup_cap_micros":{"type":["string","null"],"pattern":"^[0-9]+$","description":"Largest single top-up allowed, in micros, or null for no per-top-up cap."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the allowance expires, or null for no expiry."},"status":{"type":"string","description":"Budget status. An active budget reads `active`."}}},"prepaid_credit":{"type":["object","null"],"description":"Prepaid usage credit (top-ups, redeemed credit codes and granted credit) that can still pay for usage. Null when there is none. Omitted when an exact figure cannot be given right now; the budget is still returned.","additionalProperties":false,"required":["currency","remaining_micros","next_expires_at"],"properties":{"currency":{"type":"string","description":"Currency of the credit, which is the organization billing currency."},"remaining_micros":{"type":"string","pattern":"^[0-9]+$","description":"What the credit can still pay for, in micros."},"next_expires_at":{"type":["string","null"],"format":"date-time","description":"Earliest expiry among credits with a balance, or null when none of them expires."}}}}},"InboxStatus":{"type":"object","additionalProperties":false,"properties":{"ready":{"type":"boolean","description":"True when an active inbound domain and at least one processing route are both ready."},"receiving_ready":{"type":"boolean","description":"True when at least one active verified or managed domain can receive mail."},"processing_ready":{"type":"boolean","description":"True when at least one receiving-ready domain has an enabled webhook or function route."},"summary":{"type":"string"},"next_actions":{"type":"array","items":{"$ref":"#/components/schemas/InboxStatusNextAction"}},"domains":{"type":"array","items":{"$ref":"#/components/schemas/InboxStatusDomain"}},"endpoints":{"type":"object","additionalProperties":false,"properties":{"total":{"type":"integer"},"enabled":{"type":"integer"},"disabled":{"type":"integer"},"fallback_enabled":{"type":"integer"},"domain_scoped_enabled":{"type":"integer"},"http_enabled":{"type":"integer"},"function_enabled":{"type":"integer"}},"required":["total","enabled","disabled","fallback_enabled","domain_scoped_enabled","http_enabled","function_enabled"]},"functions":{"type":"object","additionalProperties":false,"properties":{"total":{"type":"integer"},"deployed":{"type":"integer"},"pending":{"type":"integer"},"failed":{"type":"integer"}},"required":["total","deployed","pending","failed"]},"recent_emails":{"type":"object","description":"Inbound email activity from the last 30 days.","additionalProperties":false,"properties":{"total":{"type":"integer","description":"Number of inbound emails received in the last 30 days."},"latest_received_at":{"type":["string","null"],"format":"date-time","description":"Most recent inbound email received in the last 30 days."}},"required":["total","latest_received_at"]}},"required":["ready","receiving_ready","processing_ready","summary","next_actions","domains","endpoints","functions","recent_emails"]},"InboxStatusNextAction":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string","enum":["add_domain","verify_domain","configure_processing","send_test_email","fix_failed_functions"]},"message":{"type":"string","description":"Human-readable next step."},"command":{"type":"string","description":"Suggested Primitive CLI command when there is an obvious next step."}},"required":["kind","message"]},"InboxStatusDomain":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string"},"domain":{"type":"string"},"verified":{"type":"boolean"},"active":{"type":"boolean"},"managed":{"type":"boolean"},"receiving_ready":{"type":"boolean"},"processing_ready":{"type":"boolean"},"processing_route_count":{"type":"integer"},"endpoint_count":{"type":"integer"},"enabled_endpoint_count":{"type":"integer"},"function_endpoint_count":{"type":"integer"},"email_count":{"type":"integer","description":"Number of inbound emails received for this domain in the last 30 days."},"latest_email_received_at":{"type":["string","null"],"format":"date-time","description":"Most recent inbound email received for this domain in the last 30 days."},"status":{"type":"string","enum":["ready","stored_only","pending_dns","inactive"]}},"required":["id","domain","verified","active","managed","receiving_ready","processing_ready","processing_route_count","endpoint_count","enabled_endpoint_count","function_endpoint_count","email_count","latest_email_received_at","status"]},"InboxStatusEndpointSummary":{"type":"object","additionalProperties":false,"properties":{"total":{"type":"integer"},"enabled":{"type":"integer"},"disabled":{"type":"integer"},"fallback_enabled":{"type":"integer"},"domain_scoped_enabled":{"type":"integer"},"http_enabled":{"type":"integer"},"function_enabled":{"type":"integer"}},"required":["total","enabled","disabled","fallback_enabled","domain_scoped_enabled","http_enabled","function_enabled"]},"InboxStatusFunctionSummary":{"type":"object","additionalProperties":false,"properties":{"total":{"type":"integer"},"deployed":{"type":"integer"},"pending":{"type":"integer"},"failed":{"type":"integer"}},"required":["total","deployed","pending","failed"]},"InboxStatusRecentEmailSummary":{"type":"object","description":"Inbound email activity from the last 30 days.","additionalProperties":false,"properties":{"total":{"type":"integer","description":"Number of inbound emails received in the last 30 days."},"latest_received_at":{"type":["string","null"],"format":"date-time","description":"Most recent inbound email received in the last 30 days."}},"required":["total","latest_received_at"]},"Domain":{"description":"A domain can be either verified or unverified. Verified domains have\n`is_active` and `spam_threshold` fields. Unverified domains have a\n`verification_token` and `dns_records` for DNS setup.\n","oneOf":[{"$ref":"#/components/schemas/VerifiedDomain"},{"$ref":"#/components/schemas/UnverifiedDomain"}]},"VerifiedDomain":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"verified":{"type":"boolean","const":true},"is_active":{"type":"boolean"},"spam_threshold":{"type":["number","null"],"minimum":0,"maximum":15},"verification_token":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"dns_health":{"type":"string","enum":["healthy","degraded","suspended"],"description":"What the scheduled DNS checker last observed for this domain’s ownership and inbound records. Unlike `verified`, which only reports that the domain was verified once, this reflects live state: `degraded` means a check failed and is being retried, `suspended` means the records have been missing long enough that inbound mail is rejected. Platform-managed zones always report `healthy`."},"sign_in_email_notice":{"type":"string","description":"Present on verify and activate responses when people already sign in to Primitive with an address on this domain. Informational: they will be asked to switch their sign-in email to an address on another domain."},"legacy_inbound_mx":{"oneOf":[{"$ref":"#/components/schemas/DomainDnsRecord"},{"type":"null"}],"description":"Set when the last DNS check found this domain’s MX on a legacy Primitive host. Mail is still delivered and `dns_health` is unaffected; publish this record (status `legacy`) in place of the legacy MX. Null otherwise."}},"required":["id","org_id","domain","verified","is_active","created_at","dns_health"]},"DomainDnsRecord":{"type":"object","additionalProperties":false,"properties":{"type":{"type":"string","enum":["MX","TXT"],"description":"DNS record type."},"name":{"type":"string","description":"DNS-provider host/name value relative to the managed root zone."},"fqdn":{"type":"string","description":"Fully-qualified DNS record name."},"value":{"type":"string","description":"Exact value to publish."},"priority":{"type":"integer","description":"MX priority. Present only for MX records."},"ttl":{"type":"integer","description":"Suggested TTL in seconds when the API can provide one."},"required":{"type":"boolean","const":true},"purpose":{"type":"string","enum":["inbound_mx","ownership_verification","spf","dkim","dkim_standby","dmarc","tls_reporting"]},"status":{"type":"string","enum":["pending","found","legacy","missing","incorrect","unresolved"],"description":"`legacy` means the MX routes mail to Primitive through a legacy host: mail is delivered and the domain stays healthy, but the record should be updated to `value`. `unresolved` means the DNS lookup itself was inconclusive."},"message":{"type":"string","description":"Short explanation of why this record is needed."}},"required":["type","name","fqdn","value","required","purpose","status"]},"UnverifiedDomain":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"verified":{"type":"boolean","const":false},"verification_token":{"type":"string","description":"Add this value as a TXT record to verify ownership"},"dns_records":{"type":"array","description":"Exact DNS records to publish for a pending domain claim or verification attempt.","items":{"$ref":"#/components/schemas/DomainDnsRecord"}},"created_at":{"type":"string","format":"date-time"}},"required":["id","org_id","domain","verified","verification_token","created_at"]},"AddDomainInput":{"type":"object","additionalProperties":false,"properties":{"domain":{"type":"string","minLength":1,"maxLength":253,"description":"The domain name to claim (e.g. \"example.com\")"},"confirmed":{"type":"boolean","description":"Set to true to confirm replacing an existing mailbox provider after an mx_conflict response."},"outbound":{"type":"boolean","deprecated":true,"description":"Deprecated and ignored. Outbound DNS is provisioned for every new domain claim."}},"required":["domain"]},"UpdateDomainInput":{"type":"object","additionalProperties":false,"properties":{"is_active":{"type":"boolean","description":"Whether the domain accepts incoming emails"},"spam_threshold":{"type":["number","null"],"minimum":0,"maximum":15,"description":"Per-domain spam threshold override (Pro plan required)"}},"minProperties":1},"DomainVerifyResult":{"oneOf":[{"type":"object","properties":{"verified":{"type":"boolean","const":true},"dns_records":{"type":"array","description":"Exact DNS records to publish for a pending domain claim or verification attempt.","items":{"$ref":"#/components/schemas/DomainDnsRecord"}},"dns_health":{"type":"string","enum":["healthy","degraded","suspended"],"description":"What the scheduled DNS checker last observed for this domain’s ownership and inbound records. Unlike `verified`, which only reports that the domain was verified once, this reflects live state: `degraded` means a check failed and is being retried, `suspended` means the records have been missing long enough that inbound mail is rejected. Platform-managed zones always report `healthy`."},"sign_in_email_notice":{"type":"string","description":"Present on verify and activate responses when people already sign in to Primitive with an address on this domain. Informational: they will be asked to switch their sign-in email to an address on another domain."}},"required":["verified","dns_health"]},{"type":"object","properties":{"verified":{"type":"boolean","const":false},"mxFound":{"type":"boolean","description":"Whether MX records point to Primitive"},"txtFound":{"type":"boolean","description":"Whether the TXT verification record was found"},"spfFound":{"type":"boolean","description":"Whether the SPF record includes Primitive."},"dkimFound":{"type":"boolean","description":"Whether the DKIM public key record was found."},"dmarcFound":{"type":"boolean","description":"Whether the DMARC record was found."},"tlsRptFound":{"type":"boolean","description":"Whether the TLS-RPT record was found."},"dns_records":{"type":"array","description":"Exact DNS records to publish for a pending domain claim or verification attempt.","items":{"$ref":"#/components/schemas/DomainDnsRecord"}},"error":{"type":"string","description":"Human-readable verification failure reason"}},"required":["verified","mxFound","txtFound","error"]}]},"DomainDnsHealthStatus":{"type":"string","enum":["pending","healthy","degraded","suspended"],"description":"Rollup DNS health state. `pending` means never successfully\nchecked, `healthy` means all required records verified,\n`degraded` means a previously-verified record has regressed,\nand `suspended` means the failure persisted long enough that\nthe affected capability was disabled.\n"},"DomainDnsHealthScope":{"type":"object","description":"Health of one DNS scope: `ownership` (verification TXT),\n`inbound` (MX), or `outbound` (SPF, DKIM, DMARC, TLS-RPT).\n","properties":{"scope":{"type":"string","enum":["ownership","inbound","outbound"]},"verified":{"type":"boolean","description":"Whether this scope's required records are currently verified."},"status":{"$ref":"#/components/schemas/DomainDnsHealthStatus"},"checked_at":{"type":"string","format":"date-time","description":"When this scope was last checked."},"next_check_at":{"type":["string","null"],"format":"date-time","description":"Next scheduled background check, or null when none is planned."},"consecutive_failures":{"type":"integer","minimum":0,"description":"Number of consecutive failed checks for this scope."},"records":{"type":"array","description":"The exact records inspected for this scope, each with its own status.","items":{"$ref":"#/components/schemas/DomainDnsRecord"}},"error":{"type":"string","description":"Human-readable failure reason when the scope check errored."}},"required":["scope","verified","status","checked_at","next_check_at","consecutive_failures","records"]},"DomainDnsHealthCheck":{"type":"object","description":"Result of a DNS health check, as returned by\n/domains/{id}/dns/check and recorded as the domain's current\nhealth state. The top-level fields summarize the domain\noverall; `scopes` breaks the same check down per capability.\n","properties":{"domain_id":{"type":"string","format":"uuid"},"key_id":{"type":"string","format":"uuid","description":"Active outbound DKIM key the outbound scope was checked against, when one exists."},"domain":{"type":"string"},"verified":{"type":"boolean","description":"Whether every required scope is currently verified."},"status":{"$ref":"#/components/schemas/DomainDnsHealthStatus"},"checked_at":{"type":"string","format":"date-time"},"next_check_at":{"type":["string","null"],"format":"date-time","description":"Next scheduled background check, or null when none is planned."},"outbound_verified_at":{"type":["string","null"],"format":"date-time","description":"When outbound DNS was first verified, or null if it never has been."},"consecutive_failures":{"type":"integer","minimum":0},"records":{"type":"array","description":"All records inspected across scopes, each with its own status.","items":{"$ref":"#/components/schemas/DomainDnsRecord"}},"scopes":{"type":"object","properties":{"ownership":{"$ref":"#/components/schemas/DomainDnsHealthScope"},"inbound":{"$ref":"#/components/schemas/DomainDnsHealthScope"},"outbound":{"allOf":[{"$ref":"#/components/schemas/DomainDnsHealthScope"}],"description":"Absent when the domain has no active outbound key to check against."}},"required":["ownership","inbound"]},"error":{"type":"string","description":"Human-readable failure reason when the check errored."}},"required":["domain_id","domain","verified","status","checked_at","next_check_at","outbound_verified_at","consecutive_failures","records","scopes"]},"EmailSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"message_id":{"type":["string","null"]},"domain_id":{"type":["string","null"],"format":"uuid"},"org_id":{"type":["string","null"],"format":"uuid"},"status":{"$ref":"#/components/schemas/EmailStatus"},"sender":{"type":"string","description":"SMTP envelope sender (return-path) the inbound mail server\naccepted. For most legitimate mail this equals the bare\naddress in the From header; for mailing lists, bounce\nhandlers, and forwarders it is typically the bounce address\nrather than the human-visible sender.\n\nFor the parsed From-header value (with display name handling\nand a sender-fallback when the header is unparseable), GET\nthe email by id and use `from_email`.\n"},"recipient":{"type":"string"},"subject":{"type":["string","null"]},"domain":{"type":"string"},"spam_score":{"type":["number","null"]},"created_at":{"type":"string","format":"date-time"},"received_at":{"type":"string","format":"date-time"},"raw_size_bytes":{"type":["integer","null"]},"webhook_status":{"$ref":"#/components/schemas/EmailWebhookStatus"},"webhook_attempt_count":{"type":"integer"},"thread_id":{"type":["string","null"],"format":"uuid","description":"Conversation thread this message belongs to. Fetch\n`/threads/{thread_id}` for the full ordered thread. NULL on\nmessages received before threading was enabled.\n"},"presence_control":{"$ref":"#/components/schemas/PresenceControl"},"automation_headers":{"type":["object","null"],"description":"What the message declared about being automated, verbatim:\n`List-Unsubscribe` (RFC 2369/8058), `List-Id` (RFC 2919),\n`Precedence`, and `Auto-Submitted` (RFC 3834). Null or absent when the message\ndeclared none, and on messages received before these headers\nwere captured, so a null value is not evidence that a person\nsent the message.\n","properties":{"list_unsubscribe":{"type":"string"},"list_id":{"type":"string"},"precedence":{"type":"string"},"auto_submitted":{"type":"string"}},"additionalProperties":true},"reply_count":{"type":"integer","minimum":0,"description":"Number of replies recorded against this email: sends whose\n`in_reply_to_email_id` is this email, the same linkage that populates\n`EmailDetail.replies`, excluding sends that never went out (status\n`gate_denied`, `agent_failed`, `canceled`). Queued and scheduled\nreplies count. Under an agent connection, only the replies that agent\ncan see in `EmailDetail.replies`. Unlike `awaiting`, this counts\nreplies to this one email, not to the thread.\n"},"last_replied_at":{"type":["string","null"],"format":"date-time","description":"`created_at` of the latest reply counted in `reply_count`, or\nnull when `reply_count` is 0.\n"},"awaiting":{"type":"string","enum":["you","them"],"description":"Whose turn it is in this email's conversation. A send counts as a\nreply unless its status is `gate_denied`, `agent_failed`, `canceled`\n(queued and scheduled replies count: they are committed to go). For\nan email with a `thread_id`: `them` when the thread's latest counted\noutbound send (by send `created_at`, including any counted reply to\nthis email itself) is at or after the thread's latest inbound message\n(by `received_at`); otherwise `you`. For an email with no\n`thread_id`: `them` when it has at least one counted reply\n(`reply_count > 0` for an API key), otherwise `you`. It is a\nthread-level fact: every email in a thread carries the same value, and\nit reflects sends made by any credential in the organization,\nincluding other agent connections. It changes without a new inbound\nmessage, for example when a reply is sent, a queued reply fails, a\nscheduled reply is canceled, or a message is deleted, and is always\ncurrent when read.\n"},"automated":{"type":"boolean","description":"Whether the message was sent by a machine rather than a person.\nAn inbound email is `automated` when any of these holds, decided once\nwhen it arrives: `null_envelope_sender` (an empty MAIL FROM, a bounce);\n`no_identifiable_sender` (no address in the envelope or From);\n`own_address` (a sender address is exactly one of the addresses the\nmail was delivered to; sharing a domain with the recipient is not\nenough, so a colleague or another agent in the organization is not\nautomated);\n`mailer_daemon` (mailer-daemon@ or postmaster@ on any domain);\n`auto_submitted` (Auto-Submitted with any keyword but `no`, an\nempty one included);\n`precedence` (Precedence bulk, list, junk or auto_reply);\n`list_unsubscribe` (List-Unsubscribe present); `list_id` (List-Id\npresent); `auto_response_suppress` (X-Auto-Response-Suppress asks\nfor no automatic reply: All, OOF or AutoReply);\n`failed_recipients` (X-Failed-Recipients present, a bounce);\n`report` (a delivery, feedback or disposition report). Mail\nPrimitive's own automatic responders decline to answer is always\n`automated`. The header rules only see headers captured at ingest, so\nolder mail may lack them. This is loop and noise protection, not\nsender authentication.\n"},"automated_reasons":{"type":"array","items":{"type":"string"},"description":"Why `automated` is true, in rule order; empty when it is false.\nCurrent values: `null_envelope_sender`, `no_identifiable_sender`,\n`own_address`, `mailer_daemon`, `auto_submitted`, `precedence`,\n`list_unsubscribe`, `list_id`, `auto_response_suppress`,\n`failed_recipients`, `report`. Treat an unfamiliar value as a\nreason added after your client was built.\n"},"repeat":{"type":["object","null"],"description":"Set when Primitive sent this message as part of a repeating send. Resolved from Primitive's own records, never from the message content. Null on every other message and on servers that predate repeating sends.","properties":{"repeat_id":{"type":"string","format":"uuid"},"sequence":{"type":"integer","minimum":1}},"required":["repeat_id","sequence"]},"sender_member":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["address","user_id","name"],"additionalProperties":false},{"type":"null"}]},"fyi":{"type":"boolean","description":"True when the email is an informational signal (an acknowledgement, read, working or typing signal) and nothing else, so it needs no answer, not even another informational reply. Never true for mail from the reader's owner. Older servers omit it.\n"},"interaction_hint":{"type":"string","description":"How to place this email in a timeline without downloading it: `status`, `card`, `none` or `pending`. Not declared as a closed enum so that a value added later does not fail decoding. Decided once by the server from the message's authoritative `interaction.json` part, and only when a passing DKIM signature covering From and Content-Type authenticates the From that wrote it; the `X-Primitive-Interaction` header is never used, so an unsigned or forged message is always `none`. `status`: a pure status signal (`read`, `working`, `typing`, or an `ack` in the strict signal shape), shown as a property of the message it is about rather than as a row. `card`: an interaction that renders as its own timeline item (for example `ack-request`, `x402.payment`, `wake.dispatch`, `primitive.contact`, `repeat.tick`, `repeat.stop`). `none`: ordinary mail, including any interaction kind not listed. `pending`: the email has not finished processing; read it again later. Email processed before the field existed is `none`. Treat an unfamiliar value as `none`. Older servers omit it.\n"},"interaction_kind":{"type":["string","null"],"description":"The `<protocol>/<version>` that `interaction_hint` was decided from (for example `repeat.tick/1` or `read/1`) when `interaction_hint` is `status` or `card`; null otherwise.\n"},"interaction_candidate":{"type":"boolean","description":"UNVERIFIED early signal for the `pending` window: true when the raw message carries an `X-Primitive-Interaction` header or a MIME part named `interaction.json`, recorded before any signature is checked. Use it only to decide, while `interaction_hint` is `pending`, whether to show a placeholder instead of the raw text. It is never evidence of an interaction: anyone can set that header or name a part that way, so it must not grant trust or select an interaction kind. Ignore it once `interaction_hint` is anything other than `pending`.\n"},"relay":{"type":["object","null"],"description":"Set when the message reached Primitive through a Primitive mail relay. Null for other mail. The field may be absent; treat a missing value the same as null.","properties":{"hostname":{"type":"string","description":"The relay hostname the domain's MX record points at."},"via":{"type":"string","description":"How the message arrived. Currently always `mail_relay`. Treat an unfamiliar value as one added after your client was built."}},"required":["hostname","via"]},"muted":{"type":"boolean","description":"True when the reader's address muted this email's thread with `PUT /threads/{id}/mute`. For a connected agent the reader is its own address; for other credentials it is the address the email was delivered to."},"snippet":{"type":["string","null"],"maxLength":200,"description":"A plain-text preview of `body_text` (never `body_html`), computed once when the message is stored: quoted replies (an \"On ... wrote:\" line, an \"Original Message\" separator, a From/Sent header block, `>` lines) and a `-- ` signature are cut off, whitespace is collapsed to single spaces, and the result is at most 200 characters. A message with no text body, or with nothing left once quotes and the signature are cut (a quote-only reply), has no snippet. It is not a summary and may end mid-word; read the full body from the detail endpoint. Null wherever the detail endpoint would not show the body: no text body, content not yet available or discarded, a failed parse, or a sender-blocked rejection."}},"required":["id","status","sender","recipient","domain","created_at","received_at","webhook_attempt_count","reply_count","last_replied_at","awaiting","automated","automated_reasons","snippet"]},"EmailSearchHighlights":{"type":"object","properties":{"subject":{"type":"array","items":{"type":"string"},"description":"Subject snippets with matching terms highlighted."},"body":{"type":"array","items":{"type":"string"},"description":"Body snippets with matching terms highlighted."}},"required":["subject","body"]},"EmailSearchResult":{"allOf":[{"$ref":"#/components/schemas/EmailSummary"},{"type":"object","properties":{"attachment_count":{"type":"integer","description":"Number of parsed attachments on the email."},"from_known_address":{"type":"boolean","description":"Whether the parsed From address is known to this org from prior authenticated inbound mail."},"score":{"type":"number","description":"Relevance score. Present only when sorting by relevance."},"highlights":{"$ref":"#/components/schemas/EmailSearchHighlights"},"thread_id":{"type":["string","null"],"format":"uuid","description":"The conversation thread this email belongs to, usable with `thread_id` on this endpoint and with `/threads/{id}`. Null until the email has been threaded."},"direction":{"type":"string","enum":["inbound","outbound"],"description":"Which side of the conversation the result is on: `inbound` for mail received by the organization, `outbound` for mail it sent. This endpoint currently returns received mail only, so every result is `inbound`."}},"required":["attachment_count","from_known_address","thread_id","direction"]}]},"EmailSearchMeta":{"type":"object","properties":{"total":{"type":["integer","null"],"description":"Total number of matching records, capped when `total_capped` is true. Null when the request set `count=false`."},"total_capped":{"type":"boolean","description":"Whether `total` was capped instead of counted exactly."},"limit":{"type":"integer","description":"Page size used for this request."},"cursor":{"type":["string","null"],"description":"Cursor for the next search page, or null if no more results."},"sort":{"type":"string","enum":["relevance","received_at_desc","received_at_asc"],"description":"Sort mode used for the result page."},"total_unavailable":{"type":"boolean","description":"Present and `true` when a count was requested but did not fit the search time budget. The page in `data` and `meta.cursor` are still returned (the page may itself be partial, see `partial`); only `meta.total` is missing (null). Retry with narrower filters, or use `count=false` and `meta.cursor`."},"facets_unavailable":{"type":"boolean","description":"Present and `true` when facets were requested but did not fit the search time budget. The page in `data` is still returned (it may itself be partial, see `partial`) and the top-level `facets` object is omitted."},"partial":{"type":"boolean","description":"Present and `true` when the page stopped at the search time bound before it filled. This happens when the filters reject most of what the text matches, for example a one-letter `prefix` search with a `from` that matches little or nothing. `data` then holds fewer than `limit` results, possibly none, and `meta.cursor` continues the search after the last email examined. Every result returned is correct and in order; keep following `meta.cursor` until it is null to read the rest. A page shorter than `limit` is therefore not the end of the results: only a null `meta.cursor` is."},"facets_capped":{"type":"boolean","description":"Present and `true` when the text matched more emails than the total cap, so `facets` aggregate only the most recent matches up to that cap, in the same way `total_capped` marks a capped `total`."}},"required":["total","total_capped","limit","cursor","sort"]},"EmailSearchFacetBucket":{"type":"object","properties":{"value":{"type":["string","null"]},"count":{"type":"integer"}},"required":["value","count"]},"EmailSearchFacets":{"type":"object","properties":{"by_sender":{"type":"array","description":"Sender values, ordered by `count` descending, then by `value` ascending in byte order of its UTF-8 encoding, with a null `value` last. Keeps the first 20 values in that order.","items":{"$ref":"#/components/schemas/EmailSearchFacetBucket"}},"by_domain":{"type":"array","description":"Domain values, ordered by `count` descending, then by `value` ascending in byte order of its UTF-8 encoding, with a null `value` last. Keeps the first 20 values in that order.","items":{"$ref":"#/components/schemas/EmailSearchFacetBucket"}},"by_status":{"type":"array","description":"Status values, ordered by `count` descending, then by `value` ascending in byte order of its UTF-8 encoding, with a null `value` last.","items":{"$ref":"#/components/schemas/EmailSearchFacetBucket"}},"has_attachment":{"type":"object","properties":{"true":{"type":"integer"},"false":{"type":"integer"}},"required":["true","false"]}},"required":["by_sender","by_domain","by_status","has_attachment"]},"EmailDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"message_id":{"type":["string","null"]},"domain_id":{"type":["string","null"],"format":"uuid"},"org_id":{"type":["string","null"],"format":"uuid"},"sender":{"type":"string","description":"SMTP envelope sender (return-path) the inbound mail server\naccepted. Same value as `smtp_mail_from`; both fields exist\nso protocol-aware tooling can use whichever name it expects.\n\nFor most legitimate mail this equals `from_email`; for\nmailing lists, bounce handlers, and forwarders it is\ntypically the bounce-handling address rather than the\nhuman-visible sender.\n\n**For the canonical \"who sent this email\" value, use\n`from_email`.**\n"},"recipient":{"type":"string"},"subject":{"type":["string","null"]},"body_text":{"type":["string","null"],"description":"Plain-text body parsed from the inbound MIME, matching the `email.parsed.body_text` field on the webhook payload. Null when the message had no text part or parsing failed."},"body_html":{"type":["string","null"],"description":"HTML body parsed from the inbound MIME, matching the `email.parsed.body_html` field on the webhook payload. Null when the message had no HTML part or parsing failed."},"status":{"$ref":"#/components/schemas/EmailStatus"},"domain":{"type":"string"},"spam_score":{"type":["number","null"]},"raw_size_bytes":{"type":["integer","null"]},"raw_sha256":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"received_at":{"type":"string","format":"date-time"},"rejection_reason":{"type":["string","null"]},"webhook_status":{"$ref":"#/components/schemas/EmailWebhookStatus"},"webhook_attempt_count":{"type":"integer"},"webhook_last_attempt_at":{"type":["string","null"],"format":"date-time"},"webhook_last_status_code":{"type":["integer","null"]},"webhook_last_error":{"type":["string","null"]},"webhook_fired_at":{"type":["string","null"],"format":"date-time"},"smtp_helo":{"type":["string","null"]},"smtp_mail_from":{"type":["string","null"],"description":"SMTP envelope MAIL FROM (return-path), as accepted by the\ninbound mail server. Same value as `sender`; both fields\nexist so protocol-aware tooling can use whichever name it\nexpects.\n\nFor the canonical \"who sent this email\" value (display name\nstripped, From-header preferred), use `from_email`.\n"},"smtp_rcpt_to":{"type":["array","null"],"items":{"type":"string"}},"from_header":{"type":["string","null"],"description":"Raw `From:` header from the message body, including any\ndisplay name (e.g. `\"Alice Example\" <alice@example.com>`).\nUse this when you need the display name for rendering.\n\nFor the bare email address (display name stripped), use\n`from_email`.\n"},"content_discarded_at":{"type":["string","null"],"format":"date-time"},"content_discarded_by_delivery_id":{"type":["string","null"]},"from_email":{"type":"string","description":"Bare email address parsed from the `From:` header, with\ndisplay name stripped (e.g. `alice@example.com`). Falls\nback to `sender` (the SMTP envelope MAIL FROM) when the\n`From:` header cannot be parsed.\n\n**This is the canonical \"who sent this email\" field for\nmost use cases**, including comparing against allowlists,\nrouting replies, or displaying the sender to a user. Use\n`from_header` when you specifically need the display name,\nor `sender`/`smtp_mail_from` when you need the SMTP\nenvelope value (e.g. to follow a bounce).\n"},"to_email":{"type":"string","description":"Parsed to address (same as recipient)"},"from_known_address":{"type":"boolean","description":"True when the inbound's sender address has a matching grant\nin the org's known-send-addresses list. Advisory: a true\nvalue does not by itself guarantee that a reply will be\naccepted by send-mail's gates; the per-send check at send\ntime remains authoritative.\n"},"sender_connected_agent_verified":{"type":"boolean","description":"True only when this inbound message matches a send made with the\ncurrent key bound to its sender address as a connected agent.\nRequires exact sent-message and recipient evidence: a verified\ninternal delivery, or authenticated SMTP delivery matching the\nsent record. Sender headers, organization keys, and network\nvisibility alone cannot make it true. It becomes false if the\nconnection is revoked or its bound key is deleted; it is not a\npermanent historical authorship claim.\n"},"replies":{"type":"array","description":"Sent emails recorded as replies to this inbound, in send\norder (ascending). Populated when a customer's send-mail\nrequest carries an `in_reply_to` Message-ID that matches\nthis inbound's `message_id` in the same org. Includes\nattempts that were gate-denied, so the array reflects every\nrecorded reply attempt regardless of outcome.\n","items":{"$ref":"#/components/schemas/EmailDetailReply"}},"reply_to_sent_email_id":{"type":["string","null"],"format":"uuid","description":"The `sent_emails.id` of the outbound this inbound was a\nreply to, when resolvable. Set at inbound ingest by\nmatching the parsed In-Reply-To (or References, as a\nfallback) against `sent_emails.message_id` in the same\norg. The mirror of `sent_emails.in_reply_to_email_id` for\nthe inbound side of a thread. NULL when the inbound is\nnot a threaded reply to one of your sends, when neither\nheader survived the path through intermediate MTAs, or on\ninbound received before this auto-link landed.\n"},"thread_id":{"type":["string","null"],"format":"uuid","description":"Conversation thread this message belongs to. Inbound and\noutbound messages in the same conversation share a\n`thread_id`; fetch `/threads/{thread_id}` for the full\nordered thread. Assigned at ingest. NULL on messages\nreceived before threading was enabled (until backfilled).\n"},"parsed":{"allOf":[{"$ref":"#/components/schemas/ParsedEmailData"}],"description":"Parsed MIME content (addresses, threading headers,\nattachment metadata), matching the `email.parsed` object\non the webhook payload so one parser handles both the\nwebhook and this endpoint. The top-level `body_text` /\n`body_html` fields above are the same values as\n`parsed.body_text` / `parsed.body_html`, retained for\nbackward compatibility.\n"},"auth":{"allOf":[{"$ref":"#/components/schemas/EmailAuth"}],"description":"SPF / DKIM / DMARC verdicts computed at ingest, matching\nthe `email.auth` object on the webhook payload. Use these\nto decide how much to trust a message before acting on\ninstructions it contains.\n"},"presence_control":{"$ref":"#/components/schemas/PresenceControl"},"automation_headers":{"type":["object","null"],"description":"What the message declared about being automated, verbatim:\n`List-Unsubscribe` (RFC 2369/8058), `List-Id` (RFC 2919),\n`Precedence`, and `Auto-Submitted` (RFC 3834). Null or absent when the message\ndeclared none, and on messages received before these headers\nwere captured, so a null value is not evidence that a person\nsent the message.\n","properties":{"list_unsubscribe":{"type":"string"},"list_id":{"type":"string"},"precedence":{"type":"string"},"auto_submitted":{"type":"string"}},"additionalProperties":true},"reply_count":{"type":"integer","minimum":0,"description":"Number of replies recorded against this email: sends whose\n`in_reply_to_email_id` is this email, the same linkage that populates\n`EmailDetail.replies`, excluding sends that never went out (status\n`gate_denied`, `agent_failed`, `canceled`). Queued and scheduled\nreplies count. Under an agent connection, only the replies that agent\ncan see in `EmailDetail.replies`. Unlike `awaiting`, this counts\nreplies to this one email, not to the thread.\n"},"last_replied_at":{"type":["string","null"],"format":"date-time","description":"`created_at` of the latest reply counted in `reply_count`, or\nnull when `reply_count` is 0.\n"},"awaiting":{"type":"string","enum":["you","them"],"description":"Whose turn it is in this email's conversation. A send counts as a\nreply unless its status is `gate_denied`, `agent_failed`, `canceled`\n(queued and scheduled replies count: they are committed to go). For\nan email with a `thread_id`: `them` when the thread's latest counted\noutbound send (by send `created_at`, including any counted reply to\nthis email itself) is at or after the thread's latest inbound message\n(by `received_at`); otherwise `you`. For an email with no\n`thread_id`: `them` when it has at least one counted reply\n(`reply_count > 0` for an API key), otherwise `you`. It is a\nthread-level fact: every email in a thread carries the same value, and\nit reflects sends made by any credential in the organization,\nincluding other agent connections. It changes without a new inbound\nmessage, for example when a reply is sent, a queued reply fails, a\nscheduled reply is canceled, or a message is deleted, and is always\ncurrent when read.\n"},"automated":{"type":"boolean","description":"Whether the message was sent by a machine rather than a person.\nAn inbound email is `automated` when any of these holds, decided once\nwhen it arrives: `null_envelope_sender` (an empty MAIL FROM, a bounce);\n`no_identifiable_sender` (no address in the envelope or From);\n`own_address` (a sender address is exactly one of the addresses the\nmail was delivered to; sharing a domain with the recipient is not\nenough, so a colleague or another agent in the organization is not\nautomated);\n`mailer_daemon` (mailer-daemon@ or postmaster@ on any domain);\n`auto_submitted` (Auto-Submitted with any keyword but `no`, an\nempty one included);\n`precedence` (Precedence bulk, list, junk or auto_reply);\n`list_unsubscribe` (List-Unsubscribe present); `list_id` (List-Id\npresent); `auto_response_suppress` (X-Auto-Response-Suppress asks\nfor no automatic reply: All, OOF or AutoReply);\n`failed_recipients` (X-Failed-Recipients present, a bounce);\n`report` (a delivery, feedback or disposition report). Mail\nPrimitive's own automatic responders decline to answer is always\n`automated`. The header rules only see headers captured at ingest, so\nolder mail may lack them. This is loop and noise protection, not\nsender authentication.\n"},"automated_reasons":{"type":"array","items":{"type":"string"},"description":"Why `automated` is true, in rule order; empty when it is false.\nCurrent values: `null_envelope_sender`, `no_identifiable_sender`,\n`own_address`, `mailer_daemon`, `auto_submitted`, `precedence`,\n`list_unsubscribe`, `list_id`, `auto_response_suppress`,\n`failed_recipients`, `report`. Treat an unfamiliar value as a\nreason added after your client was built.\n"},"repeat":{"type":["object","null"],"description":"Set when Primitive sent this message as part of a repeating send. Resolved from Primitive's own records, never from the message content. Null on every other message and on servers that predate repeating sends.","properties":{"repeat_id":{"type":"string","format":"uuid"},"sequence":{"type":"integer","minimum":1}},"required":["repeat_id","sequence"]},"sender_member":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["address","user_id","name"],"additionalProperties":false},{"type":"null"}]},"fyi":{"type":"boolean","description":"True when the email is an informational signal (an acknowledgement, read, working or typing signal) and nothing else, so it needs no answer, not even another informational reply. Never true for mail from the reader's owner. Older servers omit it.\n"},"interaction_hint":{"type":"string","description":"How to place this email in a timeline without downloading it: `status`, `card`, `none` or `pending`. Not declared as a closed enum so that a value added later does not fail decoding. Decided once by the server from the message's authoritative `interaction.json` part, and only when a passing DKIM signature covering From and Content-Type authenticates the From that wrote it; the `X-Primitive-Interaction` header is never used, so an unsigned or forged message is always `none`. `status`: a pure status signal (`read`, `working`, `typing`, or an `ack` in the strict signal shape), shown as a property of the message it is about rather than as a row. `card`: an interaction that renders as its own timeline item (for example `ack-request`, `x402.payment`, `wake.dispatch`, `primitive.contact`, `repeat.tick`, `repeat.stop`). `none`: ordinary mail, including any interaction kind not listed. `pending`: the email has not finished processing; read it again later. Email processed before the field existed is `none`. Treat an unfamiliar value as `none`. Older servers omit it.\n"},"interaction_kind":{"type":["string","null"],"description":"The `<protocol>/<version>` that `interaction_hint` was decided from (for example `repeat.tick/1` or `read/1`) when `interaction_hint` is `status` or `card`; null otherwise.\n"},"interaction_candidate":{"type":"boolean","description":"UNVERIFIED early signal for the `pending` window: true when the raw message carries an `X-Primitive-Interaction` header or a MIME part named `interaction.json`, recorded before any signature is checked. Use it only to decide, while `interaction_hint` is `pending`, whether to show a placeholder instead of the raw text. It is never evidence of an interaction: anyone can set that header or name a part that way, so it must not grant trust or select an interaction kind. Ignore it once `interaction_hint` is anything other than `pending`.\n"},"relay":{"type":["object","null"],"description":"Set when the message reached Primitive through a Primitive mail relay. Null for other mail. The field may be absent; treat a missing value the same as null.","properties":{"hostname":{"type":"string","description":"The relay hostname the domain's MX record points at."},"via":{"type":"string","description":"How the message arrived. Currently always `mail_relay`. Treat an unfamiliar value as one added after your client was built."},"delivery":{"type":"array","description":"Per-recipient outcome of the relay forwarding the message to the recipient's mailbox provider. The field may be absent; treat a missing value as no delivery information being available.","items":{"type":"object","properties":{"recipient":{"type":"string","description":"The recipient address the relay forwarded to."},"status":{"type":"string","description":"Outcome of the forward. Currently one of `delivered`, `deferred` or `bounced`. Treat an unfamiliar value as one added after your client was built."},"smtp_code":{"type":["integer","null"],"description":"SMTP reply code from the mailbox provider, when one was received."},"enhanced_status_code":{"type":["string","null"],"description":"Enhanced status code (for example `2.0.0`) from the mailbox provider, when one was received."},"smtp_response":{"type":["string","null"],"description":"SMTP reply text from the mailbox provider, when one was received."},"at":{"type":"string","format":"date-time","description":"When this outcome was recorded."}},"required":["recipient","status","at"]}}},"required":["hostname","via"]},"muted":{"type":"boolean","description":"True when the reader's address muted this email's thread with `PUT /threads/{id}/mute`. For a connected agent the reader is its own address; for other credentials it is the address the email was delivered to."},"collaboration":{"type":"object","additionalProperties":false,"description":"Facts for deciding how to act on this email, computed by the server under your visibility. Read it before acting: when `newer_inbound_count` is above zero, read the newer messages and answer the latest state once. Carries no text the sender wrote.","required":["thread_id","newer_inbound_count","latest_inbound_id","in_thread","sender_relationship","fyi","muted","peer_signal_on_my_last"],"properties":{"thread_id":{"type":["string","null"],"format":"uuid"},"newer_inbound_count":{"type":"integer","minimum":0,"description":"Inbound emails in this thread you can read that arrived after this one. Same count as `GET /threads/{id}?after={this id}`."},"latest_inbound_id":{"type":["string","null"],"format":"uuid","description":"The newest inbound email in this thread you can read; this email's own id when nothing newer arrived."},"in_thread":{"type":"boolean","description":"Your address has sent mail in this thread, not counting status signals."},"sender_relationship":{"type":"string","enum":["owner","org_agent","member","contact","other"],"description":"`owner`: verified member mail from the human who owns this agent connection (for other credentials, any verified organization member). `org_agent`: a connected agent of this organization, proven by its current key (`sender_connected_agent_verified`). `member`: verified member mail from a member of this organization who does not own this agent connection (only an agent connection sees it; for other credentials every member is `owner`). `contact`: an address on your contact list with notifications on. `other`: anyone else. Never derived from a From header alone."},"fyi":{"type":"boolean","description":"True when this email is an informational signal and nothing else: one `interaction.json` part stating an `ack`, `read`, `working` or `typing` signal about an earlier message, plus only the fixed fallback text; or a DKIM-authenticated repeat-stop notice of the same strict shape. It needs no answer, and a busy agent may skip it. Decided when the email is parsed. Never true for mail from the reader's owner. Sent with `fyi: true` on `/send-mail` or `/emails/{id}/reply`."},"muted":{"type":"boolean","description":"True when the reader's address muted this email's thread with `PUT /threads/{id}/mute`. For a connected agent the reader is its own address; for other credentials it is the address the email was delivered to."},"peer_signal_on_my_last":{"type":["object","null"],"additionalProperties":false,"required":["kind","at"],"description":"The latest explicit signal (`read`, `ack` or `working`) that an authenticated recipient of your last message in this thread (not counting your status signals) sent about that message, or null. An expired `working` signal is not reported. Delivery alone never produces one.","properties":{"kind":{"type":"string","enum":["read","ack","working"]},"at":{"type":"string","format":"date-time","description":"When the signal arrived."}}}}},"snippet":{"type":["string","null"],"maxLength":200,"description":"A plain-text preview of `body_text` (never `body_html`), computed once when the message is stored: quoted replies (an \"On ... wrote:\" line, an \"Original Message\" separator, a From/Sent header block, `>` lines) and a `-- ` signature are cut off, whitespace is collapsed to single spaces, and the result is at most 200 characters. A message with no text body, or with nothing left once quotes and the signature are cut (a quote-only reply), has no snippet. It is not a summary and may end mid-word; read the full body from the detail endpoint. Null wherever the detail endpoint would not show the body: no text body, content not yet available or discarded, a failed parse, or a sender-blocked rejection."}},"required":["id","sender","recipient","status","domain","created_at","received_at","webhook_attempt_count","from_email","sender_connected_agent_verified","to_email","replies","parsed","auth","reply_count","last_replied_at","awaiting","automated","automated_reasons","snippet"]},"EmailDetailReply":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Sent-email row id."},"status":{"$ref":"#/components/schemas/SentEmailStatus"},"to_address":{"type":"string","description":"Recipient address as recorded on the sent_emails row."},"subject":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"queue_id":{"type":["string","null"],"description":"Outbound relay queue identifier when available."}},"required":["id","status","to_address","created_at"]},"EmailAddress":{"type":"object","description":"A parsed RFC 5322 address with optional display name.","properties":{"name":{"type":["string","null"],"description":"Display name, when present (e.g. `Alice Example`)."},"address":{"type":"string","description":"Bare email address (e.g. `alice@example.com`)."}},"required":["address"]},"EmailAttachment":{"type":"object","description":"Metadata for one attachment. The bytes are not inline; download\nall attachments for a message as a gzipped tarball via\n`/emails/{id}/attachments.tar.gz`. `sha256` lets you verify a\nspecific part after extraction.\n","properties":{"filename":{"type":["string","null"]},"content_type":{"type":["string","null"]},"size_bytes":{"type":"integer"},"sha256":{"type":["string","null"]},"part_index":{"type":"integer","description":"Zero-based index of this part within the message."}},"required":["size_bytes"]},"ParsedEmailData":{"type":"object","description":"Parsed MIME content for an inbound email. Mirrors the\n`email.parsed` object on the webhook payload so a single parser\nhandles both surfaces. `status` is `complete` when parsing\nsucceeded; on `failed` the body/address/attachment fields are\nabsent and `error` describes why.\n","properties":{"status":{"type":"string","enum":["complete","failed"]},"body_text":{"type":["string","null"],"description":"Plain-text body. Present when `status` is `complete`."},"body_html":{"type":["string","null"],"description":"HTML body. Present when `status` is `complete`."},"reply_to":{"type":["array","null"],"items":{"$ref":"#/components/schemas/EmailAddress"},"description":"Parsed `Reply-To` header addresses."},"cc":{"type":["array","null"],"items":{"$ref":"#/components/schemas/EmailAddress"},"description":"Parsed `Cc` header addresses."},"bcc":{"type":["array","null"],"items":{"$ref":"#/components/schemas/EmailAddress"},"description":"Parsed `Bcc` header addresses (rarely present on inbound)."},"to_addresses":{"type":["array","null"],"items":{"$ref":"#/components/schemas/EmailAddress"},"description":"Parsed `To` header addresses."},"in_reply_to":{"type":["array","null"],"items":{"type":"string"},"description":"Message-IDs from the `In-Reply-To` header."},"references":{"type":["array","null"],"items":{"type":"string"},"description":"Message-IDs from the `References` header."},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/EmailAttachment"},"description":"Attachment metadata. Empty array when none."},"error":{"type":["object","null"],"description":"Present (non-null) only when `status` is `failed`. When\npresent, all three fields are populated, so a consumer can\nbranch on `code` without defensive null checks.\n","properties":{"code":{"type":"string","description":"Stable failure code (e.g. `PARSE_FAILED`)."},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["status"]},"DkimSignature":{"type":"object","description":"One DKIM signature found on the message, with its verdict.","properties":{"domain":{"type":"string"},"selector":{"type":"string"},"result":{"type":"string","description":"Verification result (e.g. `pass`, `fail`, `none`)."},"aligned":{"type":"boolean","description":"Whether the signing domain aligns with the From domain (for DMARC)."},"keyBits":{"type":["integer","null"]},"algo":{"type":["string","null"]}},"required":["domain","selector","result","aligned"]},"EmailAuth":{"type":"object","description":"SPF / DKIM / DMARC verdicts computed at ingest. Mirrors the\n`email.auth` object on the webhook payload. Field names are\ncamelCase to match that payload exactly. For messages received\nbefore auth was recorded, the verdicts default to `none`.\n","properties":{"spf":{"type":"string","description":"SPF result (e.g. `pass`, `fail`, `softfail`, `none`)."},"dmarc":{"type":"string","description":"DMARC result (e.g. `pass`, `fail`, `none`)."},"dmarcPolicy":{"type":["string","null"],"description":"Published DMARC policy (`none`, `quarantine`, `reject`)."},"dmarcFromDomain":{"type":["string","null"],"description":"The From-header domain DMARC was evaluated against."},"dmarcSpfAligned":{"type":"boolean"},"dmarcDkimAligned":{"type":"boolean"},"dmarcSpfStrict":{"type":["boolean","null"]},"dmarcDkimStrict":{"type":["boolean","null"]},"dkimSignatures":{"type":"array","items":{"$ref":"#/components/schemas/DkimSignature"}}},"required":["spf","dmarc","dmarcSpfAligned","dmarcDkimAligned","dkimSignatures"]},"Thread":{"type":"object","description":"A conversation thread: its metadata plus the inbound and\noutbound messages that belong to it, interleaved oldest-first.\nMembership is the stored `thread_id` on each message. Bodies are\nomitted here to keep the thread view lightweight; fetch\n`/emails/{id}` or `/sent-emails/{id}` for a single message's\nfull content.\n","properties":{"id":{"type":"string","format":"uuid"},"subject":{"type":["string","null"],"description":"Normalized subject of the thread (Re/Fwd prefixes stripped)."},"root_message_id":{"type":["string","null"],"description":"Message-ID of the conversation root, when known."},"message_count":{"type":"integer","description":"Total messages in the thread. `messages` is capped (most\nrecent first, then re-sorted oldest-first), so\n`message_count > messages.length` signals truncation.\n"},"first_message_at":{"type":["string","null"],"format":"date-time"},"last_message_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/ThreadMessage"}},"latest_inbound_id":{"type":["string","null"],"format":"uuid","description":"The newest inbound email in the thread that you can read, by arrival time, or null when there is none. Fetch it with `/emails/{id}`."},"latest_outbound_id":{"type":["string","null"],"format":"uuid","description":"The newest sent email in the thread that you can read and that went out or is committed to go, or null. Sends with status `gate_denied`, `agent_failed`, `canceled` never went out and are skipped. Fetch it with `/sent-emails/{id}`."},"newer_inbound_count":{"type":"integer","minimum":0,"description":"Present only when the request names `after`: how many inbound emails you can read in this thread arrived after that message. Not capped."},"newer_inbound":{"type":"array","maxItems":20,"description":"Present only when the request names `after`: the newest 20 of the inbound emails counted by `newer_inbound_count`, oldest first, so the newest is last. Compare the count with the length to tell whether the list is complete.","items":{"type":"object","additionalProperties":false,"required":["id","from","received_at"],"properties":{"id":{"type":"string","format":"uuid"},"from":{"type":"string","description":"Bare address from the From header, or the envelope sender when the header has none."},"received_at":{"type":"string","format":"date-time"}}}}},"required":["id","message_count","created_at","messages","latest_inbound_id","latest_outbound_id"]},"ThreadMessage":{"type":"object","description":"One message in a thread (inbound or outbound).","properties":{"direction":{"type":"string","enum":["inbound","outbound"],"description":"`inbound` for a received email (`/emails/{id}`), `outbound`\nfor a send (`/sent-emails/{id}`). Use it with `id` to fetch\nfull content from the right endpoint.\n"},"id":{"type":"string","format":"uuid"},"message_id":{"type":["string","null"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"subject":{"type":["string","null"]},"status":{"type":["string","null"],"description":"Lifecycle status (an EmailStatus or SentEmailStatus value, per `direction`)."},"timestamp":{"type":["string","null"],"format":"date-time","description":"received_at for inbound, created_at for outbound."},"repeat":{"type":["object","null"],"description":"Set when Primitive sent this message as part of a repeating send. Resolved from Primitive's own records, never from the message content. Null on every other message and on servers that predate repeating sends.","properties":{"repeat_id":{"type":"string","format":"uuid"},"sequence":{"type":"integer","minimum":1}},"required":["repeat_id","sequence"]},"sender_member":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["address","user_id","name"],"additionalProperties":false},{"type":"null"}]},"fyi":{"type":"boolean","description":"Outbound messages only. True when the send was an informational signal (a read, working or typing signal, or an acknowledgement) and nothing else, so it is not a reply that takes part in the conversation. Absent on inbound messages. Older servers omit it.\n"},"interaction_hint":{"type":"string","description":"Outbound messages only. How to place this send in a timeline: `status` (a pure status signal), `card` or `none`. Not declared as a closed enum so that a value added later does not fail decoding; treat an unknown value as `none`. Absent on inbound messages. Older servers omit it.\n"}},"required":["direction","id"]},"Conversation":{"type":"object","description":"The full conversation an inbound email belongs to, as ordered,\nready-to-prompt turns with bodies. Resolves the thread from the\nemail and returns every message oldest-first, so an agent that\nreceived an email can pass `messages` straight to a chat model in\none call.\n","properties":{"thread_id":{"type":["string","null"],"format":"uuid","description":"The thread this email belongs to, or null when the email\nisn't threaded yet (the conversation is then just this one\nmessage).\n"},"subject":{"type":["string","null"],"description":"Normalized thread subject (Re/Fwd prefixes stripped), or the\nemail's own subject when it isn't threaded.\n"},"message_count":{"type":"integer","description":"Total messages in the thread. `messages` is capped, so\n`truncated` is true (and this can exceed `messages.length`)\nwhen older messages were omitted.\n"},"truncated":{"type":"boolean","description":"True when `messages` omits part of the conversation because\nthe thread exceeds the per-call cap. On a window read (`around`, `before` or `after`) it is true when the conversation has more messages on either side of the ones returned, that is `has_more_before` or `has_more_after`."},"messages":{"type":"array","items":{"$ref":"#/components/schemas/ConversationMessage"}},"cursor":{"type":"string","description":"Present only on a `since` read: the position to send as\n`since` on the next read. An opaque string: send it back\nverbatim and do not parse it or build one.\n"},"has_more":{"type":"boolean","description":"Present only on a `since` read: true when this delta stopped at its page size and `cursor` resumes it. Read again with `cursor` until it is false."},"before_cursor":{"type":"string","description":"Present only on a window read (`around`, `before` or `after`). The position of the oldest message returned (or the request's own cursor when none was). Send it as `before` to read older messages. Pass it back unchanged; its format may change."},"after_cursor":{"type":"string","description":"Present only on a window read (`around`, `before` or `after`). The position of the newest message returned (or the request's own cursor when none was). Send it as `after` to read newer messages. Pass it back unchanged; its format may change."},"has_more_before":{"type":"boolean","description":"Present only on a window read (`around`, `before` or `after`). Whether the conversation has messages older than the oldest one returned."},"has_more_after":{"type":"boolean","description":"Present only on a window read (`around`, `before` or `after`). Whether the conversation has messages newer than the newest one returned."},"quoted_chars_removed":{"type":"integer","minimum":0,"description":"Present only with `text=readable`: characters of quoted history removed from the messages in this response, in total."}},"required":["thread_id","messages"]},"ConversationMessage":{"type":"object","description":"One message in the conversation, with its body and a chat role.\n`status` is the delivery status of an outbound message, as on\n`/sent-emails/{id}`, and is present only on a `since` read and\nonly on outbound messages.\n","properties":{"role":{"type":"string","enum":["user","assistant"],"description":"Chat role derived from `direction`: `user` for inbound\n(received) messages, `assistant` for outbound (your own prior\nreplies). Lets `messages` be passed directly to a chat model.\n"},"direction":{"type":"string","enum":["inbound","outbound"],"description":"`inbound` for a received email (`/emails/{id}`), `outbound`\nfor a send (`/sent-emails/{id}`).\n"},"id":{"type":"string","format":"uuid"},"message_id":{"type":["string","null"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"subject":{"type":["string","null"]},"text":{"type":"string","description":"Plain-text body. Empty string when the message has no text\npart or its content was discarded by retention.\n"},"timestamp":{"type":["string","null"],"format":"date-time","description":"received_at for inbound, created_at for outbound."},"status":{"$ref":"#/components/schemas/SentEmailStatus"},"presence_control":{"$ref":"#/components/schemas/PresenceControl"},"repeat":{"type":["object","null"],"description":"Set when Primitive sent this message as part of a repeating send. Resolved from Primitive's own records, never from the message content. Null on every other message and on servers that predate repeating sends.","properties":{"repeat_id":{"type":"string","format":"uuid"},"sequence":{"type":"integer","minimum":1}},"required":["repeat_id","sequence"]},"sender_member":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["address","user_id","name"],"additionalProperties":false},{"type":"null"}]},"fyi":{"type":"boolean","description":"Outbound messages only. True when the send was an informational signal (a read, working or typing signal, or an acknowledgement) and nothing else, so it is not a reply that takes part in the conversation. Absent on inbound messages. Older servers omit it.\n"},"interaction_hint":{"type":"string","description":"Outbound messages only. How to place this send in a timeline: `status` (a pure status signal), `card` or `none`. Not declared as a closed enum so that a value added later does not fail decoding; treat an unknown value as `none`. Absent on inbound messages. Older servers omit it.\n"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/EmailAttachment"},"description":"Attachment metadata for this message, each item as its own detail read reports it: `parsed.attachments` on `/emails/{id}` for inbound, `attachments` on `/sent-emails/{id}` for outbound, in the same order. Empty when there are none or an inbound message has no parsed content. No bytes or download URLs; download a part with `/emails/{id}/attachments/{part_index}` or `/sent-emails/{id}/attachments/{part_index}`."}},"required":["role","direction","id","text"]},"SendMailAttachment":{"type":"object","additionalProperties":false,"properties":{"filename":{"type":"string","minLength":1,"maxLength":255,"description":"Attachment filename. Control characters are rejected."},"content_type":{"type":"string","minLength":1,"maxLength":255,"description":"Optional MIME content type. Control characters are rejected."},"content_base64":{"type":"string","minLength":1,"maxLength":44040192,"description":"Base64-encoded attachment bytes."}},"required":["filename","content_base64"]},"SendMailPayloadRef":{"type":"object","additionalProperties":false,"description":"A reference to an already-uploaded Primitive Payloads object, delivered as an attachment without inlining the bytes - the way to send an attachment larger than the inline cap. Upload the object via /v1/payloads (with a client-held CEK the server never sees), then reference it here.","properties":{"root":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"The 64-char lowercase-hex Merkle root of a finalized payloads object."},"filename":{"type":"string","minLength":1,"maxLength":255,"description":"Attachment filename presented to the recipient."},"content_type":{"type":"string","minLength":1,"maxLength":255,"description":"Optional MIME content type."},"cek":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$","description":"Base64url-encoded (unpadded) content-encryption key the recipient uses to decrypt. Travels with the email; the object store only ever holds ciphertext."}},"required":["root","filename","cek"]},"SendMailInput":{"type":"object","additionalProperties":false,"properties":{"repeat":{"$ref":"#/components/schemas/RepeatInput"},"from":{"type":"string","minLength":3,"maxLength":998,"description":"RFC 5322 From header. The sender domain must be a verified outbound domain for your organization."},"to":{"type":"string","minLength":3,"maxLength":320,"description":"Recipient address. Recipient eligibility depends on your account's outbound entitlements."},"cc":{"description":"Carbon-copy recipients. Either a single address or an array of addresses. Each entry must be a single address; use the array form for multiple recipients. Cc recipients are visible to everyone who receives the message. The combined number of to, cc, and bcc recipients must not exceed 100.","oneOf":[{"type":"string","minLength":1},{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string","minLength":1}}]},"bcc":{"description":"Blind-carbon-copy recipients. Either a single address or an array of addresses. Each entry must be a single address; use the array form for multiple recipients. Bcc recipients receive the message but are not disclosed to the other recipients: no Bcc header is sent. The combined number of to, cc, and bcc recipients must not exceed 100.","oneOf":[{"type":"string","minLength":1},{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string","minLength":1}}]},"subject":{"type":"string","minLength":1,"maxLength":998,"description":"Subject line for the outbound message"},"body_text":{"type":"string","description":"Plain-text message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes."},"body_html":{"type":"string","description":"HTML message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes."},"in_reply_to":{"type":"string","minLength":1,"maxLength":998,"pattern":"^[^\\x00-\\x1F\\x7F]+$","description":"Message-ID of the direct parent email when sending a threaded reply."},"references":{"type":"array","maxItems":100,"description":"Full ordered message-id chain for the thread.","items":{"type":"string","minLength":1,"maxLength":998,"pattern":"^[^\\x00-\\x1F\\x7F]+$"}},"attachments":{"type":"array","maxItems":100,"description":"Inline attachments. Send requests with attachments to https://api.primitive.dev/v1/send-mail. Combined raw decoded attachment bytes must be at most 31457280.","items":{"$ref":"#/components/schemas/SendMailAttachment"}},"payload_attachments":{"type":"array","maxItems":1,"description":"Deliver an already-uploaded Primitive Payloads object as an attachment by reference, without inlining the bytes - the way to send attachments larger than the inline cap. Upload the object via /v1/payloads (client-held CEK), then reference it here. v1 supports at most one.","items":{"$ref":"#/components/schemas/SendMailPayloadRef"}},"wait":{"type":"boolean","description":"When true, wait for the first downstream SMTP delivery outcome before returning."},"wait_timeout_ms":{"type":"integer","minimum":1000,"maximum":30000,"description":"Maximum time to wait for a delivery outcome when wait is true. Defaults to 30000."},"scheduled_at":{"type":"string","format":"date-time","description":"Optional future execution time (ISO 8601). When set, the\nsend is recorded with status `scheduled` and executed at\nthe requested time instead of immediately. Must be in the\nfuture and at most 30 days out. Incompatible with `wait`\n(a scheduled send resolves after this request completes)\nand with `attachments` / `payload_attachments` (not yet\nsupported on scheduled sends). Reschedule via PATCH\n/sent-emails/{id}; cancel via /sent-emails/{id}/cancel.\nAccepts `Z` or a numeric UTC offset\n(`2026-10-09T17:00:00Z`, `2026-10-09T13:00:00-04:00`); an\noffset is converted to UTC, so the stored and echoed value\nis UTC. A time with no zone, or a bare date, is rejected.\n"},"fyi":{"type":"boolean","description":"Send an informational message the receiver need not answer or wake for. It is sent as an `ack` signal (status `received`) about the message in `in_reply_to`, whose note is `body_text` (at most 2000 characters, which may be omitted). Receivers see it as `fyi: true`. Plain text only: it cannot be combined with `body_html`, `attachments`, `payload_attachments` or `scheduled_at`, and on `/send-mail` it requires `in_reply_to`."}},"required":["from","to","subject"]},"EmailStatus":{"type":"string","description":"Lifecycle status of an INBOUND email (a row in the `emails`\ntable). Distinct from `SentEmailStatus`, which describes\nthe OUTBOUND lifecycle (the `sent_emails` table) and uses\na different vocabulary because the lifecycles differ.\nPossible values:\n\n  - `pending`: the row was inserted at ingestion (mx_main)\n    and has not yet completed the spam / filter / auth\n    pipeline. Body and parsed fields are present; webhook\n    delivery is not yet scheduled. Most rows transition out\n    of `pending` within seconds.\n  - `accepted`: the inbound passed the policy gates and is\n    queued for webhook delivery. The `webhook_status` field\n    tracks the separate webhook-delivery lifecycle from\n    this point.\n  - `completed`: terminal success. Webhook delivery\n    attempted and acknowledged by every active endpoint, OR\n    no endpoints are configured, so the row is durably\n    archived.\n  - `rejected`: terminal failure at ingestion (spam, blocked\n    sender, filter rule, malformed). The body and metadata\n    are stored for auditing but no webhook fires and the\n    row is not repliable.\n\nSee also `webhook_status` (separate enum tracking the\nwebhook-delivery state machine) and `SentEmailStatus` (the\noutbound vocabulary).\n","enum":["pending","accepted","completed","rejected"]},"EmailWebhookStatus":{"type":["string","null"],"description":"Webhook-delivery state for an inbound email. Tracks a\nSEPARATE lifecycle from the email's `status` field; the\nsame row carries both. Possible values:\n\n  - `pending`: ingestion is past `pending` (the email itself\n    is `accepted`) but the webhook fan-out has not yet\n    started for this row.\n  - `in_flight`: at least one delivery attempt is in flight.\n  - `fired`: terminal success. Every active endpoint\n    acknowledged the delivery (or accepted it after retries).\n  - `failed`: terminal partial-failure. At least one endpoint\n    exhausted its retry budget; some endpoints may still\n    have succeeded.\n  - `exhausted`: terminal failure. Every endpoint exhausted\n    its retry budget without success.\n  - `null`: no endpoints configured, so no webhook lifecycle\n    applies.\n\nNote that the value `pending` here does NOT mean the email\nis `pending`; it means the email is past ingestion but\nwebhook delivery has not yet begun. Two overlapping uses\nof the word `pending` for distinct lifecycle phases.\n","enum":["pending","in_flight","fired","failed","exhausted",null]},"SentEmailStatus":{"type":"string","description":"Lifecycle status of a sent_emails row. Possible values:\n\n  - `queued`: pre-call INSERT; the outbound agent has not\n    yet replied.\n  - `submitted_to_agent`: agent accepted; `queue_id` is set.\n  - `agent_failed`: agent rejected; `error_code` and\n    `error_message` carry the reason.\n  - `gate_denied`: a recipient-scope gate denied the send;\n    the agent was never called. The `gates` array carries\n    the denial detail. /send-mail returns 403 in this case\n    so callers see the denial synchronously; /sent-emails\n    additionally records the row for historical lookup,\n    which is when this status appears in a listing.\n  - `unknown`: terminal indeterminate; the on-box log\n    poller couldn't classify the receiver's response.\n  - `delivered` / `bounced` / `deferred` / `wait_timeout`:\n    terminal delivery outcomes (see DeliveryStatus).\n  - `scheduled`: created with a future `scheduled_at` and\n    not yet executed; `scheduled_at` carries the pending\n    execution time. Reschedulable via PATCH\n    /sent-emails/{id} and cancelable via\n    /sent-emails/{id}/cancel while in this status.\n  - `canceled`: terminal; a scheduled send canceled before\n    execution. `canceled_at` carries the cancellation time\n    and nothing was dispatched.\n","enum":["queued","submitted_to_agent","agent_failed","gate_denied","unknown","delivered","bounced","deferred","wait_timeout","scheduled","canceled"]},"DeliveryStatus":{"type":"string","description":"Narrower enum covering only the four terminal delivery\noutcomes returned to a synchronous `wait: true` send.\n\nOn the SendMailResult shape, `delivery_status` is always\nequal to `status` whenever both are present (i.e. on\nterminal-state replays and live wait=true responses).\nThe two fields exist so callers that want to type-narrow\non \"this is a delivery outcome\" can pattern-match against\nthe four-value enum without handling the broader\nSentEmailStatus value set (which also covers `queued`,\n`submitted_to_agent`, `agent_failed`, `gate_denied`,\n`unknown`).\n\nOn async-mode and pre-terminal responses, `delivery_status`\nis absent and only `status` is populated. Use `status` if\nyou want a single field that's always present.\n","enum":["delivered","bounced","deferred","wait_timeout"]},"SentEmailSummary":{"type":"object","description":"List-row projection of a sent-email record. Drops\n`body_text` and `body_html` to keep paginated responses\nsmall; fetch /sent-emails/{id} for the full record with\nbodies.\n","properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/SentEmailStatus"},"status_changed_at":{"type":"string","format":"date-time","description":"Timestamp of the most recent status transition.\nPolling clients should treat `status='queued'` AND\n`status_changed_at` older than 5 minutes as\n\"stuck-queued\" (the post-tx UPDATE failed and the\nactual delivery state is recoverable from on-box logs\nvia `queue_id` when populated, or `request_id`).\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"client_idempotency_key":{"type":["string","null"],"description":"Effective idempotency key used for this send. If the\ncaller passed the `Idempotency-Key` header, this is\nthat value; otherwise it's a server-derived hash of\nthe canonical request payload.\n"},"content_hash":{"type":"string","description":"Stable hash of the canonical send payload."},"from_header":{"type":"string","description":"Raw `From:` header as sent on the wire, including any\ndisplay name (e.g. `\"Acme Support\" <agent@acme.test>`).\n"},"from_address":{"type":"string","description":"Bare email address parsed from `from_header`."},"to_header":{"type":"string","description":"Raw `To:` header as sent on the wire, including any\ndisplay name.\n"},"to_address":{"type":"string","description":"Bare email address parsed from `to_header`."},"subject":{"type":"string"},"body_size_bytes":{"type":"integer","description":"Total UTF-8 byte length of `body_text` + `body_html`.\nSurfaced on the list endpoint so callers can see \"this\nrow has a 4MB body\" without fetching it.\n"},"content_discarded_at":{"type":["string","null"],"format":"date-time","description":"Timestamp at which the bodies were discarded by an\nentitlement-driven retention policy. Null when bodies\nare still present. The detail endpoint returns\nnull-valued `body_text`/`body_html` for discarded rows.\n"},"message_id":{"type":["string","null"],"description":"Wire-level Message-ID assigned to the outbound message\n(RFC 5322). Null on rows that never reached signing\n(queued, gate_denied, agent_failed before signing).\n"},"in_reply_to":{"type":["string","null"],"description":"Wire-level In-Reply-To header value, when this send\nwas a reply.\n"},"email_references":{"type":["string","null"],"description":"Wire-level References header value, when this send\nwas a reply.\n"},"in_reply_to_email_id":{"type":["string","null"],"format":"uuid","description":"Reference to the inbound `emails.id` that this send\nreplied to, when known. Populated when the caller used\n/emails/{id}/reply or when /send-mail's `in_reply_to`\nmatched a stored inbound message_id in the same org.\n"},"thread_id":{"type":["string","null"],"format":"uuid","description":"Conversation thread this send belongs to. A reply inherits\nthe thread of the inbound it answers; a fresh send starts a\nnew thread. Fetch `/threads/{thread_id}` for the full\nordered thread (inbound + outbound interleaved). NULL on\ngate-denied sends and on sends created before threading was\nenabled.\n"},"queue_id":{"type":["string","null"],"description":"Message identifier assigned by Primitive's outbound\nrelay once the agent accepts the message. Null on\nqueued, gate_denied, and agent_failed rows.\n"},"smtp_response_code":{"type":["integer","null"],"description":"Receiver's 3-digit SMTP code (e.g. 250, 550, 451).\nPopulated on terminal delivery statuses; may be null\non a deferred where the agent never got an SMTP-level\nresponse (TCP refused, DNS failed, TLS handshake\nfailed). `smtp_response_text` still carries Postfix's\ndescriptive text in those cases.\n"},"smtp_response_text":{"type":["string","null"],"description":"Free-form text portion of the receiver's SMTP\nresponse. The most useful debugging signal on a\n`bounced` or `deferred` row.\n"},"smtp_enhanced_status_code":{"type":["string","null"],"description":"RFC 3463 enhanced status code (e.g. `5.1.1` for \"Bad\ndestination mailbox address\"). Distinct from\n`smtp_response_code`: the basic 3-digit code is coarse\n(550 = \"permanent failure\"), the enhanced code is\nfiner-grained.\n"},"dkim_selector":{"type":["string","null"],"description":"DKIM selector used to sign the outbound message.\nPublic DNS data; useful for diagnosing why a downstream\nverifier rejected the signature.\n"},"dkim_domain":{"type":["string","null"],"description":"DKIM signing domain."},"error_code":{"type":["string","null"],"description":"Stable public error code on `agent_failed` rows. The\nagent's internal codes are remapped to a stable public\ntaxonomy (see `publicAgentError` in the server) so this\nfield is safe to branch on across agent versions.\n"},"error_message":{"type":["string","null"],"description":"Free-form error message accompanying `error_code`."},"gates":{"type":["array","null"],"items":{"$ref":"#/components/schemas/GateDenial"},"description":"Gate-denial detail on `gate_denied` rows. Mirrors the\nsynchronous /send-mail 403 contract so a caller's\nGateDenial handler is the same across live denies and\nhistorical lookups. Null on every other status.\n"},"request_id":{"type":["string","null"],"description":"Server-issued request identifier from the original\n/send-mail call. Surfaced as the `X-Request-Id`\nresponse header on the live send and recorded here\nfor support escalation.\n"},"scheduled_at":{"type":["string","null"],"format":"date-time","description":"Requested execution time for a scheduled send. Kept\nafter execution as the historical schedule; null on\nordinary immediate sends.\n"},"canceled_at":{"type":["string","null"],"format":"date-time","description":"When a scheduled send was canceled. Null unless the row\nreached the `canceled` status.\n"},"presence_control":{"$ref":"#/components/schemas/PresenceControl"},"repeat":{"type":["object","null"],"description":"Set when Primitive sent this message as part of a repeating send. Resolved from Primitive's own records, never from the message content. Null on every other message and on servers that predate repeating sends.","properties":{"repeat_id":{"type":"string","format":"uuid"},"sequence":{"type":"integer","minimum":1}},"required":["repeat_id","sequence"]},"sender_member":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["address","user_id","name"],"additionalProperties":false},{"type":"null"}]},"interaction_hint":{"type":"string","enum":["status","card","none"],"description":"How to place this send in a timeline without downloading it, decided when it was sent from the `interaction.json` part it carried (no authentication check: the sending organization wrote it). Same values as on received email except that a send is never `pending`. Decided once by the server from the message's authoritative `interaction.json` part, and for received mail only when a passing DKIM signature covering From and Content-Type authenticates the From that wrote it; the `X-Primitive-Interaction` header is never used, so an unsigned or forged message is always `none`. `status`: a pure status signal (`read`, `working`, `typing`, or an `ack` that is the strict signal shape), to be shown as a property of the message it is about, not as a row. `card`: an interaction that renders as its own timeline item (`ack-request`, `x402.payment`, `wake.dispatch`, `primitive.contact`, `repeat.tick`, `repeat.stop`, and the reserved `approval` and `schedule.send`). `none`: ordinary mail, including any interaction kind not listed. Treat an unfamiliar value as `none`."},"interaction_kind":{"type":["string","null"],"description":"The `<protocol>/<version>` the hint was decided from when `interaction_hint` is `status` or `card`; null otherwise."},"snippet":{"type":["string","null"],"maxLength":200,"description":"A plain-text preview of `body_text` (never `body_html`), computed once when the message is stored: quoted replies (an \"On ... wrote:\" line, an \"Original Message\" separator, a From/Sent header block, `>` lines) and a `-- ` signature are cut off, whitespace is collapsed to single spaces, and the result is at most 200 characters. A message with no text body, or with nothing left once quotes and the signature are cut (a quote-only reply), has no snippet. It is not a summary and may end mid-word; read the full body from the detail endpoint. Null when the send has no text body or its content was discarded."},"recipient_count":{"type":["integer","null"],"minimum":0,"description":"Every recipient of the send, To, Cc and Bcc together, each address counted once: the size of `to_addresses`, `cc` and `bcc` combined without duplicates. On a mail relay send `bcc` lists every hidden recipient. Null when the request uses an address-bound agent connection key whose address is not the sender, because the count would reveal Bcc recipients."}},"required":["id","status","status_changed_at","created_at","updated_at","content_hash","from_header","from_address","to_header","to_address","subject","body_size_bytes","interaction_hint","interaction_kind","snippet"]},"SemanticSearchField":{"type":"string","enum":["subject","headers","addresses","body"],"description":"A searchable email field."},"SemanticSearchInput":{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":2048,"description":"Free-text query. Required for `semantic` and `hybrid` modes;\noptional for `keyword` mode.\n"},"mode":{"type":"string","enum":["hybrid","semantic","keyword"],"default":"hybrid","description":"Ranking strategy. `keyword` is lexical only, `semantic` is\nembedding-based, `hybrid` blends both.\n"},"corpus":{"type":"array","items":{"type":"string","enum":["inbound","outbound"]},"minItems":1,"maxItems":2,"description":"Which mail to search. Defaults to both received (`inbound`)\nand sent (`outbound`).\n"},"search_in":{"type":"array","items":{"$ref":"#/components/schemas/SemanticSearchField"},"description":"Restrict matching to these fields. Defaults to all."},"exclude":{"type":"array","items":{"$ref":"#/components/schemas/SemanticSearchField"},"description":"Exclude these fields from matching."},"date_from":{"type":"string","format":"date-time","description":"Only include mail at or after this timestamp. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."},"date_to":{"type":"string","format":"date-time","description":"Only include mail at or before this timestamp. An ISO 8601 timestamp with `Z` or a numeric UTC offset (`2026-10-02T00:00:00Z`, `2026-10-02T00:00:00-04:00`). An offset is read as the instant it names. A time with no zone, or a bare date, is rejected."},"include":{"type":"array","items":{"type":"string","enum":["coverage"]},"description":"Opt-in extras. `coverage` adds an index-coverage snapshot to\n`meta`. Matched fields, snippets, and the score breakdown are\nalways returned regardless of this field.\n"},"limit":{"type":"integer","minimum":1,"maximum":100,"default":10,"description":"Maximum number of results to return."},"cursor":{"type":"string","description":"Opaque pagination cursor from a prior response's `meta.cursor`."}}},"SemanticSearchSnippet":{"type":"object","properties":{"field":{"type":"string","description":"The field this excerpt came from."},"text":{"type":"string","description":"Plain-text excerpt centered on the match (no markup)."}},"required":["field","text"]},"SemanticSearchScoreBreakdown":{"type":"object","description":"Additive contributions to `score`. `semantic` and `keyword` are the\nraw signals times the mode's weight (null when not applicable);\nthese plus `field_boost` and `recency` sum to `score` before each\nvalue is independently rounded to 5 decimal places.\n","properties":{"semantic":{"type":["number","null"]},"keyword":{"type":["number","null"]},"field_boost":{"type":"number"},"recency":{"type":"number"}},"required":["semantic","keyword","field_boost","recency"]},"SemanticSearchResult":{"type":"object","properties":{"source_type":{"type":"string","enum":["inbound_email","sent_email"],"description":"Whether this row is a received or sent message."},"id":{"type":"string","description":"Message id. Combine with `api_url` to fetch the full record."},"subject":{"type":["string","null"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"timestamp":{"type":"string","description":"Message timestamp (received_at for inbound, created_at for sent)."},"status":{"type":"string","description":"Lifecycle status of the message."},"score":{"type":"number","description":"Overall relevance score; the `score_breakdown` components account for it."},"semantic_score":{"type":["number","null"],"description":"Raw semantic similarity signal, or null when not applicable."},"keyword_score":{"type":["number","null"],"description":"Raw keyword (lexical) signal, or null when not applicable."},"matched_fields":{"type":"array","items":{"$ref":"#/components/schemas/SemanticSearchField"},"description":"Fields where the query matched."},"snippets":{"type":"array","items":{"$ref":"#/components/schemas/SemanticSearchSnippet"},"description":"Match-centered excerpts, one per matched field."},"score_breakdown":{"$ref":"#/components/schemas/SemanticSearchScoreBreakdown"},"api_url":{"type":["string","null"],"description":"Relative API path to fetch the full message."}},"required":["source_type","id","subject","from","to","timestamp","status","score","semantic_score","keyword_score","matched_fields","snippets","score_breakdown","api_url"]},"SemanticSearchCoverage":{"type":"object","description":"Index-coverage snapshot for the org, returned only when the `coverage` include option is requested.","properties":{"embedded_chunks":{"type":"integer"},"pending_chunks":{"type":"integer"},"skipped_plan_chunks":{"type":"integer"},"skipped_quota_chunks":{"type":"integer"},"unsupported_attachment_chunks":{"type":"integer"},"failed_chunks":{"type":"integer"}},"required":["embedded_chunks","pending_chunks","skipped_plan_chunks","skipped_quota_chunks","unsupported_attachment_chunks","failed_chunks"]},"SemanticSearchMeta":{"type":"object","properties":{"limit":{"type":"integer","description":"Page size used for this request."},"cursor":{"type":["string","null"],"description":"Cursor for the next page, or null if there are no more results."},"mode":{"type":"string","enum":["hybrid","semantic","keyword"],"description":"Ranking mode used for this response."},"coverage":{"oneOf":[{"$ref":"#/components/schemas/SemanticSearchCoverage"},{"type":"null"}],"description":"Index-coverage snapshot, present only when requested via\n`include: [coverage]`; otherwise null.\n"}},"required":["limit","cursor","mode","coverage"]},"SentEmailDetail":{"description":"Full sent-email record, including `body_text` and\n`body_html`. Returned by /sent-emails/{id}.\n","allOf":[{"$ref":"#/components/schemas/SentEmailSummary"},{"type":"object","properties":{"body_text":{"type":["string","null"],"description":"Plain-text body sent on the wire. Null when the\nsend carried only an HTML body, or when bodies have\nbeen discarded post-send (`content_discarded_at`\nset).\n"},"body_html":{"type":["string","null"],"description":"HTML body sent on the wire. Null when the send\ncarried only a plain-text body, or when bodies\nhave been discarded post-send.\n"},"attachments":{"type":"array","description":"Metadata for submitted inline attachments; download availability is reported separately. Metadata may exist when the archive is unavailable, and legacy sends may have no metadata. Offloaded payload files are not included in the inline archive.","items":{"type":"object","description":"Metadata for one submitted inline attachment.","additionalProperties":true,"properties":{"filename":{"type":["string","null"]},"content_type":{"type":"string"},"size_bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"sha256":{"type":"string"},"part_index":{"type":"integer","minimum":0,"maximum":9007199254740991},"tar_path":{"type":"string"}},"required":["filename","content_type","size_bytes","sha256","part_index","tar_path"]}},"attachments_size_bytes":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading."},"attachments_complete":{"type":"boolean","description":"Whether attachments enumerates the complete original submitted attachment set, including zero-byte and inline CID parts. True requires no submitted payload-reference, offloaded, or visibility-filtered attachment to be omitted, retained content, and a published archive when the attachment inventory is nonempty. False or absent provides no completeness proof, including for legacy or discarded content. This field grants no download access and has no interaction semantics."},"attachments_download_available":{"type":"boolean","description":"Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."},"to_addresses":{"type":["array","null"],"items":{"type":"string"},"description":"Every normalized To recipient of the send. Always populated on\nnew sends (a single-recipient send carries a one-element array);\nnull only on records that predate this field. `to_address` keeps\ncarrying the first recipient.\n"},"cc":{"type":["array","null"],"items":{"type":"string"},"description":"Normalized Cc address list. Null when the send had no Cc\nrecipients.\n"},"bcc":{"type":["array","null"],"items":{"type":"string"},"description":"Normalized Bcc address list, surfaced on the sending\norganization's own record. Bcc recipients are never written\ninto the transmitted message. Null when the send had no Bcc\nrecipients. Also null when the request uses an address-bound\nagent connection key whose address is not the sender, because\nBcc is withheld from recipients.\n"},"reply_to":{"type":["array","null"],"items":{"type":"string"},"description":"Normalized Reply-To address list. Null when the send had none.\n"},"tags":{"type":["array","null"],"description":"Customer metadata tags stored verbatim at send time. Null when\nthe send had none. Also null when the request uses an\naddress-bound agent connection key whose address is not the\nsender, because tags are withheld from recipients.\n","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}},"required":["name","value"]}}}},{"type":"object","properties":{"attachments":{"description":"Metadata for submitted inline attachments; download availability is reported separately. Metadata may exist when the archive is unavailable, and legacy sends may have no metadata. Offloaded payload files are not included in the inline archive.","type":"array","items":{"type":"object","properties":{"filename":{"anyOf":[{"type":"string"},{"type":"null"}]},"content_type":{"type":"string"},"size_bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"sha256":{"type":"string"},"part_index":{"type":"integer","minimum":0,"maximum":9007199254740991},"tar_path":{"type":"string"}},"required":["filename","content_type","size_bytes","sha256","part_index","tar_path"],"additionalProperties":{}}},"attachments_size_bytes":{"description":"Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading.","type":"integer","minimum":0,"maximum":9007199254740991},"attachments_download_available":{"description":"Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives.","type":"boolean"},"attachments_complete":{"description":"True only when the returned attachments enumerate the complete original submitted attachment set, including zero-byte and inline CID parts, with retained content and a published archive for nonempty inventory. False when any submitted payload-reference, offloaded, or visibility-filtered attachment is omitted, or completeness is unknown. False or absent does not prove absence of attachments. This field does not grant download access.","type":"boolean"}}}],"properties":{"presence_control":{"$ref":"#/components/schemas/PresenceControl"},"repeat":{"type":["object","null"],"description":"Set when Primitive sent this message as part of a repeating send. Resolved from Primitive's own records, never from the message content. Null on every other message and on servers that predate repeating sends.","properties":{"repeat_id":{"type":"string","format":"uuid"},"sequence":{"type":"integer","minimum":1}},"required":["repeat_id","sequence"]},"sender_member":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"user_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["address","user_id","name"],"additionalProperties":false},{"type":"null"}]}}},"ReplyEmail":{"type":"object","description":"A threaded inbound reply to one of the org's sends, keyed by\nthe inbound email's `reply_to_sent_email_id`. Compact on\npurpose: enough to read the reply and decide what to do\nnext, with `/emails/{id}` available for the fully parsed\ndetail.\n","properties":{"id":{"type":"string","format":"uuid","description":"Inbound email row id; usable with `/emails/{id}`."},"thread_id":{"type":["string","null"],"format":"uuid","description":"Conversation thread id; usable with `/threads/{id}`."},"reply_to_sent_email_id":{"type":["string","null"],"format":"uuid","description":"The sent-email id this inbound message replied to."},"from_email":{"type":"string","description":"Sender of the reply (From header when present, else envelope sender)."},"to_email":{"type":"string","description":"Recipient address the reply arrived at."},"subject":{"type":["string","null"]},"body_text":{"type":["string","null"],"description":"Plain-text body of the reply, when present."},"body_html":{"type":["string","null"],"description":"HTML body of the reply, when present."},"received_at":{"type":["string","null"],"format":"date-time"},"status":{"type":"string","description":"Inbound processing status of the reply row. Typically an\n`EmailStatus` value, but left as an open string here to\nmatch the server contract, which does not narrow it.\n"},"sender_verified":{"type":"boolean","description":"True when the reply’s From address is one of the send’s `to`, `cc` or `bcc` recipients and either it passed DMARC with aligned SPF or DKIM and its domain is the DMARC domain or a subdomain of it, or it is a verified direct delivery from Primitive’s own outbound. The full address is compared, so another mailbox on the same provider domain is never verified."},"sender_matches_recipient":{"type":"boolean","description":"True when the reply’s From address is one of the send’s `to`, `cc` or `bcc` recipients, whether or not it was authenticated. On its own it is not proof of authorship; see `sender_verified`."},"fyi":{"type":"boolean","description":"True when the reply is an acknowledgement that needs no answer. Read, working and typing signals are never returned as the reply."},"auth":{"allOf":[{"$ref":"#/components/schemas/EmailAuth"}],"description":"SPF, DKIM and DMARC verdicts computed at ingest, in the same shape as `EmailDetail.auth`."}},"required":["id","thread_id","reply_to_sent_email_id","from_email","to_email","subject","body_text","body_html","received_at","status"]},"AwaitReplyResult":{"type":"object","description":"Result of `/sent-emails/{id}/reply`. `reply` is null when no\nreply has arrived yet (no-wait call, or the wait timed out).\n","properties":{"sent_email_id":{"type":"string","format":"uuid","description":"The send this lookup was keyed on (echoes the path id)."},"reply":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/ReplyEmail"}]},{"type":"null"}]},"waited":{"type":"boolean","description":"Whether the call ran in long-poll mode (`wait=true`)."},"timed_out":{"type":"boolean","description":"True only when a `wait=true` call elapsed its timeout with no reply."}},"required":["sent_email_id","reply","waited","timed_out"],"additionalProperties":false},"OutboundStatusDomain":{"type":"object","additionalProperties":false,"description":"Per-domain outbound (sending) readiness.","properties":{"id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"status":{"type":"string","enum":["sendable","pending_outbound_dns","pending_ownership","inactive"],"description":"Single actionable state collapsing the sending\nprerequisites: `sendable` (you may send From this domain\nnow), `pending_ownership` (ownership TXT not verified),\n`pending_outbound_dns` (ownership done, SPF/DKIM/DMARC\nnot verified), or `inactive` (domain deactivated;\nre-adding it, not publishing DNS, is the fix).\n"},"ownership_verified":{"type":"boolean"},"outbound_verified":{"type":"boolean","description":"Whether the domain has an active outbound key with verified outbound DNS."}},"required":["id","domain","status","ownership_verified","outbound_verified"]},"OutboundStatus":{"type":"object","additionalProperties":false,"description":"Outbound sending readiness for the caller's org, the outbound\nmirror of InboxStatus.\n","properties":{"ready":{"type":"boolean","description":"True when at least one domain is sendable right now."},"summary":{"type":"string","description":"Short human-readable status summary."},"sendable_domains":{"type":"array","items":{"type":"string"},"description":"Flat, sorted list of From-domains the org may send from\nright now. The same set echoed in a\n`cannot_send_from_domain` error's `details.valid_senders`,\nso recovery from that error and orientation here agree.\n"},"domains":{"type":"array","items":{"$ref":"#/components/schemas/OutboundStatusDomain"}},"next_actions":{"type":"array","description":"Concrete remediation steps for domains that are not yet\nsendable. The server contract leaves entries open-ended;\nin practice each carries a `kind`, a human-readable\n`message`, and, when there is an obvious next step, a\nsuggested CLI `command`.\n","items":{"type":"object","additionalProperties":true,"properties":{"kind":{"type":"string"},"message":{"type":"string"},"command":{"type":"string"}}}}},"required":["ready","summary","sendable_domains","domains","next_actions"]},"SentEmailRescheduleInput":{"type":"object","additionalProperties":false,"properties":{"scheduled_at":{"type":"string","format":"date-time","description":"New execution time (ISO 8601). Must be in the future and\nat most 30 days out, the same bounds as the create-time\nfield on /send-mail. Accepts `Z` or a numeric UTC offset\n(`2026-10-09T17:00:00Z`, `2026-10-09T13:00:00-04:00`); an\noffset is converted to UTC. A time with no zone, or a bare\ndate, is rejected.\n"}},"required":["scheduled_at"]},"ReplyInput":{"type":"object","additionalProperties":false,"description":"Body shape for `/emails/{id}/reply`. Intentionally narrow:\nrecipients (`to`), subject, and threading headers\n(`in_reply_to`, `references`) are derived server-side from\nthe inbound row referenced by the path id and are rejected by\n`additionalProperties` if passed (returns 400).\n\n`from` IS allowed because of legitimate use cases (display-name\naddition, replying from a different verified outbound address,\nmulti-team triage). Send-mail's per-send `canSendFrom` gate\nvalidates the from-domain regardless, so the override carries\nno extra privilege.\n","properties":{"body_text":{"type":"string","description":"Plain-text reply body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes (same cap as send-mail)."},"body_html":{"type":"string","description":"HTML reply body. At least one of body_text or body_html is required."},"from":{"type":"string","minLength":3,"maxLength":998,"description":"Optional override for the reply's From header. Defaults to\nthe inbound's recipient. Use to add a display name (`\"Acme\nSupport\" <agent@company.com>`) or to reply from a different\nverified outbound address (e.g. multi-team routing where\nsupport@ triages to billing@). The from-domain must be a\nverified outbound domain for your org, same as send-mail.\n"},"wait":{"type":"boolean","description":"When true, wait for the first downstream SMTP delivery outcome before returning, mirroring the send-mail `wait` semantics."},"reply_all":{"type":"boolean","description":"Reply to everyone on the email. To is the sender (or\nReply-To) as in a plain reply; every other To and Cc address\nof the email becomes Cc, minus the replying address. Bcc is\nnever read, so a blind copy stays blind. Every send rule\napplies to each recipient exactly as for a direct send with\ncc. Defaults to false.\n"},"attachments":{"type":"array","maxItems":100,"description":"Inline attachments for this reply. Use https://api.primitive.dev/v1 for replies with attachments. Combined raw decoded attachment bytes must be at most 31457280.","items":{"$ref":"#/components/schemas/SendMailAttachment"}},"repeat":{"$ref":"#/components/schemas/RepeatInput"},"fyi":{"type":"boolean","description":"Send an informational message the receiver need not answer or wake for. It is sent as an `ack` signal (status `received`) about the message in `in_reply_to`, whose note is `body_text` (at most 2000 characters, which may be omitted). Receivers see it as `fyi: true`. Plain text only: it cannot be combined with `body_html`, `attachments`, `payload_attachments` or `scheduled_at`, and on `/send-mail` it requires `in_reply_to`."}}},"RepeatInput":{"type":"object","additionalProperties":false,"description":"Repeat this send every `every_minutes` in the same thread. The first\nmessage goes out like any send; each later one is a scheduled send\nthat replies to the previous one, so quotas, gates and cancellation\napply as usual. Requires exactly one `to` recipient and no cc, bcc,\nattachments or `fyi` (422 `repeat_unsupported`). The recipient must\nbe an address of your own organization unless the organization is\nentitled to repeat to external recipients (403\n`repeat_recipient_external`). `only_if_recipient_idle_minutes` needs\na recipient in your organization (422\n`repeat_idle_requires_internal_recipient`).\n","properties":{"every_minutes":{"type":"integer","minimum":1,"maximum":10080},"only_if_recipient_idle_minutes":{"type":"integer","minimum":1,"maximum":10080,"description":"Skip a repeat while the recipient has sent mail within this many minutes."},"stoppable_by_recipient":{"type":"boolean","default":true,"description":"Let the recipient stop the repeat."},"max_sends":{"type":"integer","minimum":2,"maximum":10000,"description":"Total messages, including the first."},"until":{"type":"string","format":"date-time","description":"No repeat is sent after this time. Must be in the future."}},"required":["every_minutes"]},"RepeatingSendStatus":{"type":"string","enum":["active","paused","stopped_by_recipient","canceled","completed"]},"RepeatingSend":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"from_address":{"type":"string"},"to_address":{"type":"string"},"subject":{"type":"string"},"body_text":{"type":["string","null"]},"body_html":{"type":["string","null"]},"every_minutes":{"type":"integer"},"only_if_recipient_idle_minutes":{"type":["integer","null"]},"stoppable_by_recipient":{"type":"boolean"},"max_sends":{"type":["integer","null"]},"until":{"type":["string","null"],"format":"date-time"},"status":{"$ref":"#/components/schemas/RepeatingSendStatus"},"next_run_at":{"type":["string","null"],"format":"date-time"},"sent_count":{"type":"integer","minimum":0},"last_sent_at":{"type":["string","null"],"format":"date-time"},"last_sent_email_id":{"type":["string","null"],"format":"uuid"},"root_message_id":{"type":["string","null"]},"stopped_at":{"type":["string","null"],"format":"date-time"},"stop_reason":{"type":["string","null"],"description":"Reason the recipient gave when it stopped the repeat. Recipient-written, untrusted text."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","org_id","from_address","to_address","subject","body_text","body_html","every_minutes","only_if_recipient_idle_minutes","stoppable_by_recipient","max_sends","until","status","next_run_at","sent_count","last_sent_at","last_sent_email_id","root_message_id","stopped_at","stop_reason","created_at","updated_at"]},"UpdateRepeatingSendRequest":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"status":{"type":"string","enum":["active","paused","canceled"]},"every_minutes":{"type":"integer","minimum":1,"maximum":10080},"only_if_recipient_idle_minutes":{"type":["integer","null"],"minimum":1,"maximum":10080},"stoppable_by_recipient":{"type":"boolean"},"max_sends":{"type":["integer","null"],"minimum":2,"maximum":10000},"until":{"type":["string","null"],"format":"date-time"},"body_text":{"type":"string","minLength":1},"subject":{"type":"string","minLength":1}}},"RepeatStopRequest":{"type":"object","additionalProperties":false,"properties":{"reason":{"type":"string","minLength":1,"maxLength":280,"description":"Short reason shown to the sender."}}},"RepeatStopResult":{"type":"object","additionalProperties":false,"properties":{"repeat_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["stopped_by_recipient"]},"stopped_at":{"type":"string","format":"date-time"},"stop_reason":{"type":["string","null"]},"reply_sent_email_id":{"type":["string","null"],"format":"uuid","description":"The repeat.stop/1 reply that told the sender, or null if it could not be sent."}},"required":["repeat_id","status","stopped_at","stop_reason","reply_sent_email_id"]},"SendMailResult":{"type":"object","properties":{"repeat_id":{"type":"string","format":"uuid","description":"Present when the request carried `repeat`. Manage it under `/repeating-sends/{id}`."},"id":{"type":"string","description":"Persisted sent-email attempt ID."},"status":{"$ref":"#/components/schemas/SentEmailStatus"},"from":{"type":"string","description":"Bare from-address actually written on the wire. Echoed\non every success branch so callers can confirm what\nwent out, particularly useful for the /emails/{id}/reply\npath where `from` is server-derived from the inbound's\nrecipient when the caller doesn't override.\n\nFor sends where the caller passed a from-header that\nincluded a display name (e.g. `\"Acme Support\" <support@acme.test>`),\nthis field is the parsed bare address (`support@acme.test`).\nThe display name was sent on the wire intact; this field\njust makes the address easy to compare against allowlists.\n"},"queue_id":{"type":["string","null"],"description":"Message identifier assigned by Primitive's OUTBOUND relay\n(the box that signs your mail and submits it to the\nreceiving MTA). NOT the receiver's queue id.\n\nThe receiver may also report its own queue id in\n`smtp_response_text` (e.g. `\"250 2.0.0 Ok: queued as\n99D111927CDA\"` from a Postfix receiver). Those two ids\nrefer to different mail systems and are NOT comparable.\nTreat `queue_id` as Primitive-internal and the\nreceiver's id as remote-system-internal.\n\nNull on rows that never reached the relay (queued,\ngate_denied, agent_failed before signing).\n"},"accepted":{"type":"array","items":{"type":"string"},"description":"Recipient addresses accepted by the relay."},"rejected":{"type":"array","items":{"type":"string"},"description":"Recipient addresses rejected by the relay."},"client_idempotency_key":{"type":"string","description":"Effective idempotency key used for this send."},"request_id":{"type":"string","description":"Server-issued request identifier for support and tracing."},"content_hash":{"type":"string","description":"Stable hash of the canonical send payload."},"delivery_status":{"$ref":"#/components/schemas/DeliveryStatus"},"smtp_response_code":{"type":["integer","null"],"description":"SMTP response code from the first downstream delivery outcome when wait is true."},"smtp_response_text":{"type":"string","description":"SMTP response text from the first downstream delivery outcome when wait is true."},"idempotent_replay":{"type":"boolean","description":"True when the response replays a previously-recorded send\nkeyed by `client_idempotency_key` (same key, same canonical\npayload). False on a fresh send and on gate-denied\nresponses. Lets callers branch on cache state without\ndiffing fields.\n"},"scheduled_at":{"type":"string","format":"date-time","description":"Echoed requested execution time on a `scheduled`\nresponse. On scheduled creates, nothing is dispatched\nyet: `queue_id` is null and `accepted` / `rejected` are\nempty. Absent on immediate sends.\n"},"idempotency":{"$ref":"#/components/schemas/SendMailIdempotencyReplay"},"dedup_reason":{"type":["string","null"],"enum":["content_hash_match","parent_already_replied",null],"description":"Why the request was answered with an existing send. Set on every replay (`idempotent_replay: true`), whether it answers with `200` or with the stored error of an earlier send that failed. `content_hash_match`: the request's idempotency key matched an earlier send. That key is the `Idempotency-Key` header when one was sent, otherwise the key derived from the request content (see `idempotency.key_source`). `parent_already_replied`: a send with no `Idempotency-Key` whose `in_reply_to` names an inbound email that already has a reply from this organization that is queued, in flight, delivered or of unknown outcome. The existing reply is returned even when the content differs, and `idempotency.window_seconds` is null. This applies to `/emails/{id}/reply`, which always sets `in_reply_to`, and to `/send-mail` and batch messages that pass it. It does not apply to an `fyi` acknowledgement or to a send made with an agent connection key. Pass an `Idempotency-Key` to send a second reply. Absent or null on a request that made a send of its own."}},"required":["id","status","from","queue_id","accepted","rejected","client_idempotency_key","request_id","content_hash","idempotent_replay"]},"SendPermissionRule":{"description":"One recipient-scope rule describing a destination the caller\nmay send to. Discriminated on `type`. Each rule carries a\nhuman-prose `description` field intended for display.\n\nRule kinds are stable within an SDK release. A response\ncontaining a `type` value not enumerated in this schema\nmeans the server is running a newer version than the SDK;\nupgrade the SDK to the release that matches the server's\nschema. Strict-parsing SDKs (Go, Python) will raise a\ndecode error in that case rather than silently dropping\nthe unknown rule, since silent drops would let an outbound\nagent reason from an incomplete view of its own permissions.\n","discriminator":{"propertyName":"type","mapping":{"any_recipient":"#/components/schemas/SendPermissionAnyRecipient","managed_zone":"#/components/schemas/SendPermissionManagedZone","your_domain":"#/components/schemas/SendPermissionYourDomain","address":"#/components/schemas/SendPermissionAddress"}},"oneOf":[{"$ref":"#/components/schemas/SendPermissionAnyRecipient"},{"$ref":"#/components/schemas/SendPermissionManagedZone"},{"$ref":"#/components/schemas/SendPermissionYourDomain"},{"$ref":"#/components/schemas/SendPermissionAddress"}]},"SendPermissionAnyRecipient":{"type":"object","description":"The caller can send to any recipient. When this rule is\npresent, every other rule in the response is redundant.\n","properties":{"type":{"type":"string","enum":["any_recipient"]},"description":{"type":"string","description":"Human-prose summary of the rule."}},"required":["type","description"]},"SendPermissionManagedZone":{"type":"object","description":"The caller can send to any address at the named\nPrimitive-managed zone. Always emitted (no entitlement\nrequired) because Primitive owns the zone and every mailbox\nbelongs to a Primitive customer by construction.\n","properties":{"type":{"type":"string","enum":["managed_zone"]},"zone":{"type":"string","description":"The managed apex domain. Sends are accepted to any\naddress at the apex itself or any subdomain (e.g.\n`alice@primitive.email` and `alice@acme.primitive.email`\nboth match the `primitive.email` zone rule).\n"},"description":{"type":"string","description":"Human-prose summary of the rule."}},"required":["type","zone","description"]},"SendPermissionYourDomain":{"type":"object","description":"The caller can send to any address at one of their own\nverified outbound domains. Emitted once per active row in\nthe org's `domains` table.\n","properties":{"type":{"type":"string","enum":["your_domain"]},"domain":{"type":"string","description":"A verified outbound domain owned by the caller's org."},"description":{"type":"string","description":"Human-prose summary of the rule."}},"required":["type","domain","description"]},"SendPermissionAddress":{"type":"object","description":"The caller can send to a specific address that has\nauthenticated inbound mail to the org. Emitted once per row\nin the org's `known_send_addresses` table, capped at\n`meta.address_cap`.\n","properties":{"type":{"type":"string","enum":["address"]},"address":{"type":"string","description":"The bare email address this rule grants sends to."},"last_received_at":{"type":"string","format":"date-time","description":"Most recent inbound email from this address that\nauthenticated successfully (DMARC pass + DKIM/SPF\nalignment). Updated on each new authenticated receipt.\n"},"received_count":{"type":"integer","description":"Total number of authenticated inbound emails from this\naddress. Increments only when `last_received_at` advances.\n"},"description":{"type":"string","description":"Human-prose summary of the rule."}},"required":["type","address","last_received_at","received_count","description"]},"SendPermissionsMeta":{"type":"object","description":"Response metadata for /send-permissions. The `address_cap`\nbounds the size of the `address` rule subset; orgs with more\nthan `address_cap` known addresses almost always also hold a\nbroader rule type (`any_recipient` or `your_domain`), so the\ncap is a response-size bound rather than a meaningful\nproduct limit.\n","properties":{"address_cap":{"type":"integer","description":"Maximum number of `address` rules included in `data`."},"truncated":{"type":"boolean","description":"True when the org has more than `address_cap` known\naddresses and the list was truncated. False when every\nknown address is represented or when the org holds no\naddress rules at all.\n"}},"required":["address_cap","truncated"]},"PullWebhookInput":{"type":"object","properties":{"wait_seconds":{"default":25,"type":"integer","minimum":0,"maximum":30}},"additionalProperties":false},"PullWebhookResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"delivery":{"type":["object","null"],"properties":{"queue_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"event_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"event_type":{"type":"string"},"delivery_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"lease_token":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"lease_expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"body":{"type":"string"},"headers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["queue_id","event_id","event_type","delivery_id","lease_token","lease_expires_at","body","headers"],"additionalProperties":false},"backlog":{"type":"integer","minimum":0,"maximum":9007199254740991},"gap_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"last_gap_reason":{"type":["string","null"]},"retention_seconds":{"type":"number","enum":[86400]},"handler_timeout_seconds":{"type":"number","enum":[30]}},"required":["delivery","backlog","gap_count","last_gap_reason","retention_seconds","handler_timeout_seconds"],"additionalProperties":false},"meta":{"type":"object","properties":{"total":{"type":"number"},"total_capped":{"type":"boolean"},"limit":{"type":"number"},"cursor":{"type":["string","null"]}},"additionalProperties":{}}},"required":["success","data"],"additionalProperties":{}},"CompleteWebhookInput":{"oneOf":[{"$ref":"#/components/schemas/CompleteWebhookHttpInput"},{"$ref":"#/components/schemas/CompleteWebhookExecInput"},{"$ref":"#/components/schemas/CompleteWebhookStdoutInput"},{"$ref":"#/components/schemas/CompleteWebhookSdkInput"}],"discriminator":{"propertyName":"mode","mapping":{"http":"#/components/schemas/CompleteWebhookHttpInput","exec":"#/components/schemas/CompleteWebhookExecInput","stdout":"#/components/schemas/CompleteWebhookStdoutInput","sdk":"#/components/schemas/CompleteWebhookSdkInput"}}},"CompleteWebhookHttpInput":{"type":"object","properties":{"queue_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"delivery_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"lease_token":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"duration_ms":{"type":"integer","minimum":0,"maximum":30000},"mode":{"type":"string","enum":["http"]},"status_code":{"type":["integer","null"],"minimum":100,"maximum":599},"transport_error":{"type":"string","enum":["timeout","network","response_too_large","io"]},"error_code":{"type":"string","pattern":"^[a-zA-Z0-9_-]{1,128}$"},"confirmed":{"default":false,"type":"boolean"}},"required":["queue_id","delivery_id","lease_token","duration_ms","mode","status_code"],"additionalProperties":false},"CompleteWebhookExecInput":{"type":"object","properties":{"queue_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"delivery_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"lease_token":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"duration_ms":{"type":"integer","minimum":0,"maximum":30000},"mode":{"type":"string","enum":["exec"]},"exit_code":{"type":["integer","null"],"minimum":0,"maximum":255},"transport_error":{"type":"string","enum":["timeout","io"]}},"required":["queue_id","delivery_id","lease_token","duration_ms","mode","exit_code"],"additionalProperties":false},"CompleteWebhookStdoutInput":{"type":"object","properties":{"queue_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"delivery_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"lease_token":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"duration_ms":{"type":"integer","minimum":0,"maximum":30000},"mode":{"type":"string","enum":["stdout"]},"write_succeeded":{"type":"boolean"},"transport_error":{"type":"string","enum":["io"]}},"required":["queue_id","delivery_id","lease_token","duration_ms","mode","write_succeeded"],"additionalProperties":false},"CompleteWebhookSdkInput":{"type":"object","properties":{"queue_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"delivery_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"lease_token":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"duration_ms":{"type":"integer","minimum":0,"maximum":30000},"mode":{"type":"string","enum":["sdk"]},"accepted":{"type":"boolean"},"transport_error":{"type":"string","enum":["timeout","io"]}},"required":["queue_id","delivery_id","lease_token","duration_ms","mode","accepted"],"additionalProperties":false},"CompleteWebhookResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"result":{"type":"string","enum":["completed","already_completed"]}},"required":["result"],"additionalProperties":false},"meta":{"type":"object","properties":{"total":{"type":"number"},"total_capped":{"type":"boolean"},"limit":{"type":"number"},"cursor":{"type":["string","null"]}},"additionalProperties":{}}},"required":["success","data"],"additionalProperties":{}},"Endpoint":{"type":"object","properties":{"recipient":{"description":"Server-enforced recipient for an address-scoped pull subscription; null for an account-wide destination created before pull creation required a connected-agent credential.","type":["string","null"]},"receiver_capabilities":{"type":"object","properties":{"completion_modes":{"type":"array","items":{"type":"string","enum":["http","exec","stdout","sdk"]}},"stream_protocols":{"type":"array","items":{"type":"string","enum":["primitive.events.v1"]}}},"required":["completion_modes","stream_protocols"],"additionalProperties":false},"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"url":{"type":["string","null"]},"enabled":{"type":"boolean"},"domain_id":{"type":["string","null"],"format":"uuid","description":"Restrict this endpoint to emails from a specific domain"},"rules":{"type":"object","description":"Endpoint-specific filtering rules","additionalProperties":true,"properties":{"event_types":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","minLength":1}}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"delivery_count":{"type":"integer","description":"Total webhook deliveries attempted"},"success_count":{"type":"integer","description":"Successful deliveries"},"failure_count":{"type":"integer","description":"Failed deliveries"},"consecutive_fails":{"type":"integer","description":"Current streak of consecutive failures"},"last_delivery_at":{"type":["string","null"],"format":"date-time"},"last_success_at":{"type":["string","null"],"format":"date-time"},"last_failure_at":{"type":["string","null"],"format":"date-time"},"deactivated_at":{"type":["string","null"],"format":"date-time"},"kind":{"type":"string","enum":["http","function","pull"],"description":"http: deliver to the webhook URL. function: invoke a Primitive Function."},"function_id":{"type":["string","null"],"format":"uuid","description":"The Function this endpoint invokes, when kind is function."},"is_route_target":{"type":"boolean","description":"When true, this endpoint is reachable only via an explicit recipient\nroute, never as a domain's default destination, and is exempt from\nthe one-endpoint-per-domain rule (so many can share a domain).\n"},"name":{"type":["string","null"],"description":"Stable name of a pull destination."}},"required":["id","org_id","enabled","rules","created_at","updated_at","delivery_count","success_count","failure_count","consecutive_fails"]},"CreateEndpointInput":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string","enum":["http","function","pull"],"default":"http","description":"http: deliver to url. function: invoke function_id. pull: receive locally using a stable name, without a URL."},"url":{"type":"string","minLength":1,"description":"The webhook URL to deliver events to. Required when kind is http; omit for function endpoints."},"function_id":{"type":"string","format":"uuid","description":"The Function to invoke. Required when kind is function."},"enabled":{"type":"boolean","description":"Whether the endpoint is active"},"domain_id":{"type":["string","null"],"format":"uuid","description":"Restrict to emails from a specific domain"},"rules":{"type":"object","description":"Endpoint-specific filtering rules","additionalProperties":true,"properties":{"event_types":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","minLength":1}}}},"is_route_target":{"type":"boolean","description":"Create this endpoint as a route-target: reachable only via an\nexplicit recipient route, never a domain's default destination, and\nexempt from the one-endpoint-per-domain rule.\n"},"name":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$","description":"Stable name within the credential scope, required for kind=pull. Only connected-agent credentials can create pull destinations, and each receives only its assigned address."}}},"UpdateEndpointInput":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","minLength":1,"description":"New webhook URL (triggers endpoint rotation)"},"enabled":{"type":"boolean"},"domain_id":{"type":["string","null"],"format":"uuid"},"rules":{"type":"object","additionalProperties":true,"properties":{"event_types":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","minLength":1}}}}},"minProperties":1},"TestResult":{"type":"object","properties":{"status":{"type":"integer","description":"HTTP status code returned by the endpoint"},"body":{"type":"string","description":"Response body (truncated to 1000 characters)"},"signature":{"type":"string","description":"The signature header value sent (if webhook secret is configured)"}},"required":["status","body"]},"TestEndpointRulesInput":{"type":"object","additionalProperties":false,"properties":{"email_id":{"type":"string","format":"uuid","description":"Id of an already-received email (in your org) to evaluate the rules against."}},"required":["email_id"]},"TestEndpointRulesResult":{"type":"object","description":"Verdict of a dry-run rule evaluation. Produced by the same\nshared matcher the live delivery paths use.\n","properties":{"would_deliver":{"type":"boolean","description":"Whether the delivery path would deliver this email to the endpoint."},"rule":{"type":["string","null"],"description":"Name of the failing rule when delivery would be\nsuppressed; null when the message matches and the\nendpoint is subscribed to the event type.\n"},"reason":{"type":["string","null"],"description":"Human-readable explanation of the failing rule; null when the message matches."},"rules_valid":{"type":"boolean","description":"False when the endpoint's stored rules blob failed\nvalidation. Delivery fails OPEN on an invalid blob\n(delivers as if unfiltered), so false here surfaces a\nmisconfiguration that is otherwise silent.\n"},"subscribed_to_event":{"type":"boolean","description":"Whether the endpoint's event-type subscription includes\nthis email's event type. A separate gate applied before\nmessage matching, surfaced independently so an\nunsubscribed endpoint is distinguishable from a rule\nrejection.\n"},"event_type":{"type":"string","description":"The event type this email would be delivered as."},"evaluated":{"type":"object","description":"The message metadata the matcher compared, so the caller\ncan see WHAT was evaluated (in particular the\nauthenticated From identity versus the raw envelope\nsender).\n","properties":{"size_bytes":{"type":"integer"},"has_attachments":{"type":"boolean"},"attachment_size_bytes":{"type":"integer"},"sender":{"type":"string","description":"Raw envelope sender the blacklist matches against."},"from_address":{"type":["string","null"],"description":"Bare From-header address, when one was present."},"sender_authenticated":{"type":"boolean","description":"Whether the sender identity passed authentication (the whitelist matches only authenticated identities)."},"sender_trust_basis":{"type":"string","description":"Which signal established (or failed to establish) the sender identity."}},"required":["size_bytes","has_attachments","attachment_size_bytes","sender","from_address","sender_authenticated","sender_trust_basis"]}},"required":["would_deliver","rule","reason","rules_valid","subscribed_to_event","event_type","evaluated"]},"Filter":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"domain_id":{"type":["string","null"],"format":"uuid","description":"If set, filter applies only to this domain"},"type":{"type":"string","enum":["whitelist","blocklist"]},"pattern":{"type":"string","description":"Email address or pattern to match (stored lowercase)"},"enabled":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"required":["id","org_id","type","pattern","enabled","created_at"]},"CreateFilterInput":{"type":"object","additionalProperties":false,"properties":{"type":{"type":"string","enum":["whitelist","blocklist"]},"pattern":{"type":"string","minLength":1,"maxLength":500,"description":"Email address or pattern to filter"},"domain_id":{"type":["string","null"],"format":"uuid","description":"Restrict filter to a specific domain (Pro plan required)"}},"required":["type","pattern"]},"UpdateFilterInput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}},"required":["enabled"]},"WakeSchedule":{"type":"object","description":"A cron schedule that sends a wake.dispatch command to a function.","properties":{"id":{"type":"string","format":"uuid"},"from_address":{"type":["string","null"],"description":"The sending identity the wake is signed as."},"target_address":{"type":"string","description":"The function address the wake is delivered to."},"command":{"type":"string"},"args":{"type":"object","additionalProperties":true},"cron_expr":{"type":"string","description":"5-field cron expression."},"timezone":{"type":"string","description":"IANA timezone the cron is evaluated in."},"next_run_at":{"type":"string","format":"date-time"},"last_run_at":{"type":["string","null"],"format":"date-time"},"enabled":{"type":"boolean"},"note":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","target_address","command","cron_expr","timezone","next_run_at","enabled","created_at","updated_at"]},"CreateWakeScheduleInput":{"type":"object","additionalProperties":false,"properties":{"from_address":{"type":"string","description":"Sending identity (must be a domain the org can sign)."},"target_address":{"type":"string","description":"Your function address (must differ from from_address)."},"command":{"type":"string","minLength":1,"maxLength":200},"args":{"type":"object","additionalProperties":true,"description":"Optional JSON object passed through to the woken function."},"cron_expr":{"type":"string","minLength":1,"maxLength":120},"timezone":{"type":"string","minLength":1,"maxLength":64,"default":"UTC"},"note":{"type":"string","maxLength":2000}},"required":["from_address","target_address","command","cron_expr"]},"UpdateWakeScheduleInput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"command":{"type":"string","minLength":1,"maxLength":200},"args":{"type":"object","additionalProperties":true},"cron_expr":{"type":"string","minLength":1,"maxLength":120},"timezone":{"type":"string","minLength":1,"maxLength":64},"from_address":{"type":"string"},"target_address":{"type":"string"},"note":{"type":["string","null"],"maxLength":2000}}},"WakeAuthorization":{"type":"object","description":"A per-target allowlist grant authorizing a sender to wake a function.","properties":{"id":{"type":"string","format":"uuid"},"recipient_endpoint_id":{"type":"string","format":"uuid"},"allowed_sender_domain":{"type":"string"},"allowed_sender_address":{"type":["string","null"]},"allowed_commands":{"type":["array","null"],"items":{"type":"string"}},"enabled":{"type":"boolean"},"note":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}},"required":["id","recipient_endpoint_id","allowed_sender_domain","enabled","created_at"]},"CreateWakeAuthorizationInput":{"type":"object","additionalProperties":false,"properties":{"recipient_endpoint_id":{"type":"string","format":"uuid"},"allowed_sender_domain":{"type":"string","minLength":1,"maxLength":253,"description":"Fully-qualified sender domain (at least two labels)."},"allowed_sender_address":{"type":["string","null"],"description":"Optional specific sender address to pin the grant to."},"allowed_commands":{"type":["array","null"],"maxItems":64,"items":{"type":"string","minLength":1,"maxLength":200},"description":"Optional command allowlist; null = any command."},"note":{"type":"string","maxLength":2000}},"required":["recipient_endpoint_id","allowed_sender_domain"]},"UpdateWakeAuthorizationInput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}},"required":["enabled"]},"WakeDispatch":{"type":"object","description":"A recorded wake.dispatch interaction (audit row).","properties":{"id":{"type":"string","format":"uuid"},"wire_id":{"type":"string"},"role":{"type":"string"},"state":{"type":"string"},"outcome":{"type":["string","null"]},"awaiting":{"type":["string","null"]},"counterparty_address":{"type":"string"},"our_address":{"type":"string"},"step_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"}},"required":["id","wire_id","role","state","counterparty_address","our_address","created_at"]},"RecipientRoute":{"type":"object","description":"A recipient routing rule binding an address pattern to one endpoint.","properties":{"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"domain_id":{"type":["string","null"],"format":"uuid","description":"Domain the route is scoped to; null = org-wide."},"match_type":{"type":"string","enum":["exact","wildcard","regex"]},"pattern":{"type":"string","description":"The recipient address pattern (an exact address or a wildcard)."},"pattern_norm":{"type":["string","null"],"description":"Normalized pattern used for matching."},"endpoint_id":{"type":"string","format":"uuid","description":"The endpoint inbound mail matching this rule is delivered to."},"priority":{"type":"integer","description":"Evaluation order within a scope; lower is checked first."},"enabled":{"type":"boolean"},"match_count":{"type":"string","description":"How many emails have matched this rule (a bigint, returned as a string)."},"last_matched_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"required":["id"]},"CreateRouteInput":{"type":"object","additionalProperties":false,"description":"Provide exactly one of `endpoint_id` or `function_id`. With `function_id`,\na route-target endpoint is minted for that function and the route is bound\nto it in one transaction.\n","properties":{"match_type":{"type":"string","enum":["exact","wildcard","regex"]},"pattern":{"type":"string","minLength":1,"maxLength":512},"endpoint_id":{"type":"string","format":"uuid","description":"An existing endpoint to route to. Mutually exclusive with function_id."},"function_id":{"type":"string","format":"uuid","description":"Route to this function, minting its route-target endpoint if needed. Mutually exclusive with endpoint_id."},"domain_id":{"type":["string","null"],"format":"uuid","description":"Scope the route to a domain; defaults to the pattern's domain."},"priority":{"type":"integer","minimum":0,"maximum":1000000},"enabled":{"type":"boolean"}},"required":["match_type","pattern"]},"UpdateRouteInput":{"type":"object","additionalProperties":false,"properties":{"match_type":{"type":"string","enum":["exact","wildcard","regex"]},"pattern":{"type":"string","minLength":1,"maxLength":512},"endpoint_id":{"type":"string","format":"uuid"},"domain_id":{"type":["string","null"],"format":"uuid"},"priority":{"type":"integer","minimum":0,"maximum":1000000},"enabled":{"type":"boolean"}}},"ReorderRoutesInput":{"type":"object","additionalProperties":false,"properties":{"updates":{"type":"array","minItems":1,"maxItems":1000,"items":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"priority":{"type":"integer","minimum":0,"maximum":1000000}},"required":["id","priority"]}}},"required":["updates"]},"SimulateRouteInput":{"type":"object","additionalProperties":false,"properties":{"recipient":{"type":"string","minLength":1,"maxLength":320},"event_type":{"type":"string","minLength":1,"maxLength":100,"description":"Event type to model; defaults to email.received."}},"required":["recipient"]},"RouteEvaluatedEntry":{"type":"object","properties":{"route_id":{"type":"string"},"tier":{"type":"string","enum":["exact","wildcard","regex"]},"pattern":{"type":"string"},"result":{"type":"string","enum":["hit","miss","skipped","error"]},"reason":{"type":"string"}},"required":["route_id","tier","pattern","result"]},"SimulateRouteResult":{"type":"object","description":"Where an inbound email to the recipient would be delivered, and why.","properties":{"outcome":{"type":"string","enum":["matched","defaulted","none"]},"recipient":{"type":"string"},"endpoint_id":{"type":["string","null"]},"matched_route_id":{"type":["string","null"]},"matched_tier":{"type":["string","null"],"enum":["exact","wildcard","regex",null]},"matched_pattern":{"type":["string","null"]},"default_scope":{"type":["string","null"],"enum":["domain","org",null]},"evaluated":{"type":"array","items":{"$ref":"#/components/schemas/RouteEvaluatedEntry"}},"truncated":{"type":"boolean"}},"required":["outcome","recipient","endpoint_id","matched_route_id","matched_tier","matched_pattern","default_scope","evaluated","truncated"]},"DeliverySummary":{"type":"object","properties":{"id":{"type":"string","description":"Delivery ID (numeric string)"},"email_id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"endpoint_id":{"type":"string","format":"uuid"},"endpoint_url":{"type":"string"},"status":{"type":"string","enum":["pending","delivered","header_confirmed","failed"]},"attempt_count":{"type":"integer"},"duration_ms":{"type":["integer","null"]},"last_error":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"email":{"type":["object","null"],"properties":{"sender":{"type":"string"},"recipient":{"type":"string"},"subject":{"type":["string","null"]}},"required":["sender","recipient"]}},"required":["id","email_id","org_id","endpoint_id","endpoint_url","status","attempt_count","created_at","updated_at"]},"ReplayResult":{"type":"object","properties":{"delivered":{"type":"integer","description":"Number of successful deliveries"},"failed":{"type":"integer","description":"Number of failed deliveries"}},"required":["delivered","failed"]},"DiscardContentResult":{"type":"object","properties":{"discarded":{"type":"boolean","description":"Always `true` on a 2xx response. The content is either now\ndiscarded as a result of this call, or was already discarded\nbefore this call ran.\n"},"already_discarded":{"type":"boolean","description":"`true` if the email's content was already discarded before\nthis call ran (no work was done). `false` if this call was\nthe one that performed the discard.\n"}},"required":["discarded","already_discarded"]},"FunctionDeployStatus":{"type":"string","enum":["pending","deployed","failed"],"description":"Lifecycle state of the latest deploy attempt:\n  * `pending` - deploy in flight; the runtime has not yet\n    confirmed the new bundle is live.\n  * `deployed` - the running edge handler is the latest code.\n  * `failed` - the most recent deploy attempt failed; the\n    previously-live code (if any) is still running. The\n    `deploy_error` field carries the error message.\n"},"FunctionListItem":{"type":"object","description":"One row from the function listing.","properties":{"id":{"type":"string","format":"uuid","description":"Function id, also the script name in the edge runtime."},"name":{"type":"string","description":"Slug-style name set on creation. Stable; cannot be changed."},"deploy_status":{"$ref":"#/components/schemas/FunctionDeployStatus"},"deployed_at":{"type":["string","null"],"format":"date-time","description":"Timestamp of the most recent successful deploy. Null until the first deploy succeeds."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","deploy_status","created_at","updated_at"]},"FunctionDetail":{"type":"object","description":"Full function record returned by GET / PUT.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string","description":"The bundled handler source. UTF-8 string up to 1 MiB. The\nsame value most recently passed as `code` to POST or PUT.\n"},"deploy_status":{"$ref":"#/components/schemas/FunctionDeployStatus"},"deploy_error":{"type":["string","null"],"description":"Error message from the most recent failed deploy, or null\nafter a successful deploy. Surface this to users to explain\na `failed` status without polling.\n"},"deployed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","code","deploy_status","created_at","updated_at"]},"CreateFunctionInput":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","pattern":"^[a-z0-9_-]{1,64}$","description":"Slug-style name. Lowercase letters, digits, hyphens, and\nunderscores. 1 to 64 characters. Must be unique within the\norg; a 409 is returned on collision.\n"},"code":{"type":"string","minLength":1,"maxLength":1048576,"description":"Pre-built handler as a single ESM module. Up to 1 MiB UTF-8.\nMust export a default `{ async fetch(req, env, ctx) { ... } }`\nobject. Provide either `code` or `files`, not both.\n"},"sourceMap":{"type":"string","minLength":1,"maxLength":5242880,"description":"Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored with the deployment attempt and sent to the runtime\nto symbolicate stack traces in the function's logs. Only\nvalid with `code`.\n"},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"Source files for a managed build, as a map of path to file\ncontents (for example {\"package.json\": \"...\",\n\"src/index.ts\": \"...\"}). Provide this INSTEAD of `code` to\nhave the server install dependencies and bundle the source\nfor the Workers runtime before deploying. Include a\npackage.json (its `dependencies` are installed). Provide\neither `code` or `files`, not both.\n"}},"required":["name"]},"CreateFunctionResult":{"type":"object","description":"Returned by POST /functions on a successful deploy.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"deploy_status":{"$ref":"#/components/schemas/FunctionDeployStatus"}},"required":["id","name","deploy_status"]},"UpdateFunctionInput":{"type":"object","additionalProperties":false,"properties":{"code":{"type":"string","minLength":1,"maxLength":1048576,"description":"New pre-built handler. Same rules as CreateFunctionInput.code. Provide either `code` or `files`, not both."},"sourceMap":{"type":"string","minLength":1,"maxLength":5242880},"files":{"type":"object","additionalProperties":{"type":"string"},"description":"Source files for a managed build, as a map of path to file\ncontents. Provide this INSTEAD of `code` to rebuild and\nredeploy from source. Same rules as CreateFunctionInput.files.\n"}},"required":[]},"TestInvocationResult":{"type":"object","description":"Metadata returned by POST /functions/{id}/test. The send is\nqueued; poll `trace_url` to watch the run progress through\nsend -> inbound -> webhook deliveries -> outbound requests,\nlogs, and replies.\n","properties":{"test_run_id":{"type":"string","format":"uuid","description":"Durable test run id used to fetch the run trace."},"inbound_domain":{"type":"string","description":"Verified inbound domain the test email was sent to."},"to":{"type":"string","description":"Synthetic local-part plus inbound_domain. Visible in the org's inbox."},"from":{"type":"string","description":"Primitive-controlled outbound sender used for the test."},"send_id":{"type":"string","description":"Outbound message id from the underlying send. NOT the\ninbound email's id; the inbound id is created when the\nemail arrives via MX and lands on the function's\ninvocations list.\n"},"subject":{"type":"string","description":"Subject placed on the test email so it can be located in the inbox."},"poll_since":{"type":"string","format":"date-time","description":"ISO timestamp suitable as a `since` lower bound when\npolling /emails for the inbound's arrival. Captured\nslightly before the send to absorb light clock skew.\n"},"watch_url":{"type":"string","format":"uri","description":"Function detail page where invocations show up live."},"trace_url":{"type":"string","description":"Relative API URL for GET /functions/{id}/test-runs/{test_run_id}/trace."}},"required":["test_run_id","inbound_domain","to","from","send_id","subject","poll_since","watch_url","trace_url"]},"FunctionRouting":{"type":"object","description":"A single route binding for a function. `domain` is null when the\nbinding is the org's fallback (any active domain without a scoped\nbinding); otherwise it carries the scoped domain. `rules` is\nreserved for future routing predicates.\n","properties":{"endpoint_id":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"},"domain":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}},"required":["id"]},"rules":{"type":"object","description":"Future routing predicates. Currently empty."},"delivery_count":{"type":"integer"},"success_count":{"type":"integer"},"failure_count":{"type":"integer"},"consecutive_fails":{"type":"integer"},"last_delivery_at":{"type":["string","null"],"format":"date-time"},"last_success_at":{"type":["string","null"],"format":"date-time"},"last_failure_at":{"type":["string","null"],"format":"date-time"}},"required":["endpoint_id","enabled","domain","rules"]},"RoutingTopology":{"type":"object","description":"Org-wide map of function routing: which domain points at which\nfunction, the org's fallback binding (if any), and every\ndeployed function with no route currently bound.\n","properties":{"domains":{"type":"array","items":{"type":"object","properties":{"domain_id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"routed_function":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]},"endpoint_enabled":{"type":["boolean","null"]}},"required":["domain_id","domain","routed_function","endpoint_enabled"]}},"fallback_function":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]},"fallback_enabled":{"type":["boolean","null"]},"unrouted_functions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["domains","fallback_function","fallback_enabled","unrouted_functions"]},"FunctionRouteBody":{"type":"object","description":"Target for a route binding. Either a specific verified domain\n(scoped) or the org-wide fallback. Pass `takeover: true` to\ndeactivate any conflicting binding before installing this one.\n","properties":{"target":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["domain"]},"domainId":{"type":"string","format":"uuid"}},"required":["kind","domainId"]},{"type":"object","properties":{"kind":{"type":"string","enum":["fallback"]}},"required":["kind"]}]},"takeover":{"type":"boolean","description":"When true, deactivate any conflicting binding before installing this one."}},"required":["target"]},"FunctionRouteResult":{"type":"object","description":"On success, carries the new `routing`. On conflict, carries\n`conflict` describing the binding holder so the caller can\nre-issue with `takeover: true`.\n","properties":{"routing":{"oneOf":[{"$ref":"#/components/schemas/FunctionRouting"},{"type":"null"}]},"conflict":{"type":"object","properties":{"kind":{"type":"string","enum":["http","function"]},"functionId":{"type":["string","null"],"format":"uuid"},"functionName":{"type":["string","null"]},"url":{"type":["string","null"]}},"required":["kind"]}}},"FunctionTestRunState":{"type":"string","description":"High-level state for a function test run trace:\n  - `send_failed`: the initial test email send failed.\n  - `waiting_for_send`: the test run was created but no send result has been recorded yet.\n  - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.\n  - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.\n  - `completed`: the function webhook completed successfully.\n  - `failed`: webhook delivery exhausted retries.\n","enum":["send_failed","waiting_for_send","waiting_for_inbound","waiting_for_function","completed","failed"]},"FunctionTestRun":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"function_id":{"type":"string","format":"uuid"},"inbound_domain":{"type":"string"},"to":{"type":"string"},"from":{"type":"string"},"subject":{"type":"string"},"poll_since":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"sent_at":{"type":["string","null"],"format":"date-time"},"send_error":{"type":["string","null"]}},"required":["id","function_id","inbound_domain","to","from","subject","poll_since","created_at","sent_at","send_error"]},"FunctionTestRunSend":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/SentEmailStatus"},"queue_id":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","status","queue_id","created_at","updated_at"]},"FunctionTestRunInboundEmail":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/EmailStatus"},"received_at":{"type":"string","format":"date-time"},"from":{"type":"string"},"to":{"type":"string"},"subject":{"type":["string","null"]},"webhook_status":{"$ref":"#/components/schemas/EmailWebhookStatus"},"webhook_attempt_count":{"type":"integer"},"webhook_last_status_code":{"type":["integer","null"]},"webhook_last_error":{"type":["string","null"]}},"required":["id","status","received_at","from","to","subject","webhook_status","webhook_attempt_count","webhook_last_status_code","webhook_last_error"]},"FunctionTestRunDeliveryEndpoint":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","description":"Endpoint kind. Current traces may include `http` or `function`; future endpoint kinds may appear."},"function_id":{"type":["string","null"],"format":"uuid"},"function_name":{"type":["string","null"]},"domain_id":{"type":["string","null"],"format":"uuid"},"enabled":{"type":"boolean"},"deactivated_at":{"type":["string","null"],"format":"date-time"},"is_current_function":{"type":"boolean"}},"required":["id","kind","function_id","function_name","domain_id","enabled","deactivated_at","is_current_function"]},"FunctionTestRunDelivery":{"type":"object","properties":{"id":{"type":"string","description":"Webhook delivery id."},"endpoint_id":{"type":"string","format":"uuid"},"endpoint_url":{"type":"string","format":"uri"},"status":{"type":"string","enum":["pending","delivered","header_confirmed","failed"]},"attempt_count":{"type":"integer"},"duration_ms":{"type":["integer","null"]},"last_error":{"type":["string","null"]},"last_error_code":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"endpoint":{"$ref":"#/components/schemas/FunctionTestRunDeliveryEndpoint"}},"required":["id","endpoint_id","endpoint_url","status","attempt_count","duration_ms","last_error","last_error_code","created_at","updated_at","endpoint"]},"FunctionTestRunOutboundRequest":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"function_id":{"type":"string","format":"uuid"},"webhook_delivery_id":{"type":["string","null"]},"email_id":{"type":["string","null"],"format":"uuid"},"endpoint_id":{"type":["string","null"],"format":"uuid"},"method":{"type":"string"},"url":{"type":"string","format":"uri"},"host":{"type":"string"},"path":{"type":"string"},"status_code":{"type":["integer","null"]},"ok":{"type":["boolean","null"]},"duration_ms":{"type":"integer"},"error":{"type":["string","null"]},"ts":{"type":"string","format":"date-time"}},"required":["id","function_id","webhook_delivery_id","email_id","endpoint_id","method","url","host","path","status_code","ok","duration_ms","error","ts"]},"FunctionTestRunReply":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/SentEmailStatus"},"to":{"type":"string"},"subject":{"type":"string"},"queue_id":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}},"required":["id","status","to","subject","queue_id","created_at"]},"FunctionTestRunTrace":{"type":"object","description":"End-to-end trace for a `POST /functions/{id}/test` run. The\nshape is stable, but many nested sections are null or empty\nuntil the corresponding phase has happened.\n","properties":{"state":{"$ref":"#/components/schemas/FunctionTestRunState"},"test_run":{"$ref":"#/components/schemas/FunctionTestRun"},"test_send":{"$ref":"#/components/schemas/FunctionTestRunSend"},"inbound_email":{"$ref":"#/components/schemas/FunctionTestRunInboundEmail"},"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/FunctionTestRunDelivery"}},"outbound_requests":{"type":"array","items":{"$ref":"#/components/schemas/FunctionTestRunOutboundRequest"}},"logs":{"type":"array","items":{"$ref":"#/components/schemas/FunctionLogRow"}},"replies":{"type":"array","items":{"$ref":"#/components/schemas/FunctionTestRunReply"}}},"required":["state","test_run","test_send","inbound_email","deliveries","outbound_requests","logs","replies"]},"FunctionLogRow":{"type":"object","description":"One row from GET /functions/{id}/logs. Represents a single\ncaptured log line emitted by the running handler (e.g. via\n`console.log` / `console.error`).\n","properties":{"id":{"type":"string","format":"uuid","description":"Unique log row id (stable across pages)."},"function_id":{"type":"string","format":"uuid","description":"The function this log row belongs to."},"level":{"type":"string","enum":["debug","log","info","warn","error"],"description":"Severity. `log` is the runtime's default for unannotated\n`console.log` calls; the other levels match standard\n`console.*` methods.\n"},"message":{"type":"string","description":"The textual message body. The runtime stringifies non-string\narguments before persisting, so this is always a plain\nstring.\n"},"ts":{"type":"string","format":"date-time","description":"When the handler emitted this line. Newest-first ordering\non this column drives pagination; clock is the runtime's,\nnot the gateway's.\n"},"metadata":{"type":["object","null"],"additionalProperties":true,"description":"Optional structured payload the runtime attaches alongside\nthe message (e.g. extra args passed to `console.log`).\nShape is opaque; treat keys as untyped.\n"}},"required":["id","function_id","level","message","ts"]},"FunctionSecretListItem":{"type":"object","description":"One row from GET /functions/{id}/secrets. Discriminate on the\n`managed` field:\n  * `managed = true`  - system secret provisioned by Primitive.\n    `description` is set; `created_at` / `updated_at` are\n    null because the row is virtual (resolved at deploy time\n    from the managed registry, not stored in the secrets\n    table).\n  * `managed = false` - secret the user set via the API.\n    `created_at` / `updated_at` are set; `description` is\n    null.\n","properties":{"key":{"type":"string"},"managed":{"type":"boolean","description":"True for managed system secrets, false for user-set entries."},"description":{"type":["string","null"],"description":"Set on managed entries only; null on user-set entries."},"created_at":{"type":["string","null"],"format":"date-time","description":"Set on user-set entries only; null on managed entries."},"updated_at":{"type":["string","null"],"format":"date-time","description":"Set on user-set entries only; null on managed entries."}},"required":["key","managed"]},"CreateFunctionSecretInput":{"type":"object","additionalProperties":false,"description":"Body for POST /functions/{id}/secrets.","properties":{"key":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$","description":"Uppercase letters, digits, and underscores. Must start with\na letter or underscore. System-managed keys (e.g.\nPRIMITIVE_WEBHOOK_SECRET, PRIMITIVE_API_KEY, and\nPRIMITIVE_API_BASE_URL) are reserved.\n"},"value":{"type":"string","minLength":1,"maxLength":4096,"description":"Secret value, up to 4096 UTF-8 bytes. Encrypted at rest.\nNever returned by any read endpoint.\n"}},"required":["key","value"]},"SetFunctionSecretInput":{"type":"object","additionalProperties":false,"description":"Body for PUT /functions/{id}/secrets/{key}. Key comes from the path.","properties":{"value":{"type":"string","minLength":1,"maxLength":4096}},"required":["value"]},"FunctionSecretWriteResult":{"type":"object","description":"Returned by POST and PUT secret routes.","properties":{"key":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"created":{"type":"boolean","description":"True if this call inserted a new row, false if it updated an existing one."}},"required":["key","created_at","updated_at","created"]},"OrgSecretListItem":{"type":"object","description":"One row from GET /org/secrets. Org secrets are always user-set\n(there are no managed org secrets), so `created_at` /\n`updated_at` are always present.\n","properties":{"key":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["key","created_at","updated_at"]},"CreateOrgSecretInput":{"type":"object","additionalProperties":false,"description":"Body for POST /org/secrets.","properties":{"key":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$","description":"Uppercase letters, digits, and underscores. Must start with\na letter or underscore. System-managed keys are reserved.\n"},"value":{"type":"string","minLength":1,"maxLength":4096,"description":"Secret value, up to 4096 UTF-8 bytes. Encrypted at rest.\nNever returned by any read endpoint.\n"}},"required":["key","value"]},"SetOrgSecretInput":{"type":"object","additionalProperties":false,"description":"Body for PUT /org/secrets/{key}. Key comes from the path.","properties":{"value":{"type":"string","minLength":1,"maxLength":4096}},"required":["value"]},"OrgSecretWriteResult":{"type":"object","description":"Returned by POST and PUT org secret routes.","properties":{"key":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"created":{"type":"boolean","description":"True if this call inserted a new row, false if it updated an existing one."}},"required":["key","created_at","updated_at","created"]},"NumericString":{"type":"string","pattern":"^[0-9]+$","description":"Bigint counter serialized as a base-10 string."},"MemoryJsonValue":{"description":"JSON value accepted by Primitive Memories. The server accepts strings, numbers, booleans, null, arrays, and objects, validates nested values, and rejects values that do not serialize as JSON.","oneOf":[{"type":"null"},{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"$ref":"#/components/schemas/MemoryJsonValue"}},{"type":"object","additionalProperties":{"$ref":"#/components/schemas/MemoryJsonValue"}}]},"MemoryScope":{"description":"Memory scope. `org` resolves to the authenticated organization. `function` requires the function id UUID in `id`; function names are not valid scope identifiers. Function-authenticated requests cannot override their own Function scope.","oneOf":[{"type":"object","additionalProperties":false,"properties":{"type":{"type":"string","enum":["org"]}},"required":["type"]},{"type":"object","additionalProperties":false,"properties":{"type":{"type":"string","enum":["function"]},"id":{"type":"string","format":"uuid","description":"Function id UUID."}},"required":["type","id"]}]},"MemoryResolvedScope":{"type":"object","additionalProperties":false,"description":"Resolved memory scope returned by the API.","properties":{"type":{"type":"string","enum":["org","function"]},"id":{"type":"string","format":"uuid","description":"Org id for org scope, function id for function scope."}},"required":["type","id"]},"MemoryRecord":{"type":"object","additionalProperties":false,"description":"Metadata for a Primitive memory. Search responses omit `value` when `include_value=false`.","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string","minLength":1,"maxLength":512,"description":"Caller-defined key, at most 512 UTF-8 bytes."},"scope":{"$ref":"#/components/schemas/MemoryResolvedScope"},"value":{"$ref":"#/components/schemas/MemoryJsonValue"},"version":{"$ref":"#/components/schemas/NumericString"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"last_read_at":{"type":["string","null"],"format":"date-time","description":"Last successful get timestamp, or null before any get."},"read_count":{"$ref":"#/components/schemas/NumericString"},"write_count":{"$ref":"#/components/schemas/NumericString"},"expires_at":{"type":["string","null"],"format":"date-time","description":"Expiration timestamp, or null for no TTL."},"created_by":{"type":["string","null"],"description":"Actor that created the memory, when available."},"updated_by":{"type":["string","null"],"description":"Actor that last updated the memory, when available."}},"required":["id","key","scope","version","created_at","updated_at","last_read_at","read_count","write_count","expires_at","created_by","updated_by"]},"MemoryRecordWithValue":{"type":"object","additionalProperties":false,"description":"Memory record returned by get and set operations.","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string","minLength":1,"maxLength":512,"description":"Caller-defined key, at most 512 UTF-8 bytes."},"scope":{"$ref":"#/components/schemas/MemoryResolvedScope"},"value":{"$ref":"#/components/schemas/MemoryJsonValue"},"version":{"$ref":"#/components/schemas/NumericString"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"last_read_at":{"type":["string","null"],"format":"date-time","description":"Last successful get timestamp, or null before any get."},"read_count":{"$ref":"#/components/schemas/NumericString"},"write_count":{"$ref":"#/components/schemas/NumericString"},"expires_at":{"type":["string","null"],"format":"date-time","description":"Expiration timestamp, or null for no TTL."},"created_by":{"type":["string","null"],"description":"Actor that created the memory, when available."},"updated_by":{"type":["string","null"],"description":"Actor that last updated the memory, when available."}},"required":["id","key","scope","value","version","created_at","updated_at","last_read_at","read_count","write_count","expires_at","created_by","updated_by"]},"SetMemoryInput":{"type":"object","additionalProperties":false,"properties":{"key":{"type":"string","minLength":1,"maxLength":512,"description":"Caller-defined key, at most 512 UTF-8 bytes."},"value":{"$ref":"#/components/schemas/MemoryJsonValue"},"scope":{"$ref":"#/components/schemas/MemoryScope"},"ttl_seconds":{"type":"integer","minimum":1,"maximum":31536000,"description":"Set or replace the TTL in seconds. Mutually exclusive with `expires_at` and `clear_ttl`."},"expires_at":{"type":"string","format":"date-time","description":"Set or replace the absolute expiration timestamp. Mutually exclusive with `ttl_seconds` and `clear_ttl`."},"clear_ttl":{"type":"boolean","description":"Clear any existing TTL. Mutually exclusive with `ttl_seconds` and `expires_at`."},"if_absent":{"type":"boolean","description":"Create only when the key is absent. Mutually exclusive with `if_version`."},"if_version":{"$ref":"#/components/schemas/NumericString"}},"required":["key","value"]},"DeleteMemoryResult":{"type":"object","additionalProperties":false,"properties":{"deleted":{"type":"boolean"},"key":{"type":"string"},"scope":{"$ref":"#/components/schemas/MemoryResolvedScope"}},"required":["deleted","key","scope"]},"TemplateAuthor":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"url":{"type":"string","format":"uri"}},"required":["id","name"]},"TemplateSource":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"mode":{"type":"string","const":"managed-build"},"dir":{"type":"string","minLength":1,"default":"."}},"required":["mode","dir"]},{"type":"object","additionalProperties":false,"properties":{"mode":{"type":"string","const":"bundle"},"file":{"type":"string","minLength":1}},"required":["mode","file"]}]},"TemplateInstall":{"type":"object","additionalProperties":false,"properties":{"mode":{"type":"string","enum":["deploy","scaffold"]},"editFiles":{"type":"array","items":{"type":"string","minLength":1},"default":[]},"reason":{"type":"string","default":""}},"required":["mode","editFiles","reason"]},"TemplateSecret":{"type":"object","additionalProperties":false,"properties":{"key":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"},"required":{"type":"boolean","default":true},"description":{"type":"string"}},"required":["key","required"]},"TemplateSecretGroup":{"type":"object","additionalProperties":false,"properties":{"keys":{"type":"array","minItems":2,"items":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"}},"min":{"type":"integer","minimum":1,"default":1},"description":{"type":"string"}},"required":["keys","min"]},"TemplateVariableValidation":{"type":"object","additionalProperties":false,"properties":{"pattern":{"type":"string","minLength":1},"maxLength":{"type":"integer","minimum":1}}},"TemplateVariable":{"type":"object","additionalProperties":false,"properties":{"key":{"type":"string","minLength":1},"prompt":{"type":"string","minLength":1},"default":{"type":"string"},"file":{"type":"string","minLength":1},"type":{"type":"string","enum":["string","select","url","email"],"default":"string"},"options":{"type":"array","items":{"type":"string","minLength":1}},"validation":{"$ref":"#/components/schemas/TemplateVariableValidation"}},"required":["key","prompt","type"]},"TemplateSetup":{"type":"object","additionalProperties":false,"properties":{"agent":{"type":"string","minLength":1},"prompt":{"type":"string","minLength":1},"produces":{"type":"array","items":{"type":"string","minLength":1},"default":[]}},"required":["agent","prompt","produces"]},"TemplateManifest":{"type":"object","additionalProperties":false,"properties":{"schemaVersion":{"type":"integer","const":1},"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$","description":"Stable template slug from the manifest."},"title":{"type":"string","minLength":1},"summary":{"type":"string","minLength":1},"description":{"type":"string"},"author":{"$ref":"#/components/schemas/TemplateAuthor"},"tags":{"type":"array","items":{"type":"string","minLength":1},"default":[]},"source":{"$ref":"#/components/schemas/TemplateSource"},"install":{"$ref":"#/components/schemas/TemplateInstall"},"secrets":{"type":"array","items":{"$ref":"#/components/schemas/TemplateSecret"},"default":[]},"secretGroups":{"type":"array","items":{"$ref":"#/components/schemas/TemplateSecretGroup"},"default":[]},"variables":{"type":"array","items":{"$ref":"#/components/schemas/TemplateVariable"},"default":[]},"consumesVendors":{"type":"array","items":{"$ref":"#/components/schemas/TemplateVendorConsumption"},"default":[]},"setup":{"$ref":"#/components/schemas/TemplateSetup"},"postInstall":{"type":"string"}},"required":["schemaVersion","id","title","summary","author","tags","source","install","secrets","secretGroups","variables"]},"TemplateRegistryStatus":{"type":"string","enum":["pending","approved","rejected"]},"TemplateRegistrySummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$","description":"Stable template slug used in template URLs and install commands."},"title":{"type":"string"},"summary":{"type":"string"},"author":{"$ref":"#/components/schemas/TemplateAuthor"},"tags":{"type":"array","items":{"type":"string"}},"verified":{"type":"boolean"},"install_count":{"type":"integer","minimum":0},"github_repo":{"type":"string","pattern":"^[A-Za-z0-9-]+/[A-Za-z0-9_.-]+$","description":"GitHub repository in owner/repo form."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","slug","title","summary","author","tags","verified","install_count","github_repo","created_at","updated_at"]},"TemplateRegistryDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$","description":"Stable template slug used in template URLs and install commands."},"title":{"type":"string"},"summary":{"type":"string"},"author":{"$ref":"#/components/schemas/TemplateAuthor"},"tags":{"type":"array","items":{"type":"string"}},"verified":{"type":"boolean"},"install_count":{"type":"integer","minimum":0},"github_repo":{"type":"string","pattern":"^[A-Za-z0-9-]+/[A-Za-z0-9_.-]+$","description":"GitHub repository in owner/repo form."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"description":{"type":["string","null"]},"github_sha":{"type":"string"},"github_path":{"type":["string","null"]},"manifest":{"$ref":"#/components/schemas/TemplateManifest"},"readme":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/TemplateRegistryStatus"}},"required":["id","slug","title","summary","author","tags","verified","install_count","github_repo","created_at","updated_at","description","github_sha","github_path","manifest","readme","status"]},"TemplateRegistryPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TemplateRegistrySummary"}},"next_cursor":{"type":["string","null"],"description":"Cursor to pass as the next `cursor` query value, or null when there are no more templates."}},"required":["items","next_cursor"]},"InstallTemplateBody":{"type":"object","additionalProperties":false,"properties":{"address":{"type":"string","pattern":"^[a-z0-9][a-z0-9._-]{0,63}$","description":"Localpart to claim on the selected inbound domain. Defaults to the template slug."},"domain":{"type":"string","minLength":3,"description":"Org domain name to claim the address on. Defaults to the org's newest active domain."},"variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Template variable values keyed by manifest variable key."},"secrets":{"type":"object","additionalProperties":{"type":"string"},"description":"Secret values keyed by manifest secret key."}}},"TemplateInstallState":{"type":"string","enum":["deploying","connecting","bound","tested","deploy_failed","bind_failed","test_failed"]},"TemplateInstallStatus":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"template_slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$","description":"Template slug for this install."},"state":{"$ref":"#/components/schemas/TemplateInstallState"},"address":{"type":["string","null"]},"function_id":{"type":["string","null"],"format":"uuid"},"error":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","template_slug","state","address","function_id","error","created_at","updated_at"]},"AgentPresence":{"anyOf":[{"type":"object","properties":{"last_checked_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"valid_for_ms":{"type":"integer","minimum":0,"maximum":600000}},"required":["last_checked_at","expires_at","valid_for_ms"],"additionalProperties":false},{"type":"null"}]},"PresenceControl":{"anyOf":[{"type":"object","properties":{"status":{"type":"string","enum":["verified","pending","rejected"]},"valid_for_ms":{"type":"integer","minimum":0,"maximum":600000}},"required":["status","valid_for_ms"],"additionalProperties":false},{"type":"null"}]},"PresenceProfile":{"type":"object","properties":{"protocol":{"type":"string","const":"primitive.presence"},"version":{"type":"number","const":1},"authentication_profile":{"type":"string","const":"primitive-issued-v1"},"return_address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["protocol","version","authentication_profile","return_address"],"additionalProperties":false},"MemberAddress":{"type":"object","properties":{"address":{"type":"string","format":"email"},"name":{"type":["string","null"]}},"required":["address","name"]},"MemberSender":{"type":"object","description":"Verified human authorship, projected only within the member organization. Historical attribution is not current sending or owner authority.","properties":{"address":{"type":"string","format":"email"},"user_id":{"type":"string"},"name":{"type":["string","null"]}},"required":["address","user_id","name"]},"CallerIdentity":{"type":"object","properties":{"org_id":{"type":"string"},"user_id":{"type":"string"},"role":{"type":"string"},"request_id":{"type":"string"},"auth_method":{"type":["string","null"]},"key_id":{"type":["string","null"]},"member_address":{"type":["object","null"],"properties":{"address":{"type":"string","format":"email"},"name":{"type":["string","null"]}},"required":["address","name"]},"member_address_suggestion":{"type":["string","null"],"description":"Suggested personal address; nothing is assigned until explicitly saved."}},"required":["org_id","user_id","role","request_id","auth_method","key_id","member_address","member_address_suggestion"]},"AgentConnection":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"shopping_function_id":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},{"type":"null"}]},"address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string","minLength":1,"maxLength":80},"owner_address":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"owner_member_address":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"status":{"type":"string","enum":["pending","claimed","connected","revoked"]},"created_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"updated_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"claimed_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"verified_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"revoked_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"presence":{"anyOf":[{"type":"object","properties":{"last_checked_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"valid_for_ms":{"type":"integer","minimum":0,"maximum":600000}},"required":["last_checked_at","expires_at","valid_for_ms"],"additionalProperties":false},{"type":"null"}]},"ownership_kind":{"type":"string","enum":["personal","shared","legacy_unknown"]},"owner_user_id":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},{"type":"null"}]},"owner_active":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"type":"boolean"},"hidden_reason":{"anyOf":[{"type":"string","enum":["idle","manual"]},{"type":"null"}]},"hidden_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"keep_visible":{"type":"boolean"},"last_activity_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]}},"required":["address","name","owner_address","owner_member_address","status","created_at","updated_at","claimed_at","verified_at","last_seen_at","ownership_kind","owner_user_id","owner_active"],"additionalProperties":false},"PublicAddressNote":{"type":"object","additionalProperties":false,"properties":{"address":{"type":"string","format":"email","maxLength":254,"description":"Bare email address, normalized to lowercase. Plus tags are preserved."},"name":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema."},"value":{"description":"Any JSON value. Both UTF-8 serialized JSON and stored JSONB text must fit within 65536 bytes."},"updated_at":{"type":"string","format":"date-time"}},"required":["address","name","value","updated_at"]},"AddressNote":{"type":"object","additionalProperties":false,"properties":{"address":{"type":"string","format":"email","maxLength":254,"description":"Bare email address, normalized to lowercase. Plus tags are preserved."},"name":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"Case-sensitive note name. AGENT_INFO, AGENT_USES and AGENT_RUNTIME (where the agent runs, e.g. its runtime, machine and working directory) are conventions, with no required value schema."},"value":{"description":"Any JSON value. Both UTF-8 serialized JSON and stored JSONB text must fit within 65536 bytes."},"updated_at":{"type":"string","format":"date-time"},"visibility":{"type":"string","enum":["private","public"]},"version":{"type":"string","pattern":"^[0-9]+$","maxLength":19,"description":"Monotonic version for this org, address and name, including deletion and recreation."},"created_at":{"type":"string","format":"date-time"}},"required":["address","name","value","updated_at","visibility","version","created_at"]},"PutAddressNoteRequest":{"type":"object","additionalProperties":false,"required":["value"],"properties":{"value":{"description":"Any JSON value. Both UTF-8 serialized JSON and stored JSONB text must fit within 65536 bytes."},"visibility":{"type":"string","enum":["private","public"],"description":"New notes default to private. Omit to retain current visibility. Public edits become public immediately. Publishing requires current active verified domain ownership."},"if_absent":{"type":"boolean","description":"When true, fail with 409 if a live note exists. Mutually exclusive with if_version."},"if_version":{"type":"string","pattern":"^[0-9]+$","maxLength":19,"description":"Monotonic version for this org, address and name, including deletion and recreation."}},"not":{"required":["if_absent","if_version"],"properties":{"if_absent":{"const":true}}}},"Contact":{"type":"object","additionalProperties":false,"properties":{"address":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"display_name":{"type":["string","null"],"maxLength":200},"version":{"type":"string","format":"uuid","description":"Opaque CAS token. Changes on mutations and cannot be reused after deletion/recreation."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["address","display_name","version","created_at","updated_at"]},"AgentContact":{"type":"object","additionalProperties":false,"properties":{"agent_address":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"contact_address":{"type":"string","format":"email","maxLength":254,"description":"Bare email address; trim and lowercase, preserving dots and plus tags."},"purpose":{"type":["string","null"],"maxLength":2000},"notify":{"type":"boolean"},"notify_since":{"type":["string","null"],"format":"date-time"},"notification_generation":{"type":["string","null"],"format":"uuid"},"version":{"type":"string","format":"uuid","description":"Opaque CAS token. Changes on mutations and cannot be reused after deletion/recreation."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["agent_address","contact_address","purpose","notify","notify_since","notification_generation","version","created_at","updated_at"]},"PutContactRequest":{"type":"object","additionalProperties":false,"properties":{"display_name":{"type":["string","null"],"maxLength":200},"if_absent":{"type":"boolean","const":true},"if_version":{"type":"string","format":"uuid","description":"Opaque CAS token. Changes on mutations and cannot be reused after deletion/recreation."}},"oneOf":[{"required":["if_absent"],"not":{"required":["if_version"]}},{"required":["if_version"],"not":{"required":["if_absent"]}}]},"PutAgentContactRequest":{"type":"object","additionalProperties":false,"properties":{"purpose":{"type":["string","null"],"maxLength":2000},"notify":{"type":"boolean"},"if_absent":{"type":"boolean","const":true},"if_version":{"type":"string","format":"uuid","description":"Opaque CAS token. Changes on mutations and cannot be reused after deletion/recreation."}},"oneOf":[{"required":["if_absent"],"not":{"required":["if_version"]}},{"required":["if_version"],"not":{"required":["if_absent"]}}]},"ThreadMute":{"type":"object","additionalProperties":false,"required":["thread_id","address","muted","muted_at"],"properties":{"thread_id":{"type":"string","format":"uuid"},"address":{"type":"string","description":"The receiving address the mute applies to, lowercased."},"muted":{"type":"boolean"},"muted_at":{"type":["string","null"],"format":"date-time"}}},"EmailSignalInput":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["read","working","typing"],"description":"`read`: you read the email. `working`: you are working on it. `typing`: you are writing a reply."},"expires_in_seconds":{"type":"integer","minimum":1,"maximum":60,"description":"working and typing only: how long the signal holds. Defaults to 30. Not allowed for read."}}},"ThreadReadState":{"type":"object","additionalProperties":false,"required":["thread_id","last_read_at","last_read_email_id","unread_count","updated_at"],"properties":{"thread_id":{"type":"string","format":"uuid"},"last_read_at":{"type":["string","null"],"format":"date-time","description":"Arrival time of the newest message the member has read in this thread, in microsecond UTC form, or null when the member never marked it. Sending it back as `up_to` names the same instant; a timestamp cursor covers every message that arrived at that instant, so it is at or ahead of an id cursor at the same instant."},"last_read_email_id":{"type":["string","null"],"format":"uuid","description":"The email or sent email the cursor was set from, or null (never marked, or marked by `up_to`)."},"unread_count":{"type":"integer","minimum":0,"description":"Inbound mail in the thread that arrived after the cursor, counting only mail from others: informational signals (`fyi: true`, such as read and acknowledgement notices) and mail the member sent from their own address are not counted. A thread the member never marked counts all of it."},"updated_at":{"type":["string","null"],"format":"date-time","description":"When the cursor last moved, or null when it never did. Orders the `updated_since` change feed."}}},"MarkThreadReadResult":{"type":"object","additionalProperties":false,"required":["thread_id","last_read_at","last_read_email_id","unread_count","updated_at","advanced"],"properties":{"thread_id":{"type":"string","format":"uuid"},"last_read_at":{"type":["string","null"],"format":"date-time","description":"Arrival time of the newest message the member has read in this thread, in microsecond UTC form, or null when the member never marked it. Sending it back as `up_to` names the same instant; a timestamp cursor covers every message that arrived at that instant, so it is at or ahead of an id cursor at the same instant."},"last_read_email_id":{"type":["string","null"],"format":"uuid","description":"The email or sent email the cursor was set from, or null (never marked, or marked by `up_to`)."},"unread_count":{"type":["integer","null"],"minimum":0,"description":"Inbound mail in the thread that arrived after the cursor, counting only mail from others: informational signals (`fyi: true`, such as read and acknowledgement notices) and mail the member sent from their own address are not counted. A thread the member never marked counts all of it. Null only together with `state_unavailable`."},"updated_at":{"type":["string","null"],"format":"date-time","description":"When the cursor last moved, or null when it never did. Orders the `updated_since` change feed."},"advanced":{"type":"boolean","description":"False when the request named a position at or behind the stored cursor, so nothing changed."},"state_unavailable":{"type":"boolean","const":true,"description":"Present only when the cursor write committed but the unread count could not be read afterwards. The cursor fields are the committed ones and `unread_count` is null: treat the write as done and read the count later from `GET /threads/read-state` rather than retrying the write."}}},"MarkThreadReadInput":{"type":"object","additionalProperties":false,"description":"Name exactly one of `up_to_email_id` or `up_to`.","properties":{"up_to_email_id":{"type":"string","format":"uuid","description":"An email or sent email in this thread. It and everything that arrived before it are read."},"up_to":{"type":"string","format":"date-time","description":"Everything that arrived at or before this instant is read. A future instant is clamped to now."}}},"EmailReadableLink":{"type":"object","additionalProperties":false,"required":["n","url"],"properties":{"n":{"type":"integer","minimum":1,"description":"The number in this link's `[n]` marker in `body_text`."},"url":{"type":"string","description":"The target as the email wrote it. It can be long, and can be a tracking redirect."}}},"EmailReadableAttachment":{"type":"object","additionalProperties":false,"description":"One attached file. Download it with `GET /emails/{id}/attachments/{part_index}`.","required":["filename","content_type","size_bytes","part_index"],"properties":{"filename":{"type":["string","null"],"description":"The name the sender gave it, if any."},"content_type":{"type":["string","null"],"description":"The declared media type, such as `application/pdf`."},"size_bytes":{"type":["integer","null"],"minimum":0,"description":"Size of the decoded file."},"part_index":{"type":["integer","null"],"minimum":0,"description":"Its position among the attachments."}}},"EmailReadable":{"type":"object","additionalProperties":false,"description":"What an inbound email says, as plain text, with the fields that identify it.","required":["id","thread_id","received_at","from","to","subject","parse_status","preheader","body_text","body_source","body_chars","body_offset","body_next_offset","body_incomplete","quoted_chars_removed","image_count","link_count","attachments"],"properties":{"id":{"type":"string","format":"uuid","description":"The inbound email id."},"thread_id":{"type":["string","null"],"format":"uuid","description":"Its conversation, once it has been placed in one."},"received_at":{"type":"string","format":"date-time","description":"When it was received."},"from":{"type":"string","description":"The From header with its display name, or the bare sender address when there is none."},"to":{"type":"string","description":"The address it was delivered to."},"subject":{"type":["string","null"],"description":"The subject line."},"parse_status":{"type":["string","null"],"enum":["pending","processing","complete","failed",null],"description":"How far parsing has got, as on `GET /emails/{id}`. While it is `pending` or `processing`, the body, `thread_id` and attachments may be missing or change, so read again shortly. `complete` is final. `failed` is final too: parsing ended without them, so they may be empty. Null on an email older than this field, and before parsing is recorded."},"preheader":{"type":["string","null"],"description":"The preview line a mail client shows beside the subject, hidden at the top of the body. Not repeated in `body_text`."},"body_text":{"type":"string","description":"One page of the readable body: the words of the email in reading order. Each link is a `[n]` marker beside its label, without its target (see `links`). Hidden content and the quoted history below a reply are left out. Empty when there is nothing to read or the content is withheld."},"body_source":{"type":"string","enum":["html","text","none"],"description":"The stored body the text came from, or `none` when there was nothing to read."},"body_chars":{"type":"integer","minimum":0,"description":"Length in characters of the whole readable body, across every page."},"body_offset":{"type":"integer","minimum":0,"description":"Where this page starts in the readable body: the `offset` sent, or 0."},"body_next_offset":{"type":["integer","null"],"minimum":0,"description":"The `offset` of the next page. Null at the end of the body."},"body_incomplete":{"type":"boolean","description":"True when the HTML body was nested too deeply to read to the end and there was no text body to use: the readable body stops early. Use the detail read for the rest."},"quoted_chars_removed":{"type":"integer","minimum":0,"description":"Characters of quoted history (earlier messages a reply repeats) removed."},"image_count":{"type":"integer","minimum":0,"description":"Images in the body. Many images with little text means the email says most of it in pictures."},"link_count":{"type":"integer","minimum":0,"description":"Different links the whole readable body refers to, across every page."},"links":{"type":"array","items":{"$ref":"#/components/schemas/EmailReadableLink"},"description":"Link targets. Present only when the request carried `links`."},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/EmailReadableAttachment"},"description":"The attached files. Empty when there are none or the content is withheld."}}},"Change":{"type":"object","additionalProperties":false,"description":"One invalidation: what changed, by id, never its content. Every field is present; fields that do not apply to the kind are null. Apply idempotently: a change can repeat.","required":["kind","email_id","sent_email_id","thread_id","status","agent_address","reason","changed_at"],"properties":{"kind":{"type":"string","enum":["email.visible","email.updated","email.deleted","sent_email.created","sent_email.updated","sent_email.deleted","thread.read_state_changed","thread.mute_changed","agent.changed","access.changed"],"description":"`email.visible`: an inbound email became visible to this credential (arrived, or its sender-authentication hold was released). `email.updated`: something a list row or conversation shows changed. `email.deleted`: gone, or withdrawn from view. `sent_email.created` / `sent_email.updated`: a send appeared or changed; `status` is its current status, and several changes between two reads collapse into one entry. Created is a hint (first committed after the cursor): treat both as upsert, including `sent_email.updated` for an id you have not seen. `sent_email.deleted`: the send was deleted. `thread.read_state_changed`: the signed-in member's read cursor for `thread_id` moved, on any device. `thread.mute_changed`: `thread_id` was muted or unmuted for an address this credential reads, which changes the `muted` flag and `exclude_muted` results of its mail. `agent.changed`: an agent in the directory changed (connected, disconnected, renamed, hidden, listed, presence extended, receiver live). `access.changed`: this credential's own access changed; rebuild every snapshot from `since=start`."},"email_id":{"type":["string","null"],"format":"uuid","description":"email.* kinds."},"sent_email_id":{"type":["string","null"],"format":"uuid","description":"sent_email.* kinds."},"thread_id":{"type":["string","null"],"format":"uuid","description":"The conversation, when known. Null for a connected agent reading a send, or its deletion, that it received but did not send, matching what GET /sent-emails withholds. Inbound mail is announced when it becomes visible, which can be before its conversation is resolved, so `email.visible` can carry null; resolving or changing the conversation is always announced afterwards as `email.updated` with the `thread_id`."},"status":{"type":["string","null"],"description":"sent_email.created and sent_email.updated: the send's current status."},"agent_address":{"type":["string","null"],"description":"agent.changed: the agent's address."},"reason":{"type":["string","null"],"enum":["membership","agent_connection",null],"description":"access.changed: `membership` (the member's role or membership) or `agent_connection` (this agent connection's status, owner or generation)."},"changed_at":{"type":"string","format":"date-time","description":"The commit position in microsecond UTC form. Orders the feed; not a business timestamp."}}},"ChangePage":{"type":"object","additionalProperties":false,"required":["changes","next_cursor","has_more","baseline"],"properties":{"changes":{"type":"array","items":{"$ref":"#/components/schemas/Change"},"description":"Oldest first, in commit order."},"next_cursor":{"type":"string","description":"Always present. Send it unchanged as `since` next time, and store it only after applying the page. It is exactly the `since` you sent when nothing at all happened after it; it moves past changes this credential cannot see, so the page can be empty with a new cursor."},"has_more":{"type":"boolean","description":"More changes are already pending: call again at once, without waiting."},"baseline":{"type":"boolean","description":"True only for a `since=start` request, whose page is always empty."}}},"PutMailRoutingRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mailbox":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"participants":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"placement":{"type":"string","enum":["inbox","conversations"]},"expected_version":{"type":"integer","minimum":0,"maximum":2147483646},"mutation_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["mailbox","participants","placement","expected_version","mutation_id"],"additionalProperties":false},"MailRoutingList":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mailbox":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"items":{"type":"array","items":{"type":"object","properties":{"conversation_key":{"type":"string","minLength":1,"maxLength":25600},"placement":{"type":"string","enum":["inbox","conversations"]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mutation_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"updated_at":{"type":"string"}},"required":["conversation_key","placement","version","mutation_id","updated_at"],"additionalProperties":false}},"next_cursor":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{64}$"},{"type":"null"}]}},"required":["mailbox","items","next_cursor"],"additionalProperties":false},"MailRoutingUpdate":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mailbox":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"item":{"type":"object","properties":{"conversation_key":{"type":"string","minLength":1,"maxLength":25600},"placement":{"type":"string","enum":["inbox","conversations"]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mutation_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"updated_at":{"type":"string"}},"required":["conversation_key","placement","version","mutation_id","updated_at"],"additionalProperties":false}},"required":["mailbox","item"],"additionalProperties":false},"PutMailTopicEditsRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mailbox":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"participants":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"edit":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"membership"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"message":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"required":["kind","topic_id","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"summary"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"text":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]},"sources":{"minItems":1,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}}},"required":["kind","topic_id","text","sources"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"task"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"task_id":{"type":"string","minLength":1,"maxLength":256},"text":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"completed":{"type":"boolean"},"dismissed":{"type":"boolean"},"evidence":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"sources":{"minItems":1,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}}},"required":["kind","topic_id","task_id","text","completed","dismissed","sources"],"additionalProperties":false}]},"expected_version":{"type":"integer","minimum":0,"maximum":2147483646},"mutation_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["mailbox","participants","edit","expected_version","mutation_id"],"additionalProperties":false},"MailTopicEditsList":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mailbox":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"items":{"type":"array","items":{"type":"object","properties":{"edit":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"membership"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"message":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"required":["kind","topic_id","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"summary"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"text":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]},"sources":{"minItems":1,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}}},"required":["kind","topic_id","text","sources"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"task"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"task_id":{"type":"string","minLength":1,"maxLength":256},"text":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"completed":{"type":"boolean"},"dismissed":{"type":"boolean"},"evidence":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"sources":{"minItems":1,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}}},"required":["kind","topic_id","task_id","text","completed","dismissed","sources"],"additionalProperties":false}]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mutation_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"updated_at":{"type":"string"}},"required":["edit","version","mutation_id","updated_at"],"additionalProperties":false}},"next_cursor":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{64}$"},{"type":"null"}]}},"required":["mailbox","items","next_cursor"],"additionalProperties":false},"MailTopicEditsUpdate":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mailbox":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"item":{"type":"object","properties":{"edit":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"membership"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"message":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"required":["kind","topic_id","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"summary"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"text":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]},"sources":{"minItems":1,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}}},"required":["kind","topic_id","text","sources"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"task"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"task_id":{"type":"string","minLength":1,"maxLength":256},"text":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"completed":{"type":"boolean"},"dismissed":{"type":"boolean"},"evidence":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"sources":{"minItems":1,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}}},"required":["kind","topic_id","task_id","text","completed","dismissed","sources"],"additionalProperties":false}]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mutation_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"updated_at":{"type":"string"}},"required":["edit","version","mutation_id","updated_at"],"additionalProperties":false}},"required":["mailbox","item"],"additionalProperties":false},"PutMailTopicGenerationsRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mailbox":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"participants":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"generation":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"membership"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"sources":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"input_digest":{"type":"string","pattern":"^[0-9a-f]{64}$"},"provider":{"type":"string","const":"apple-on-device"},"message":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"required":["kind","topic_id","sources","input_digest","provider","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"intelligence"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"sources":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"input_digest":{"type":"string","pattern":"^[0-9a-f]{64}$"},"provider":{"type":"string","const":"apple-on-device"},"summary":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"summary_source_message_ids":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"tasks":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":256},"text":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"source_message_ids":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"evidence":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"}},"required":["id","text","source_message_ids","evidence"],"additionalProperties":false}}},"required":["kind","topic_id","sources","input_digest","provider","summary","summary_source_message_ids","tasks"],"additionalProperties":false}]},"expected_version":{"type":"integer","minimum":0,"maximum":2147483646},"mutation_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["mailbox","participants","generation","expected_version","mutation_id"],"additionalProperties":false},"MailTopicGenerationsList":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mailbox":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"items":{"type":"array","items":{"type":"object","properties":{"generation":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"membership"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"sources":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"input_digest":{"type":"string","pattern":"^[0-9a-f]{64}$"},"provider":{"type":"string","const":"apple-on-device"},"message":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"required":["kind","topic_id","sources","input_digest","provider","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"intelligence"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"sources":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"input_digest":{"type":"string","pattern":"^[0-9a-f]{64}$"},"provider":{"type":"string","const":"apple-on-device"},"summary":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"summary_source_message_ids":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"tasks":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":256},"text":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"source_message_ids":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"evidence":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"}},"required":["id","text","source_message_ids","evidence"],"additionalProperties":false}}},"required":["kind","topic_id","sources","input_digest","provider","summary","summary_source_message_ids","tasks"],"additionalProperties":false}]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mutation_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"updated_at":{"type":"string"}},"required":["generation","version","mutation_id","updated_at"],"additionalProperties":false}},"next_cursor":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{64}$"},{"type":"null"}]}},"required":["mailbox","items","next_cursor"],"additionalProperties":false},"MailTopicGenerationsUpdate":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mailbox":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"item":{"type":"object","properties":{"generation":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"membership"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"sources":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"input_digest":{"type":"string","pattern":"^[0-9a-f]{64}$"},"provider":{"type":"string","const":"apple-on-device"},"message":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"required":["kind","topic_id","sources","input_digest","provider","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"intelligence"},"topic_id":{"type":"string","minLength":1,"maxLength":26000},"sources":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["inbound","outbound"]},"message_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"required":["direction","message_id"],"additionalProperties":false}},"input_digest":{"type":"string","pattern":"^[0-9a-f]{64}$"},"provider":{"type":"string","const":"apple-on-device"},"summary":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"summary_source_message_ids":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"tasks":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":256},"text":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},"source_message_ids":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"evidence":{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"}},"required":["id","text","source_message_ids","evidence"],"additionalProperties":false}}},"required":["kind","topic_id","sources","input_digest","provider","summary","summary_source_message_ids","tasks"],"additionalProperties":false}]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mutation_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"updated_at":{"type":"string"}},"required":["generation","version","mutation_id","updated_at"],"additionalProperties":false}},"required":["mailbox","item"],"additionalProperties":false},"TemplateVendorConsumption":{"type":"object","additionalProperties":false,"properties":{"slug":{"type":"string","minLength":1},"required":{"type":"boolean","default":true}},"required":["slug"]},"SendMailIdempotencyReplay":{"type":"object","description":"Present only when the request was answered with an existing send instead of making a new one (`idempotent_replay: true`). Nothing was sent for this request. Says how the idempotency key was derived and which send the request collapsed onto.","required":["replayed","key_source","original_sent_email_id","original_created_at","window_seconds"],"properties":{"replayed":{"type":"boolean","const":true,"description":"Always `true`. The object is absent on a request that made a send of its own."},"key_source":{"type":"string","enum":["explicit","auto_content","function_trigger"],"description":"`explicit`: the request carried an `Idempotency-Key` header. `auto_content`: no header was sent, so the key was derived from the canonical request content (recipients included) and a fixed 5-minute window; an identical send inside the same window was not sent again. `function_trigger`: no header on a send made by a Function, so the key was derived from the content, the Function and the inbound email (or event) that invoked it."},"original_sent_email_id":{"type":"string","format":"uuid","description":"The send this request collapsed onto. Same value as `id`."},"original_created_at":{"type":["string","null"],"format":"date-time","description":"When that send was created."},"window_seconds":{"type":["integer","null"],"description":"Length of the content window (300) when an `auto_content` key matched on content. Null for `explicit` and `function_trigger` keys, which have no window, and for a keyless reply matched because its parent already has a reply (`dedup_reason: parent_already_replied`)."}}}},"headers":{"IdempotentReplayed":{"description":"Set to `true` when the request was answered with an existing send instead of making a new one. Absent on a request that sent. On a batch it means at least one message replayed; see `Idempotent-Replayed-Count`.","schema":{"type":"string","enum":["true"]}},"IdempotentReplayedCount":{"description":"Batch sends only: how many messages were answered with an existing send instead of being sent. Absent when every message sent.","schema":{"type":"integer","minimum":1}},"IdempotencyKey":{"description":"Effective idempotency key for this request. When the caller supplies an `Idempotency-Key` request header the same value is echoed; otherwise the server derives one from the canonical payload hash and, on sends, a fixed 5-minute window, so an identical keyless send inside the same window replays the first instead of sending again (the response then carries `Idempotent-Replayed: true`). Use this to correlate a request to its stored sent-email record via the `idempotency_key` filter on `GET /sent-emails`.","schema":{"type":"string","minLength":1,"maxLength":255}},"ratelimit-limit":{"description":"Maximum number of requests allowed in the current window. Example: a standard authenticated organization.","schema":{"type":"integer","minimum":1,"example":3000}},"ratelimit-remaining":{"description":"Remaining requests in the current window.","schema":{"type":"integer","minimum":0,"example":2998}},"ratelimit-reset":{"description":"Unix timestamp (seconds) when the current window resets.","schema":{"type":"integer","example":1700000060}},"ratelimit-policy":{"description":"Rate-limit policy in `limit;w=seconds` format. Example: a standard authenticated organization.","schema":{"type":"string","example":"3000;w=60"}},"ratelimit-limit-unauthenticated":{"description":"Request limit of the bucket this request was charged to: the unauthenticated IP bucket for a missing or invalid bearer token (example), or the organization bucket when an authenticated request is refused.","schema":{"type":"integer","minimum":1,"example":120}},"ratelimit-remaining-unauthenticated":{"description":"Requests remaining in the bucket this request was charged to (see ratelimit-limit). Absent while the limiter is unavailable.","schema":{"type":"integer","minimum":0,"example":119}},"ratelimit-reset-unauthenticated":{"description":"Unix timestamp (seconds) when the bucket this request was charged to resets. Absent while the limiter is unavailable.","schema":{"type":"integer","example":1700000060}},"ratelimit-policy-unauthenticated":{"description":"Policy of the bucket this request was charged to, in `limit;w=seconds` format. The example is the unauthenticated IP policy.","schema":{"type":"string","example":"120;w=60"}},"ratelimit-limit-429":{"description":"Limit of the limiter that rejected this request: the API limit (example), a send cap, or a per-resource limit. Absent, with the other ratelimit headers, when that limiter reports only a wait.","schema":{"type":"integer","minimum":1,"example":3000}},"ratelimit-remaining-429":{"description":"Requests left in the limiter that rejected this request.","schema":{"type":"integer","minimum":0,"example":0}},"ratelimit-policy-429":{"description":"That limiter as `limit;w=seconds`, with its own window: 60 for API limits, 3600 or 86400 for send caps.","schema":{"type":"string","example":"3000;w=60"}}}},"x-tag-specs":"https://api.primitive.dev/v1/openapi/tags"}