{"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. A webhook whose signed_with is own_secret is signed with its own secret instead; read it from GET \/endpoints\/{id}\/signing-secret.\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 the secret the endpoint's real deliveries use, so you can confirm verification end-to-end without needing real inbound mail. The test response carries the exact `signature` header value sent on the wire, so you can compare strings directly. It is answered only to a caller who may see it: for a webhook signed with the organization's secret, a caller who may read that secret; for one signed with its own, the person who owns it, calling as themselves.\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 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, own-address note reads and writes, reads of other organization agents' AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes, 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 API is **v1**, served under `\/v1\/`. Changes are listed in the changelog. Fields and operations are added to v1 without a version change, so a client should ignore response fields it does not recognize. Deprecated operations and fields are marked with the standard OpenAPI `deprecated: true` flag and carry a plain-English description of the replacement. A response to a request that uses a deprecated operation or field 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.","contact":{"name":"Primitive","url":"https:\/\/primitive.dev"},"license":{"name":"Proprietary","url":"https:\/\/primitive.dev\/terms"},"x-stability-level":"stable","x-deprecation-policy":"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 version is v1, and changes are listed in the changelog. 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":"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. 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":"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":"Projects","description":"Organization people, shared projects and work-email invitations. Human OAuth sessions only, except that an org-wide API key may list the organization members. An invitation grants access only after verified acceptance; matching email domains do not grant membership.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/projects"},{"name":"Organizations","description":"Human organization invitations. Acceptance adds a membership without sharing projects.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/organizations"},{"name":"Mailbox access","description":"Who may read, draft and send for each mailbox, and the groups access can be given to. Readable by people and org-wide API keys; changed by a dashboard session, an org-wide API key, or the person's own Primitive CLI. Other OAuth apps need a session step-up to change it. Answers 404 feature_disabled, with details.feature naming the feature (mailbox_access, or roles for groups), while it is not enabled for the organization; contact Primitive to turn it on.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/mailbox-access"},{"name":"Credentials","description":"API keys, the credential inventory, the credential audit trail and the credential policy. For people only: API keys, agent connection keys and agents never administer credentials. Key writes accept the person's own OAuth token with a step-up token; everything else is a dashboard session. Each operation says how it answers while the organization's credential features are not enabled.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/credentials"},{"name":"Organization settings","description":"Organization administration for people: your own connected apps and CLI logins, the organization's connected apps, its name and logo, and semantic search consent. API keys, agent connection keys and agents are refused. Billing and deleting the organization are done in the dashboard only.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/organization-settings"},{"name":"Email notifications","description":"The notification emails Primitive sends an organization's members: the organization chooses, per kind, whether it is off, left to each member or required, and each member chooses where it is left to them. Some kinds are always sent, and some (org_only) are the organization's alone: on for every owner and admin, or off. API keys, agent connection keys and agents are refused.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/email-notifications"},{"name":"Roles and members","description":"Roles, member roles and removal, the mailbox access mode, break-glass, transfers and suspensions. Reads are open to people; every write is person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up. API keys never reach these. Answers 404 feature_disabled while the feature is not enabled for the organization, with details.feature naming the feature; contact Primitive to turn it on.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/roles-and-members"},{"name":"Agent Work","description":"Agent Work operations.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/agent-work"},{"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":"Interactions","description":"Typed multi-step protocols carried over email between agents (acknowledgements, approvals, Wake dispatches). Each step is a carrier email with an interaction.json part and a DKIM-signed X-Primitive-Interaction classifier header.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/interactions"},{"name":"Stats","description":"Activity figures for the organization: received and sent mail per hour, and how webhook deliveries ended.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/stats"},{"name":"Activity","description":"What the calling credential itself did: the objects it created, by id, name and state.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/activity"},{"name":"Email Rules","description":"Rules a member writes in plain English to pick out received mail. The organization needs the email rules feature, and the caller must be a member (their own session, or an OAuth or CLI token that is not narrowed) with mail:read.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/email-rules"},{"name":"Email Rule Oversight","description":"The email rules that reach your own mailboxes, whoever wrote them, read-only, and your objections to them: asking a rule's author to turn it off, narrow it or explain it. The organization needs the email rules feature, and the caller must be a member with mail:read on their own dashboard session or the Primitive CLI, not narrowed.","x-openapi-spec":"https:\/\/api.primitive.dev\/v1\/openapi\/tags\/email-rule-oversight"},{"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":"Discovery","description":"Unauthenticated natural-language query surface that describes Primitive to an arriving agent.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["account:claim"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["account:claim"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["account:claim"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["credentials:revoke_self"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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. Fields the credential has no permission to read are null: plan, limits, entitlements, settings and the managed inbox need org:read, the subscription dates billing:read, the signup email the access that shows members' emails, and the webhook secret rotation date webhook_secret:read."},"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":[]}],"x-primitive-permission":["account:manage"],"description":"Update the organization's spam threshold (spam_threshold) and whether an email's content is discarded once a webhook confirms receipt (discard_content_on_webhook_confirmed), the opt-in POST \/emails\/{id}\/discard-content needs."}},"\/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":[]}],"x-primitive-permission":["org:read"],"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\nEvery webhook is signed with this secret unless it has its own. Rotate this secret via `POST \/account\/webhook-secret\/rotate`. A webhook whose signed_with is own_secret is signed with its own secret instead; read it from GET \/endpoints\/{id}\/signing-secret. A webhook's own secret rotates via `POST \/endpoints\/{id}\/signing-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":[]}],"x-primitive-permission":["webhook_secret:read"]}},"\/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":[]}],"x-primitive-permission":["webhook_secret:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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 (or the member it was transferred to), based on that user's current role, and a key without that authority gets the same `credit_code_invalid` refusal as an unknown code. A key transferred to another member without being rotated is refused with `403` and `error.details.reason` `key_transferred_unrotated`: rotate it and redeem with its successor. 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."}}}}}},"x-primitive-permission":["credits:redeem"]}},"\/credits\/balance":{"get":{"operationId":"getCreditBalance","summary":"Get credit balance","tags":["Credits"],"description":"Read the organization credit position. `prepaid_credit` is the prepaid usage credit (redeemed credit codes and granted credit) that can still pay for usage. `budget` is always null: agent spending budgets were retired, and the field is kept for existing clients. 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"}}}}}},"x-primitive-permission":["credits:read"]}},"\/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":[]}],"x-primitive-permission":["domains:read"]},"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":[]}],"x-primitive-permission":["domains:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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\/UpdatedDomain"}}}]}}},"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":[]}],"x-primitive-permission":["domains:manage"]},"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":[]}],"x-primitive-permission":["domains:manage"]}},"\/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, responds 400 `dns_verification_failed`; `error.details.records`\nlists the exact DNS records expected, each with its `status`. 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":"validation_error: the request is invalid. dns_verification_failed: the DNS check did not pass; `error.details.records` lists every expected record with its `status`.","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"}}}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["domains:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["domains:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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 for a domain claim. SPF is omitted when an existing SPF policy is observed or DNS lookup is inconclusive; read the file comments for the required manual SPF review. 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. Cannot be combined with inbound_only=true. By default, include all records."},{"name":"inbound_only","in":"query","schema":{"type":"boolean"},"description":"When true, include only inbound MX and ownership TXT records. Cannot be combined with outbound_only=true."}],"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"}}}}}},"x-primitive-permission":["domains:read"]}},"\/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":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/InboxStatus"}},"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"}}},"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"}}}}}},"x-primitive-permission":["domains:read"]}},"\/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. It filters only the mail the caller can read, and is never refused for that (200): a mailbox outside the caller's mail access lists nothing, and one the caller reads only in part (for example through an email rule) lists only that part. For a caller limited to some mailboxes, meta.recipient_outside_access or meta.recipient_rules_only says so when the mailbox is neither one of its addresses nor on one of its domains."},{"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":"from_verified","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"Only return emails whose `from_verified` (the From domain was authenticated on arrival; see the field) has this value. Combines with every other filter and with both `cursor` and `since`."},{"name":"include","in":"query","required":false,"schema":{"type":"string","maxLength":200,"example":"body_text,headers"},"description":"Extra parts on every row, comma-separated: `body_text`, `body_html`, `headers`, `attachments`. Reads a page of messages in one request instead of one detail read per row. Parts are withheld where the detail read withholds them. Each body field is cut to 64 KiB per row and a response carries at most 2048 KiB of body text, in page order; a cut or omitted body is named in `truncated_fields`. Unknown values are a `validation_error`."},{"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":"search_regex","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"With `true`, `search` is a POSIX regular expression matched against the subject, sender and recipient (case-insensitive unless `search_case_sensitive` is `true`). At most 100 characters; nested quantifiers, groups of five or more alternatives (nested groups included), backreferences and repeated range quantifiers are refused with 400. Needs a non-empty `search`; not with `wait`."},{"name":"search_case_sensitive","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"With `true`, `search` matches case exactly. Needs a non-empty `search`; not with `wait`."},{"name":"search_negate","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"With `true`, keep only emails whose subject, sender and recipient all fail to match `search` (an empty field counts as not matching). Needs a non-empty `search`; not with `wait`."},{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["regular","dsn","complaint","auto_reply","tls_report","dmarc_report","dmarc_failure"]},"description":"Only received messages of this kind: `regular` mail, a delivery status notification (`dsn`), a `complaint`, an `auto_reply`, or a `tls_report`, `dmarc_report` or `dmarc_failure` report."},{"name":"content_discarded","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"With `true`, only emails whose content was discarded after delivery; with `false`, only emails that still have it."},{"name":"group","in":"query","required":false,"schema":{"type":"string","enum":["message"]},"description":"With `message`, one row per message instead of one per mailbox copy (a message to several mailboxes of the org is stored once per mailbox). The row is the copy in your own mailbox (the `recipient` filter, then your bound address, then your member address), else the copy that arrived first, rejected or not, the same row in history and forward-tail paging. A message is listed when at least one copy you can read matches every filter at once: `awaiting=you` means some copy awaits you, `status=completed` that some copy completed, `search` that some copy matches; the row shown is still the representative. Under a connected agent key, which reads one mailbox, filters test the row itself. A filter other than `recipient` is tested copy by copy, so such a page is a bounded walk and can answer 504 `search_timeout`. With `thread_id`, only copies in that thread are grouped. `meta.total` counts messages. Each row adds `copies` and, when you can read the send, `sent_email_id`. A row can be replaced by a better-ranked copy that becomes visible later: drop any row whose id is in a newer row's `copies`. Paging history, a message whose representative was deleted or outranked between pages can show again on a later page under another id."},{"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\/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailSummary"}},"meta":{"$ref":"#\/components\/schemas\/EmailListMeta"}},"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"}}},"504":{"description":"search_timeout: searches are bounded, and this one read its whole budget without filling the page. Narrow the search, the date range or add more filters.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["mail:read"]}},"\/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:`, `header:`, `has-header:`). 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. Supports `from_verified:true` and `from_verified:false` to filter on the `from_verified` field. `header:Name=Value` matches mail whose header `Name` has exactly `Value` (name and value case-insensitive, the whole value, quote the term for a value with spaces: `header:\"List-Id=Team news <news.example.com>\"`); `has-header:Name` matches mail that carries header `Name` with any value. Neither can be an `OR` alternative. Header filters read a stored copy of each message's headers: for received mail every header except trace and signature headers (`Received`, `DKIM-Signature`, `ARC-*`, `Authentication-Results` and similar) and credential-like names (a word such as `auth`, `token`, `key`, `secret`, `cookie`, `session`, `bearer`, `jwt` or `hmac`, including joined forms such as `AuthToken` or `SessionId`), at most 100 headers and 16 KiB per message, each value cut to 1024 bytes; for sent mail the `headers` the request set. A filter on a header that is never stored is a `validation_error`. Mail stored before header search was available has no stored headers and is not matched."},{"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"},{"name":"from_verified","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"Only return emails whose `from_verified` (the From domain was authenticated on arrival; see the field) has this value. Also available in `q` as `from_verified:true` or `from_verified:false`."},{"name":"direction","in":"query","required":false,"schema":{"type":"string","enum":["inbound","outbound","any"],"default":"inbound"},"description":"Which mail to search: `inbound` (mail received by the organization, the default), `outbound` (mail it sent) or `any` (both, merged in date order). Each result carries `direction`. When sent mail is included, results are in date order only (`sort=relevance` is a `validation_error`), filters that describe received mail only (`status`, `spam_score_lt`, `spam_score_gte`, `awaiting`, `automated`, `reply_to_sent_email_id`, `from_verified`, and `status:`, `awaiting:`, `automated:`, `from_verified:` in `q`) are a `validation_error`, as is `include`, and `facets` are not computed. `meta.total` is computed on the first page only and is null on later pages. A `meta.cursor` continues the direction it was issued for: using it with another `direction` is a `validation_error`. Text search over sent mail (`q`, `subject` or `body`) answers 503 `sent_mail_search_unavailable` in an environment where sent-mail search is not enabled. An agent connection searches only the mail its own address sent. On a sent result, `received_at` is when the message was accepted for sending, `recipient` is its first To address, and fields that describe receiving (`spam_score`, `raw_size_bytes`, `webhook_status`) are null, as is `from_verified` (sent mail has no From-authentication verdict). A sent result carries its sending status (`queued`, `delivered`, `bounced` and so on) in `delivery_status`; its `status` stays in the received-mail vocabulary as the nearest reading of it: `pending` (queued, scheduled, deferred), `accepted` (handed off, outcome not confirmed), `completed` (delivered) or `rejected` (not delivered: failed, denied, bounced or canceled)."},{"name":"include","in":"query","required":false,"schema":{"type":"string","maxLength":200,"example":"body_text,headers"},"description":"Extra parts on every row, comma-separated: `body_text`, `body_html`, `headers`, `attachments`. Reads a page of messages in one request instead of one detail read per row. Parts are withheld where the detail read withholds them. Each body field is cut to 64 KiB per row and a response carries at most 2048 KiB of body text, in page order; a cut or omitted body is named in `truncated_fields`. Unknown values are a `validation_error`."}],"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"}}}}},"503":{"description":"Sent-mail text search is not enabled in this environment (`sent_mail_search_unavailable`). Returned only for text terms (`q`, `subject` or `body`) with `direction=outbound` or `direction=any`; retry with `direction=inbound`, or without text terms.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"504":{"description":"Search query timed out","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["mail:read"]}},"\/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":[]}],"x-primitive-permission":["mail:read"]},"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":[]}],"x-primitive-permission":["mail:delete"],"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"}}}}}},"x-primitive-permission":["mail.raw:read"]}},"\/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 of a received email as a gzip-compressed tar archive. Authenticate with a dashboard session, an organization API key, an authorized organization OAuth token, an address-bound agent connection key (only for mail received at its own address), or the signed download token a webhook payload carries (the `token` query parameter). A signed token that names who it was issued for works only while that access still reaches the email. Function credentials are refused, and so is access that reaches the mailbox only below content depth (403). Content the organization's sender filter withheld is not served.","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"}}},"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: with an agent connection key, the archive was replaced or the message changed while it was being read, so nothing is returned. Refresh the email detail before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"410":{"description":"content_discarded: with an agent connection key, the email content was discarded while the archive was being read.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"500":{"description":"The attachment archive could not be retrieved.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"attachment_not_ready: the archive is still being built. Retry after Retry-After.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying, as a whole number. Sent with every 503 code this operation lists.","schema":{"type":"integer","minimum":1}}}}},"x-primitive-permission":["mail:read"]}},"\/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. To select by filename, use the ByFilename operation (?filename=), or this path with a filename that is not digits only."}],"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"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying, as a whole number. Sent with every 503 code this operation lists.","schema":{"type":"integer","minimum":1}}}}},"x-primitive-permission":["mail:read"]}},"\/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. To select by filename, use the ByFilename operation (?filename=), or this path with a filename that is not digits only."}],"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"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying, as a whole number. Sent with every 503 code this operation lists.","schema":{"type":"integer","minimum":1}}}}},"x-primitive-permission":["mail:read"]}},"\/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":[]}],"x-primitive-permission":["mail:signal"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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 (a lower request 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"}}}}},"409":{"description":"latest_message_mismatch: if_latest_message_id is no longer the newest message in the thread. Nothing was sent. error.details carries latest_message_id (null when the thread has no message visible to this credential), latest_message_at and thread_id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"410":{"description":"The prior send was deleted. Its idempotency key remains reserved, and a delivered reply that was deleted also blocks a keyless reply from the same sender to the same inbound email (not an fyi signal or an agent connection key), so this request will not send another message. Pass a new Idempotency-Key to send a corrected reply.","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). Or latest_message_thread_unresolved: if_latest_message_id was given but in_reply_to resolves to no single thread this credential can check. Nothing was sent, and the same request will not succeed on retry. error.details.thread_id is null.","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`, `error.details.dedup_reason`, `error.details.deduplicated` and `error.details.original_sent_email_id`.","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`, `error.details.dedup_reason`, `error.details.deduplicated` and `error.details.original_sent_email_id`.","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`, `error.details.dedup_reason`, `error.details.deduplicated` and `error.details.original_sent_email_id`.","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`, `error.details.dedup_reason`, `error.details.deduplicated` and `error.details.original_sent_email_id`.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}},"headers":{"Idempotent-Replayed":{"$ref":"#\/components\/headers\/IdempotentReplayed"}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["mail:reply"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key does not perform the action a second time, so a network failure can be retried without duplicating side effects. A reply retried with the same key returns the original result; an interaction start or step retried with the same key returns the interaction as it stands now, which can differ from the first answer if it has moved on since.","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":[]}],"x-primitive-permission":["webhooks:replay"],"x-primitive-permission-alternatives":[["endpoints:manage"]],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["mail.state:write"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["mail:read"]}},"\/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":[]}],"x-primitive-permission":["webhooks:read"],"x-primitive-permission-alternatives":[["endpoints.pull:manage"]]},"post":{"operationId":"createEndpoint","summary":"Create a webhook endpoint","description":"Creates a new webhook endpoint. A URL can have one endpoint per domain, route targets included (unlike the one default destination per domain), and endpoints with no domain_id count as one domain. A disabled endpoint still holds its URL; a deleted one does not. A create at a URL another endpoint has on the same domain is refused (400). A deleted endpoint's URL can be used again: the create makes a new endpoint, with a new id, and the deleted one stays as it was. Subject to plan limits on the number of active\nendpoints.\n\n**Signing.** A webhook is signed with the organization's webhook secret (`GET \/account\/webhook-secret`) unless it has its own. A webhook made as a person's own, or by an API key limited to some mailboxes, a third-party app or an agent delegate for itself, is made with its own secret: the response carries it once as `signing_secret`, and `GET \/endpoints\/{id}\/signing-secret` returns it afterwards. Other webhooks, those a person who can read every mailbox makes among them, sign with the organization's secret until given their own. `signed_with` on the endpoint says which secret signs it. 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. Pulling or completing an account destination created earlier returns the same 403. 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\nAgent connection keys (pconn_) may use endpoints.pull:manage instead of endpoints:manage here, for their own pull subscriptions only.","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\/CreatedEndpoint"}}}]}}},"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\/CreatedEndpoint"}}}]}}}},"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":"The request conflicts with the current state of the resource"}}}}},"503":{"description":"Local receiving is unavailable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["endpoints:manage"],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `webhooks.write` (Create a webhook or change where it delivers), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session. Asked for only from the person's own Primitive CLI login, and only when the organization's credential policy sets `cli_writes_require_approval`: creating a webhook that delivers to a URL (`kind` `http`), or changing an endpoint's `url`. The token only approves the request, which is still made by the CLI login, and it is spent whatever the answer.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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. Changing the URL keeps the endpoint: its id, recipient routes, signing secret, delivery history and any deliveries still owed to it stay with it, and owed deliveries go to the new URL. A URL can have one endpoint per domain, route targets included (unlike the one default destination per domain), and endpoints with no domain_id count as one domain. A disabled endpoint still holds its URL; a deleted one does not. A change onto a URL another endpoint has on the same domain is refused (400), and a move onto a domain where another endpoint has this URL is refused (409).\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.\n\nGiving a webhook the organization does not own to a different key (owner_key_id), or a key's webhook back to the organization (owner_key_id null), turns it off unless the request also sets enabled: its URL was chosen under its previous owner. It then reads enabled false with disabled_reason owner_changed, and turning it on or giving it a new URL starts it again. A key that carries on for the owning key through rotation (its successor, at any depth up to 16 rotations) is not a different key: the webhook stays as it was, with the same signing secret. That does not hold when the owning key was made from a key a transfer moved or rotated away, or from one that has since been deleted: no successor carries on for it. The answer's warnings name each such change (turned_off_owner_changed, turned_on_new_url), so a client can say so.","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\/UpdatedEndpoint"}}}]}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":[]}],"x-primitive-permission":["endpoints:manage"],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `webhooks.write` (Create a webhook or change where it delivers), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session. Asked for only from the person's own Primitive CLI login, and only when the organization's credential policy sets `cli_writes_require_approval`: creating a webhook that delivers to a URL (`kind` `http`), or changing an endpoint's `url`. The token only approves the request, which is still made by the CLI login, and it is spent whatever the answer.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}]},"delete":{"operationId":"deleteEndpoint","summary":"Delete a webhook endpoint","description":"Delete a webhook endpoint. Inbound messages are no longer delivered to it.\n\nAgent connection keys (pconn_) may use endpoints.pull:manage instead of endpoints:manage here, for their own pull subscriptions only.","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":[]}],"x-primitive-permission":["endpoints:manage"]},"get":{"operationId":"getEndpoint","summary":"Get an endpoint","description":"One endpoint, as GET \/endpoints shows it, to a caller who would see it there. Any other id, a deleted endpoint's included, answers 404. For a webhook, delivery says whether it is being sent received mail at all, why not, and how much rule-granted mail is held while its owner cannot act.","tags":["Endpoints"],"responses":{"200":{"description":"The 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"}}},"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"}}}}},"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":[]}],"x-primitive-permission":["webhooks:read"],"x-primitive-permission-alternatives":[["endpoints.pull:manage"]]}},"\/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":"The request conflicts with the current state of the resource"}}}}},"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 (pull_unavailable), or the subscription was too busy to claim from in time (service_unavailable): nothing was claimed, so retry the pull after Retry-After","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["endpoints:pull"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":"The request conflicts with the current state of the resource"}}}}},"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":[]}],"x-primitive-permission":["endpoints:pull"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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\n\nThe test is sent whether or not real mail would be. reachable says whether the endpoint answered it with a 2xx; delivery says, apart from that, whether real email.received events would be sent to the endpoint by its own conditions (on, not held, an owner that can receive mail, subscribed to the event), with every reason it would not be. A 200 from the test alone does not mean mail arrives.","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":[]}],"x-primitive-permission":["endpoints:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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\n\nwould_deliver also covers the endpoint's own delivery conditions for this email (delivery): it is on, neither it nor the email's route is held, its owner may receive this email, and it subscribes to the event. Which endpoint routing sends the email to is decided separately: POST \/routes\/simulate answers that. A 503 means those conditions could not be read just now; retry.","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"}}}}},"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":[]}],"x-primitive-permission":["endpoints:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["filters:manage"]},"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":[]}],"x-primitive-permission":["filters:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["filters:manage"]},"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":[]}],"x-primitive-permission":["filters:manage"],"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":[]}],"x-primitive-permission":["wake:manage"]},"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":[]}],"x-primitive-permission":["wake:manage","mail:send"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["wake:manage"],"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":[]}],"x-primitive-permission":["wake:manage"]},"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":[]}],"x-primitive-permission":["wake:manage"],"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":[]}],"x-primitive-permission":["wake:manage","mail:send"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["wake:manage"]},"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":"The request conflicts with the current state of the resource"}}}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["wake:manage","endpoints:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["wake:manage","endpoints:manage"]},"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":[]}],"x-primitive-permission":["wake:manage","endpoints:manage"],"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":[]}],"x-primitive-permission":["wake:manage"]}},"\/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":[]}],"x-primitive-permission":["routes:manage"]},"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\nA route that could never deliver is refused: an exact pattern that the endpoint's owner may not receive, or that the route's own owner may not act on, answers 400 validation_error with details.reason endpoint_owner_access or route_owner_access and details.recipient. A wildcard or regex route to an owned endpoint is written, with warnings saying it delivers only what the owner may receive. A route written disabled is judged when it is turned on.","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":{"allOf":[{"$ref":"#\/components\/schemas\/RecipientRoute"},{"type":"object","required":["warnings"],"properties":{"warnings":{"type":"array","description":"What the route may not do as written. endpoint_owner_limited: its endpoint is owned by a key, a third-party app or agent, or a person limited to some mail, so it delivers only mail for recipients that owner may receive (for an outbound-only owner, only bounces). route_owner_limited: the same for the route's own owner. owner_unchecked: that could not be checked just now. Empty when nothing limits it. Check any recipient with POST \/v1\/routes\/simulate.","items":{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string","enum":["endpoint_owner_limited","route_owner_limited","owner_unchecked"]},"message":{"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"}}}}},"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":"The request conflicts with the current state of the resource"}}}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["routes:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["routes:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["routes:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":{"allOf":[{"$ref":"#\/components\/schemas\/RecipientRoute"},{"type":"object","required":["warnings"],"properties":{"warnings":{"type":"array","description":"What the route may not do as written. endpoint_owner_limited: its endpoint is owned by a key, a third-party app or agent, or a person limited to some mail, so it delivers only mail for recipients that owner may receive (for an outbound-only owner, only bounces). route_owner_limited: the same for the route's own owner. owner_unchecked: that could not be checked just now. Empty when nothing limits it. Check any recipient with POST \/v1\/routes\/simulate.","items":{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string","enum":["endpoint_owner_limited","route_owner_limited","owner_unchecked"]},"message":{"type":"string"}}}}}}]}}}]}}},"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":"The request conflicts with the current state of the resource"}}}}}},"description":"A route that could never deliver is refused: an exact pattern that the endpoint's owner may not receive, or that the route's own owner may not act on, answers 400 validation_error with details.reason endpoint_owner_access or route_owner_access and details.recipient. A wildcard or regex route to an owned endpoint is written, with warnings saying it delivers only what the owner may receive. A route written disabled is judged when it is turned on.","security":[{"BearerAuth":[]}],"x-primitive-permission":["routes:manage"]},"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":[]}],"x-primitive-permission":["routes:manage"],"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","skipped_by_rules","held","discarded"]},"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."},{"name":"event_type","in":"query","required":false,"description":"Only deliveries of this event type, for example sent_email.delivered. Deliveries recorded before event types existed have none and never match.","schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"sent_email_id","in":"query","required":false,"description":"Only deliveries of outbound events about this sent email.","schema":{"type":"string","format":"uuid"}},{"name":"endpoint_id","in":"query","required":false,"description":"Only deliveries to this endpoint (an id from listEndpoints). A caller limited to some mailboxes still sees only deliveries of mail it reads.","schema":{"type":"string","format":"uuid"}}],"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":[]}],"x-primitive-permission":["webhooks:read"]}},"\/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":[]}],"x-primitive-permission":["webhooks:replay"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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 a lower request body limit applies there.\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 (a lower request 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"}}}}},"409":{"description":"latest_message_mismatch: if_latest_message_id is no longer the newest message in the thread. Nothing was sent. error.details carries latest_message_id (null when the thread has no message visible to this credential), latest_message_at and thread_id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"410":{"description":"The prior send was deleted. Its idempotency key remains reserved, and a delivered reply that was deleted also blocks a keyless reply from the same sender to the same inbound email (not an fyi signal or an agent connection key), so this request will not send another message. Pass a new Idempotency-Key to send a corrected reply.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"latest_message_thread_unresolved: if_latest_message_id was given but in_reply_to resolves to no single thread this credential can check. Nothing was sent, and the same request will not succeed on retry. error.details.thread_id is null.","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`, `error.details.dedup_reason`, `error.details.deduplicated` and `error.details.original_sent_email_id`.","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`, `error.details.dedup_reason`, `error.details.deduplicated` and `error.details.original_sent_email_id`.","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`, `error.details.dedup_reason`, `error.details.deduplicated` and `error.details.original_sent_email_id`.","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`, `error.details.dedup_reason`, `error.details.deduplicated` and `error.details.original_sent_email_id`.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}},"headers":{"Idempotent-Replayed":{"$ref":"#\/components\/headers\/IdempotentReplayed"}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["mail:send"]}},"\/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":[]}],"x-primitive-permission":["mail.search:semantic"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["domains:read"],"x-primitive-permission-alternatives":[["mail:send"]]}},"\/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":[]}],"x-primitive-permission":["sent:manage"]}},"\/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":[]}],"x-primitive-permission":["sent:manage"],"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":[]}],"x-primitive-permission":["sent:manage"]},"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":[]}],"x-primitive-permission":["sent:manage"]}},"\/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":"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":"include","in":"query","required":false,"schema":{"type":"string","maxLength":200,"example":"body_text,headers"},"description":"Extra parts on every row, comma-separated: `body_text`, `body_html`, `headers`, `attachments`. Reads a page of messages in one request instead of one detail read per row. Parts are withheld where the detail read withholds them. Each body field is cut to 64 KiB per row and a response carries at most 2048 KiB of body text, in page order; a cut or omitted body is named in `truncated_fields`. Unknown values are a `validation_error`."},{"name":"header","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":1200},"description":"Return only sends that set this header: `Name=Value` matches that exact value, `Name` alone any value. Name and value compare case-insensitively; the value is trimmed and matched whole, not as a substring, and only the first `=` separates the two. Matches only the `headers` the send request set. History pages only: with `since` or `wait` it is a `validation_error`, as is a name that is never stored (trace, signature or credential-like). Sends made before header search was available are not matched."}],"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."}}}}},"504":{"description":"search_timeout: searches are bounded, and this one read its whole budget without filling the page. Narrow the search, the date range or add more filters.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"x-primitive-permission":["mail:read"]}},"\/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. Unknown IDs return 404. Needs sent:manage, and follows mail access rather than role: a caller with the whole organization's mail (an owner, admin or member whose access covers every mailbox, or an organization-wide key) can delete any send in the organization; a caller limited to some mailboxes, an owner or admin included, can delete only sends from addresses its access covers, made since that access starts (a member limited to their own mail: their own), and any other send returns 404. Repeating a completed deletion returns success; for a caller limited to some mailboxes, only when the send was from an address its access names one by one, otherwise 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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}},"x-primitive-permission":["sent:manage"]},"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":[]}],"x-primitive-permission":["mail:read"]},"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":[]}],"x-primitive-permission":["sent:manage"]}},"\/agent-connections":{"post":{"operationId":"createAgentConnection","tags":["Agent Connections"],"summary":"create Agent Connection","description":"Create an address-bound agent. An explicit address may be on a Primitive-managed domain or on one of the organization's own verified custom domains that receives and sends through Primitive; a narrower-than-organization creator needs agent_connections:custom_domains for a custom domain and a fresh address. Without an explicit address, allocate a readable name on a managed domain 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":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `agent_connections.mint` (Create, re-invite or rebind an agent connection), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":"The request conflicts with the current state of the resource"}}}}},"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-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-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}$"},"remove_others_access":{"type":"boolean"}},"required":["name"],"additionalProperties":false}}}},"x-primitive-permission":["agent_connections:manage"]},"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, own-address note reads and writes, reads of other organization agents' AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes, 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","meta"],"properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/AgentConnection"}},"meta":{"type":"object","required":["limit","cursor"],"properties":{"limit":{"type":"integer","description":"The page size this answer used."},"cursor":{"type":["string","null"],"description":"The connection id to pass as cursor for the next page; null on the last page."}}}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_connections:manage"]}},"\/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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `agent_connections.mint` (Create, re-invite or rebind an agent connection), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":"The request conflicts with the current state of the resource"}}}}},"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}}}},"x-primitive-permission":["agent_connections:manage"]}},"\/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-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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_connections:update"]}},"\/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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}},"x-primitive-permission":["agent_connections:manage"]}},"\/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, own-address note reads and writes, reads of other organization agents' AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes, 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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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-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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"api_key":{"type":"string"},"api_base_url":{"type":"string","format":"uri"},"challenge_marker":{"type":"string","pattern":"^primitive-connection:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:[1-9][0-9]*$"},"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-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":"The request conflicts with the current state of the resource"}}}}},"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}}}},"x-primitive-permission":["agent_connections:update"]}},"\/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":"The request conflicts with the current state of the resource"}}}}},"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":[]}],"x-primitive-permission":["sent:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["mail:read"]}},"\/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":[]}],"x-primitive-permission":["mail:read"]}},"\/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":[]}],"x-primitive-permission":["functions:read"]},"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. 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":[]}],"x-primitive-permission":["functions:deploy"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["functions:read"]},"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\/UpdateFunctionResult"}}}]}}},"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":[]}],"x-primitive-permission":["functions:deploy"]},"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":[]}],"x-primitive-permission":["functions:manage"]}},"\/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":[]}],"x-primitive-permission":["functions:deploy"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["functions:manage"]}},"\/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":[]}],"x-primitive-permission":["functions:read"]}},"\/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":[]}],"x-primitive-permission":["functions:read"]}},"\/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":[]}],"x-primitive-permission":["functions:manage"]},"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":[]}],"x-primitive-permission":["functions:manage"]}},"\/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":[]}],"x-primitive-permission":["secrets:manage"]},"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":[]}],"x-primitive-permission":["secrets:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["secrets:manage"]},"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":[]}],"x-primitive-permission":["secrets:manage"]}},"\/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":[]}],"x-primitive-permission":["secrets:manage"]},"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":[]}],"x-primitive-permission":["secrets:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"x-primitive-permission":["secrets:manage"]},"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":[]}],"x-primitive-permission":["secrets:manage"]}},"\/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":[]}],"x-primitive-permission":["functions:manage"]}},"\/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":"Function id UUID the Functions runner sets on a Function's own requests. For other credentials it is the default scope when the body does not include `scope`, and is ignored when `scope` is provided. A Function scope is available only to that Function's own credential, to an org-wide API key that can manage the Function, or to a person that can manage the Function; other credentials get `403 forbidden`.","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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["memories:write"]},"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":"Function id UUID the Functions runner sets on a Function's own requests. For other credentials it is the default scope when query scope parameters are omitted. A Function scope is available only to that Function's own credential, to an org-wide API key that can manage the Function, or to a person that can manage the Function; other credentials get `403 forbidden`.","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"}}}}}},"x-primitive-permission":["memories:read"]},"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":"Function id UUID the Functions runner sets on a Function's own requests. For other credentials it is the default scope when query scope parameters are omitted. A Function scope is available only to that Function's own credential, to an org-wide API key that can manage the Function, or to a person that can manage the Function; other credentials get `403 forbidden`.","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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["memories:write"]}},"\/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":"Function id UUID the Functions runner sets on a Function's own requests. For other credentials it is the default scope when query scope parameters are omitted. A Function scope is available only to that Function's own credential, to an org-wide API key that can manage the Function, or to a person that can manage the Function; other credentials get `403 forbidden`.","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"}}}}}},"x-primitive-permission":["memories:read"]}},"\/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"}}}}}}}},"\/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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_networks:read"]}},"\/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-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","meta"],"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-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":{"type":["string","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-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","required":["limit","cursor"],"properties":{"limit":{"type":"integer","description":"The page size this answer used."},"cursor":{"type":["string","null"],"description":"The address to pass as cursor for the next page; null on the last page."}}}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_networks:manage"]}},"\/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-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-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":{"type":["string","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-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":"The request conflicts with the current state of the resource"}}}}},"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}}}},"x-primitive-permission":["agent_networks:manage"]},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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-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":{"type":["string","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-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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_networks:manage"]},"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-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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_networks:manage"]}},"\/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-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","meta"],"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-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":{"type":["string","null"]}},"required":["user_id","name"],"additionalProperties":false},{"type":"null"}]}},"required":["address","name","last_seen_at","receiver","ownership_kind","owner"],"additionalProperties":false}},"meta":{"type":"object","required":["limit","cursor"],"properties":{"limit":{"type":"integer","description":"The page size this answer used."},"cursor":{"type":["string","null"],"description":"The address to pass as cursor for the next page; null on the last page."}}}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_networks:read"]}},"\/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-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-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":{"type":["string","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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_networks:read"]}},"\/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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":"The request conflicts with the current state of the resource"}}}}},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["email_id","sender_address"],"additionalProperties":false}}}},"x-primitive-permission":["agent_networks:admit"]}},"\/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 read only the contacts on their own agent contact list (another contact reads as 404) and always see display_name as null, 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","required":["limit","cursor"],"properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}},"required":["meta"]}]}}},"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"}}}}}},"x-primitive-permission":["contacts:read"]}},"\/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 read only the contacts on their own agent contact list (another contact reads as 404) and always see display_name as null, 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"}}}}}},"x-primitive-permission":["contacts:read"]},"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 read only the contacts on their own agent contact list (another contact reads as 404) and always see display_name as null, 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"}}}}}},"x-primitive-permission":["contacts:write"]},"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 read only the contacts on their own agent contact list (another contact reads as 404) and always see display_name as null, 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"}}}}}},"x-primitive-permission":["contacts:delete"]}},"\/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 read only the contacts on their own agent contact list (another contact reads as 404) and always see display_name as null, 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","required":["limit","cursor"],"properties":{"limit":{"type":"integer"},"cursor":{"type":["string","null"]}}}},"required":["meta"]}]}}},"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"}}}}}},"x-primitive-permission":["agent_contacts:manage"]}},"\/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 read only the contacts on their own agent contact list (another contact reads as 404) and always see display_name as null, 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"}}}}}},"x-primitive-permission":["agent_contacts:manage"]},"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 read only the contacts on their own agent contact list (another contact reads as 404) and always see display_name as null, 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"}}}}}},"x-primitive-permission":["agent_contacts:manage"]}},"\/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"}}}}}},"x-primitive-permission":["contacts:policy"]},"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"}}}}}},"x-primitive-permission":["contacts:policy"]}},"\/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"}}}}}},"x-primitive-permission":["agent_contact_policy:read"]},"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"}}}}}},"x-primitive-permission":["contacts:policy"]}},"\/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. email (your own sign-in email) and role_name (your role as the members list labels it) are set only when you are signed in as yourself, in the dashboard or with your own Primitive CLI or app login, and are null for API keys, Functions, connected agents, agent signup logins and other apps acting for you.","parameters":[{"name":"include","in":"query","required":false,"description":"Set to `grant` to add `grant`: the calling credential's permissions, mail scope, resources, template, credential kind and expiry. Other values are ignored.","schema":{"type":"string","maxLength":40}},{"name":"check","in":"query","required":false,"description":"A permission name. Adds `check`: whether this credential would be allowed it, and when not, the same `reason` and `details` a refusal carries. Changes nothing. An unknown permission is a 400 validation_error.","schema":{"type":"string","minLength":1,"maxLength":100}},{"name":"resource","in":"query","required":false,"description":"With `check`: the resource to decide it for, as `<type>:<id>` where type is `function`, `domain`, `endpoint` or `route`. Ignored without `check`.","schema":{"type":"string","minLength":1,"maxLength":220}}],"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":["string","null"]}},"required":["address","name"],"additionalProperties":false},{"type":"null"}]},"member_address_suggestion":{"type":["string","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":{"type":["string","null"]},"key_id":{"type":["string","null"]},"email":{"description":"Your own sign-in email, when you are signed in as yourself: in the dashboard, or with your own Primitive CLI or app login. Null for API keys, Functions, connected agents, agent signup logins and other apps acting for you.","type":["string","null"]},"role_name":{"description":"Your role as the organization's members list labels it: a template or custom role as \"<name> (template)\" or \"<name> (custom)\", otherwise Owner, Admin, Member or Restricted. Null for the same credentials as email.","type":["string","null"]},"grant":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"check":{"type":"object","properties":{"permission":{"type":"string"},"allow":{"type":"boolean"},"reason":{"type":"string"},"details":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"resource":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false}},"required":["permission","allow"],"additionalProperties":false}},"required":["member_address","member_address_suggestion","org_id","user_id","role","request_id","auth_method","key_id","email","role_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":"The request conflicts with the current state of the resource"}}}}},"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. An address someone else in the organization already uses (sends from, or received mail at) is refused for anyone but an owner; otherwise 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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"confirm_existing_mail":{"type":"boolean"},"remove_others_access":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":["string","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"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["member_address:manage"]}},"\/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-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":"The request conflicts with the current state of the resource"}}}}},"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}}}},"x-primitive-permission":["agent_connections:manage"]}},"\/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-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":"The request conflicts with the current state of the resource"}}}}},"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}}}},"x-primitive-permission":["agent_connections:update"]}},"\/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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_connections:manage"]},"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":"The request conflicts with the current state of the resource"}}}}},"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}}}},"x-primitive-permission":["agent_connections:manage"]}},"\/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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_connections:read_self"]}},"\/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"}}}}}}]}}}}}}},"\/agent-work\/agents\/{agentId}\/records\/{workId}":{"put":{"operationId":"agentWorkCreate","tags":["Agent Work"],"summary":"create","description":"Create immutable work scope for a current personal agent using a stable work UUID. An identical retry returns the current record; a different scope conflicts. Only its current human owner or bound connected key may create work. A proposed date is not owner agreement.","security":[{"BearerAuth":[]}],"parameters":[{"name":"agentId","in":"path","required":true,"description":"Current personal agent connection UUID.","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":"workId","in":"path","required":true,"description":"Work record UUID.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Current work state. Mutations are version checked; only an immediate identical retry is deduplicated.","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":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#\/components\/schemas\/AgentWorkRecord"}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":{"workTitle":{"type":"string","minLength":1,"maxLength":200},"workDetail":{"type":"string","minLength":1,"maxLength":2000},"proposedDeadlineAt":{"default":null,"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":["workTitle","workDetail"],"additionalProperties":false}}}},"x-primitive-permission":["agent_work:write"]},"get":{"operationId":"agentWorkGet","tags":["Agent Work"],"summary":"get","description":"Read work for a current personal agent. Only its current human owner or bound connected key may read it. Work from another connection generation or owner membership is unavailable. Text requires content depth at the agent address.","security":[{"BearerAuth":[]}],"parameters":[{"name":"agentId","in":"path","required":true,"description":"Current personal agent connection UUID.","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":"workId","in":"path","required":true,"description":"Work record UUID.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Current work state. Mutations are version checked; only an immediate identical retry is deduplicated.","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":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#\/components\/schemas\/AgentWorkRecord"}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_work:read"]}},"\/agent-work\/agents\/{agentId}\/records":{"get":{"operationId":"agentWorkList","tags":["Agent Work"],"summary":"list","description":"List current owned work in work UUID order. The current human owner or bound connected key may read it. nextCursor is null at the end; text requires content depth at the agent address.","security":[{"BearerAuth":[]}],"parameters":[{"name":"agentId","in":"path","required":true,"description":"Current personal agent connection UUID.","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":"cursor","in":"query","required":false,"description":"Last work UUID from the preceding page; omit for the first page.","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":"limit","in":"query","required":false,"description":"Number of records per page, from 1 to 100; defaults to 50.","schema":{"default":50,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Current work state. Mutations are version checked; only an immediate identical retry is deduplicated.","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":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#\/components\/schemas\/AgentWorkPage"}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["agent_work:read"]}},"\/agent-work\/agents\/{agentId}\/records\/{workId}\/deadline-proposal":{"post":{"operationId":"agentWorkProposeDeadline","tags":["Agent Work"],"summary":"propose Deadline","description":"Propose or clear a date without changing the current owner agreement. Requires active work and its expected version. The current human owner or bound connected key may propose.","security":[{"BearerAuth":[]}],"parameters":[{"name":"agentId","in":"path","required":true,"description":"Current personal agent connection UUID.","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":"workId","in":"path","required":true,"description":"Work record UUID.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Current work state. Mutations are version checked; only an immediate identical retry is deduplicated.","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":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#\/components\/schemas\/AgentWorkRecord"}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":{"expectedVersion":{"type":"integer","minimum":1,"maximum":2147483646},"proposedDeadlineAt":{"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":["expectedVersion","proposedDeadlineAt"],"additionalProperties":false}}}},"x-primitive-permission":["agent_work:write"]}},"\/agent-work\/agents\/{agentId}\/records\/{workId}\/status":{"post":{"operationId":"agentWorkReportStatus","tags":["Agent Work"],"summary":"report Status","description":"Report completion with attribution and clear the current agreement. Only the current human owner or bound connected key may report completion. Requires the expected version.","security":[{"BearerAuth":[]}],"parameters":[{"name":"agentId","in":"path","required":true,"description":"Current personal agent connection UUID.","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":"workId","in":"path","required":true,"description":"Work record UUID.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Current work state. Mutations are version checked; only an immediate identical retry is deduplicated.","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":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#\/components\/schemas\/AgentWorkRecord"}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":{"expectedVersion":{"type":"integer","minimum":1,"maximum":2147483646},"status":{"type":"string","const":"completed"}},"required":["expectedVersion","status"],"additionalProperties":false}}}},"x-primitive-permission":["agent_work:write"]}},"\/agent-work\/agents\/{agentId}\/records\/{workId}\/owner-status":{"post":{"operationId":"agentWorkManageState","tags":["Agent Work"],"summary":"manage State","description":"The current human owner may cancel or reopen work with its expected version. Reopening never restores a previous agreement. Agent-delegate and primitive-agent OAuth credentials cannot perform this action.","security":[{"BearerAuth":[]}],"parameters":[{"name":"agentId","in":"path","required":true,"description":"Current personal agent connection UUID.","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":"workId","in":"path","required":true,"description":"Work record UUID.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Current work state. Mutations are version checked; only an immediate identical retry is deduplicated.","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":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#\/components\/schemas\/AgentWorkRecord"}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":{"expectedVersion":{"type":"integer","minimum":1,"maximum":2147483646},"status":{"type":"string","enum":["active","cancelled"]}},"required":["expectedVersion","status"],"additionalProperties":false}}}},"x-primitive-permission":["agent_work:manage"]}},"\/agent-work\/agents\/{agentId}\/records\/{workId}\/deadline-agreements":{"post":{"operationId":"agentWorkAgreeDeadline","tags":["Agent Work"],"summary":"agree Deadline","description":"The current human owner explicitly agrees to an exact future date for the reviewed work version. Creates append-only agreement facts with a new agreement UUID. Replacing a date needs a new agreement ID. Agent-delegate and primitive-agent OAuth credentials cannot agree.","security":[{"BearerAuth":[]}],"parameters":[{"name":"agentId","in":"path","required":true,"description":"Current personal agent connection UUID.","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":"workId","in":"path","required":true,"description":"Work record UUID.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Current work state. Mutations are version checked; only an immediate identical retry is deduplicated.","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":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#\/components\/schemas\/AgentWorkRecord"}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"422":{"description":"The agreed date must be in the future.","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":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"expectedVersion":{"type":"integer","minimum":1,"maximum":2147483646},"agreementId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"dueAt":{"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":["expectedVersion","agreementId","dueAt"],"additionalProperties":false}}}},"x-primitive-permission":["agent_work:manage"]}},"\/agent-work\/agents\/{agentId}\/records\/{workId}\/deadline-withdrawal":{"post":{"operationId":"agentWorkWithdrawDeadline","tags":["Agent Work"],"summary":"withdraw Deadline","description":"The current human owner withdraws the current agreement with its expected version. Historical agreement facts remain immutable. Agent-delegate and primitive-agent OAuth credentials cannot withdraw.","security":[{"BearerAuth":[]}],"parameters":[{"name":"agentId","in":"path","required":true,"description":"Current personal agent connection UUID.","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":"workId","in":"path","required":true,"description":"Work record UUID.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Current work state. Mutations are version checked; only an immediate identical retry is deduplicated.","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":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#\/components\/schemas\/AgentWorkRecord"}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":{"expectedVersion":{"type":"integer","minimum":1,"maximum":2147483646}},"required":["expectedVersion"],"additionalProperties":false}}}},"x-primitive-permission":["agent_work:manage"]}},"\/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":"The name of the last note on the previous page, as meta.cursor gave it. Omit for the first page.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"The name of the last note on the previous page, as meta.cursor gave it. Omit for the first page."}},{"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. A connected agent credential writes only its own address's notes and reads all of them; of another live agent's address in its organization it reads only the AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes, and nothing of any other address. List and batch reads return only those notes; reading any other note name on another address returns 403, and one of those three names on an address that is not a live agent reads as not found. 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"}}}}}},"x-primitive-permission":["notes:read"]}},"\/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, does not see itself and gets only AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes. 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, does not see itself and gets only AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes. 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"}}}}}},"x-primitive-permission":["notes:read"]}},"\/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. A connected agent credential writes only its own address's notes and reads all of them; of another live agent's address in its organization it reads only the AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes, and nothing of any other address. List and batch reads return only those notes; reading any other note name on another address returns 403, and one of those three names on an address that is not a live agent reads as not found. 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"}}}}}},"x-primitive-permission":["notes:read"]},"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. A connected agent credential writes only its own address's notes and reads all of them; of another live agent's address in its organization it reads only the AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes, and nothing of any other address. List and batch reads return only those notes; reading any other note name on another address returns 403, and one of those three names on an address that is not a live agent reads as not found. 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"}}}},"x-primitive-permission":["notes:write"]},"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. A connected agent credential writes only its own address's notes and reads all of them; of another live agent's address in its organization it reads only the AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes, and nothing of any other address. List and batch reads return only those notes; reading any other note name on another address returns 403, and one of those three names on an address that is not a live agent reads as not found. 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"}}}}}},"x-primitive-permission":["notes:delete"]}},"\/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":"The name of the last note on the previous page, as meta.cursor gave it. Omit for the first page.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$","description":"The name of the last note on the previous page, as meta.cursor gave it. Omit for the first page."}},{"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. An address-bound agent connection key reads only the archives of mail sent from its own address. 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"}}}}},"409":{"description":"attachment_changed: with an agent connection key, the archive was replaced or the message changed while it was being read, so nothing is returned. Refresh the email detail before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"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"}}}}},"x-primitive-permission":["mail:read"]}},"\/sent-emails\/cancel":{"post":{"operationId":"cancelSentEmailByIdempotencyKey","summary":"Cancel a scheduled send by idempotency key","description":"Cancels the still-scheduled send created with this `Idempotency-Key` (matched exactly), as `POST \/sent-emails\/{id}\/cancel` does, and releases the key. 404 `not_found`: no send this credential can act on was created with the key. 409 `not_scheduled`: the send holding the key is no longer scheduled; `error.details` names its `sent_email_id` and `status` (`canceled` if already canceled). Pass `created_before` on a retry so it cannot reach a newer send that reused the released key. A connected agent can cancel any scheduled send from its own address, whoever created it, and no other.","tags":["Sending"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","additionalProperties":false,"required":["idempotency_key"],"properties":{"idempotency_key":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7e]+$","description":"The `Idempotency-Key` the send was created with."},"created_before":{"type":"string","format":"date-time","description":"Only consider sends created at or before this instant, such as when you first asked to cancel."}}}}}},"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":"`not_scheduled`: `error.details` carries the `sent_email_id` and current `status` of the send holding the key.","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"}}}}}},"x-primitive-permission":["sent:cancel"]}},"\/emails\/{id}\/attachments":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"parameters":[{"name":"filename","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":1024},"description":"The attachment's exact filename as shown in its metadata. Case-sensitive, compared after Unicode NFC normalization. Any name, digits only included. No other query parameter is accepted."}],"operationId":"downloadEmailAttachmentByFilename","summary":"Download one received email attachment by filename","description":"Returns the same bytes as downloadEmailAttachmentPart, selecting the attachment by its exact filename. The path form `\/emails\/{id}\/attachments\/{filename}` also works for any name that is not digits only, which that form reads as a part_index. When several attachments share the filename the response is 409 attachment_name_ambiguous with every matching part_index, and nothing is returned. Authenticate with an organization API key, an authorized organization OAuth token, or an address-bound agent connection key. A connected agent reads only mail received at its own address. Function credentials are not supported. 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 the returned bytes."},"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":"Missing or inaccessible message, or no attachment with this filename.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"attachment_name_ambiguous: more than one attachment has this filename, or a digits-only path value matches no part_index but is the name of an attachment; error.details.part_indexes lists every match, so fetch by part_index or by ?filename=. Or attachment_changed: attachment metadata changed during the read.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"410":{"description":"content_discarded","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"413":{"description":"attachment_limit_exceeded: the attachment exceeds a documented resource 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"}}}}},"502":{"description":"attachment_integrity_failed","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"attachment_not_ready or attachment_storage_unavailable; see Retry-After.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying, as a whole number. Sent with every 503 code this operation lists.","schema":{"type":"integer","minimum":1}}}}},"x-primitive-permission":["mail:read"]}},"\/emails\/{id}\/attachments\/{attachment}\/members":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"attachment","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":1024},"description":"The attachment: its exact part_index when the value is digits only, otherwise its exact filename (case-sensitive, compared after Unicode NFC normalization). A digits-only value always selects that part_index, even when another attachment has that name; read such a name with the ByFilename operation (?filename=)."}],"get":{"operationId":"listEmailAttachmentArchiveMembers","summary":"List the files inside a received zip or tar attachment","description":"Lists the regular files inside a zip, tar or tar.gz attachment with each one's path, decoded size and SHA-256. The format is detected from the content. Unsafe paths refuse the whole archive, and resource limits bound the work (see the 413 and 422 responses). Authenticate with an organization API key, an authorized organization OAuth token, or an address-bound agent connection key. A connected agent reads only mail received at its own address. Function credentials are not supported. Discarded content is unavailable.","tags":["Emails"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Files inside the archive","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ArchiveMembers"}}}]}}},"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":"Missing or inaccessible message, or no such attachment.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"attachment_name_ambiguous: more than one attachment has this filename, or a digits-only path value matches no part_index but is the name of an attachment; error.details.part_indexes lists every match, so fetch by part_index or by ?filename=. Or attachment_changed: attachment metadata changed during the read.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"410":{"description":"content_discarded","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"413":{"description":"archive_limit_exceeded, or attachment_limit_exceeded for the attachment itself. Limits: at most 1000 entries of any kind (files, directories and links all count); each file at most 31457280 bytes decoded; all files together (for tar.gz, the whole decoded tar stream) at most 33554432 bytes; a compression ratio above 100:1 is refused once decoded output passes 1048576 bytes; member paths at most 1024 UTF-8 bytes. Any limit exceeded is 413 archive_limit_exceeded.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"attachment_not_archive, archive_unsafe_path, archive_invalid or archive_unsupported. The format is detected from the content, not the filename: zip, tar, or tar inside gzip. An archive with an absolute, drive-letter, backslash, control-character, Unicode format or separator character (bidirectional controls included), empty, \".\" or \"..\" path segment is refused whole (422 archive_unsafe_path) rather than listed without the entry. Any symbolic or hard link is refused the same way (422 archive_unsafe_path), wherever it points. Paths that differ only by letter case or Unicode normalization count as duplicates. Duplicate paths, overlapping zip data, a zip local header whose name or method disagrees with the central directory, and truncated or corrupt structures are 422 archive_invalid; encryption, zip64, multi-volume archives, compression other than stored or deflate, sparse tar files, and device or other special files are 422 archive_unsupported. Directories are validated and counted but neither listed nor served.","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","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"attachment_not_ready, attachment_storage_unavailable or archive_busy; see Retry-After. archive_busy: this server is already reading as many archives as it allows at once. Nothing is wrong with the archive; retry after Retry-After.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying, as a whole number. Sent with every 503 code this operation lists.","schema":{"type":"integer","minimum":1}}}}},"x-primitive-permission":["mail:read"]}},"\/emails\/{id}\/attachments\/{attachment}\/members\/{path}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"attachment","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":1024},"description":"The attachment: its exact part_index when the value is digits only, otherwise its exact filename (case-sensitive, compared after Unicode NFC normalization). A digits-only value always selects that part_index, even when another attachment has that name; read such a name with the ByFilename operation (?filename=)."},{"name":"path","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":1024},"description":"The member path exactly as listed, percent-encoded as one path segment (slashes as %2F)."}],"get":{"operationId":"downloadEmailAttachmentArchiveMember","summary":"Download one file from inside a received zip or tar attachment","description":"Returns the decoded bytes of one regular file inside a zip, tar or tar.gz attachment, by its listed path. The archive's structure, every member path and every limit are checked as for the listing, but only the requested file is decoded and verified (its zip CRC-32, and X-Content-SHA256 of the bytes returned); list the archive to have every file decoded. Authenticate with an organization API key, an authorized organization OAuth token, or an address-bound agent connection key. A connected agent reads only mail received at its own address. Function credentials are not supported. Discarded content is unavailable.","tags":["Emails"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Decoded bytes of the archive member","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 the returned bytes."},"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":"Missing or inaccessible message, no such attachment, or no regular file at this path.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"attachment_name_ambiguous: more than one attachment has this filename, or a digits-only path value matches no part_index but is the name of an attachment; error.details.part_indexes lists every match, so fetch by part_index or by ?filename=. Or attachment_changed: attachment metadata changed during the read.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"410":{"description":"content_discarded","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"413":{"description":"archive_limit_exceeded, or attachment_limit_exceeded for the attachment itself. Limits: at most 1000 entries of any kind (files, directories and links all count); each file at most 31457280 bytes decoded; all files together (for tar.gz, the whole decoded tar stream) at most 33554432 bytes; a compression ratio above 100:1 is refused once decoded output passes 1048576 bytes; member paths at most 1024 UTF-8 bytes. Any limit exceeded is 413 archive_limit_exceeded.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"attachment_not_archive, archive_unsafe_path, archive_invalid or archive_unsupported. The format is detected from the content, not the filename: zip, tar, or tar inside gzip. An archive with an absolute, drive-letter, backslash, control-character, Unicode format or separator character (bidirectional controls included), empty, \".\" or \"..\" path segment is refused whole (422 archive_unsafe_path) rather than listed without the entry. Any symbolic or hard link is refused the same way (422 archive_unsafe_path), wherever it points. Paths that differ only by letter case or Unicode normalization count as duplicates. Duplicate paths, overlapping zip data, a zip local header whose name or method disagrees with the central directory, and truncated or corrupt structures are 422 archive_invalid; encryption, zip64, multi-volume archives, compression other than stored or deflate, sparse tar files, and device or other special files are 422 archive_unsupported. Directories are validated and counted but neither listed nor served.","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","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"attachment_not_ready, attachment_storage_unavailable or archive_busy; see Retry-After. archive_busy: this server is already reading as many archives as it allows at once. Nothing is wrong with the archive; retry after Retry-After.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying, as a whole number. Sent with every 503 code this operation lists.","schema":{"type":"integer","minimum":1}}}}},"x-primitive-permission":["mail:read"]}},"\/sent-emails\/{id}\/attachments":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"parameters":[{"name":"filename","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":1024},"description":"The attachment's exact filename as shown in its metadata. Case-sensitive, compared after Unicode NFC normalization. Any name, digits only included. No other query parameter is accepted."}],"operationId":"downloadSentAttachmentByFilename","summary":"Download one sent email attachment by filename","description":"Returns the same bytes as downloadSentAttachmentPart, selecting the attachment by its exact filename. The path form `\/sent-emails\/{id}\/attachments\/{filename}` also works for any name that is not digits only, which that form reads as a part_index. When several attachments share the filename the response is 409 attachment_name_ambiguous with every matching part_index, and nothing is returned. Authenticate with an organization API key, an authorized organization OAuth token, or an address-bound agent connection key. A connected agent reads only mail sent from its own address. Function credentials are not supported. 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 the returned bytes."},"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":"Missing or inaccessible message, or no attachment with this filename.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"attachment_name_ambiguous: more than one attachment has this filename, or a digits-only path value matches no part_index but is the name of an attachment; error.details.part_indexes lists every match, so fetch by part_index or by ?filename=. Or attachment_changed: attachment metadata changed during the read.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"410":{"description":"content_discarded","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"413":{"description":"attachment_limit_exceeded: the attachment exceeds a documented resource 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"}}}}},"502":{"description":"attachment_integrity_failed","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"attachment_not_ready or attachment_storage_unavailable; see Retry-After.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying, as a whole number. Sent with every 503 code this operation lists.","schema":{"type":"integer","minimum":1}}}}},"x-primitive-permission":["mail:read"]}},"\/sent-emails\/{id}\/attachments\/{attachment}\/members":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"attachment","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":1024},"description":"The attachment: its exact part_index when the value is digits only, otherwise its exact filename (case-sensitive, compared after Unicode NFC normalization). A digits-only value always selects that part_index, even when another attachment has that name; read such a name with the ByFilename operation (?filename=)."}],"get":{"operationId":"listSentAttachmentArchiveMembers","summary":"List the files inside a sent zip or tar attachment","description":"Lists the regular files inside a zip, tar or tar.gz attachment with each one's path, decoded size and SHA-256. The format is detected from the content. Unsafe paths refuse the whole archive, and resource limits bound the work (see the 413 and 422 responses). Authenticate with an organization API key, an authorized organization OAuth token, or an address-bound agent connection key. A connected agent reads only mail sent from its own address. Function credentials are not supported. Discarded content is unavailable.","tags":["Sending"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Files inside the archive","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ArchiveMembers"}}}]}}},"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":"Missing or inaccessible message, or no such attachment.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"attachment_name_ambiguous: more than one attachment has this filename, or a digits-only path value matches no part_index but is the name of an attachment; error.details.part_indexes lists every match, so fetch by part_index or by ?filename=. Or attachment_changed: attachment metadata changed during the read.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"410":{"description":"content_discarded","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"413":{"description":"archive_limit_exceeded, or attachment_limit_exceeded for the attachment itself. Limits: at most 1000 entries of any kind (files, directories and links all count); each file at most 31457280 bytes decoded; all files together (for tar.gz, the whole decoded tar stream) at most 33554432 bytes; a compression ratio above 100:1 is refused once decoded output passes 1048576 bytes; member paths at most 1024 UTF-8 bytes. Any limit exceeded is 413 archive_limit_exceeded.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"attachment_not_archive, archive_unsafe_path, archive_invalid or archive_unsupported. The format is detected from the content, not the filename: zip, tar, or tar inside gzip. An archive with an absolute, drive-letter, backslash, control-character, Unicode format or separator character (bidirectional controls included), empty, \".\" or \"..\" path segment is refused whole (422 archive_unsafe_path) rather than listed without the entry. Any symbolic or hard link is refused the same way (422 archive_unsafe_path), wherever it points. Paths that differ only by letter case or Unicode normalization count as duplicates. Duplicate paths, overlapping zip data, a zip local header whose name or method disagrees with the central directory, and truncated or corrupt structures are 422 archive_invalid; encryption, zip64, multi-volume archives, compression other than stored or deflate, sparse tar files, and device or other special files are 422 archive_unsupported. Directories are validated and counted but neither listed nor served.","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","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"attachment_not_ready, attachment_storage_unavailable or archive_busy; see Retry-After. archive_busy: this server is already reading as many archives as it allows at once. Nothing is wrong with the archive; retry after Retry-After.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying, as a whole number. Sent with every 503 code this operation lists.","schema":{"type":"integer","minimum":1}}}}},"x-primitive-permission":["mail:read"]}},"\/sent-emails\/{id}\/attachments\/{attachment}\/members\/{path}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"},{"name":"attachment","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":1024},"description":"The attachment: its exact part_index when the value is digits only, otherwise its exact filename (case-sensitive, compared after Unicode NFC normalization). A digits-only value always selects that part_index, even when another attachment has that name; read such a name with the ByFilename operation (?filename=)."},{"name":"path","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":1024},"description":"The member path exactly as listed, percent-encoded as one path segment (slashes as %2F)."}],"get":{"operationId":"downloadSentAttachmentArchiveMember","summary":"Download one file from inside a sent zip or tar attachment","description":"Returns the decoded bytes of one regular file inside a zip, tar or tar.gz attachment, by its listed path. The archive's structure, every member path and every limit are checked as for the listing, but only the requested file is decoded and verified (its zip CRC-32, and X-Content-SHA256 of the bytes returned); list the archive to have every file decoded. Authenticate with an organization API key, an authorized organization OAuth token, or an address-bound agent connection key. A connected agent reads only mail sent from its own address. Function credentials are not supported. Discarded content is unavailable.","tags":["Sending"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Decoded bytes of the archive member","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 the returned bytes."},"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":"Missing or inaccessible message, no such attachment, or no regular file at this path.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"attachment_name_ambiguous: more than one attachment has this filename, or a digits-only path value matches no part_index but is the name of an attachment; error.details.part_indexes lists every match, so fetch by part_index or by ?filename=. Or attachment_changed: attachment metadata changed during the read.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"410":{"description":"content_discarded","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"413":{"description":"archive_limit_exceeded, or attachment_limit_exceeded for the attachment itself. Limits: at most 1000 entries of any kind (files, directories and links all count); each file at most 31457280 bytes decoded; all files together (for tar.gz, the whole decoded tar stream) at most 33554432 bytes; a compression ratio above 100:1 is refused once decoded output passes 1048576 bytes; member paths at most 1024 UTF-8 bytes. Any limit exceeded is 413 archive_limit_exceeded.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"attachment_not_archive, archive_unsafe_path, archive_invalid or archive_unsupported. The format is detected from the content, not the filename: zip, tar, or tar inside gzip. An archive with an absolute, drive-letter, backslash, control-character, Unicode format or separator character (bidirectional controls included), empty, \".\" or \"..\" path segment is refused whole (422 archive_unsafe_path) rather than listed without the entry. Any symbolic or hard link is refused the same way (422 archive_unsafe_path), wherever it points. Paths that differ only by letter case or Unicode normalization count as duplicates. Duplicate paths, overlapping zip data, a zip local header whose name or method disagrees with the central directory, and truncated or corrupt structures are 422 archive_invalid; encryption, zip64, multi-volume archives, compression other than stored or deflate, sparse tar files, and device or other special files are 422 archive_unsupported. Directories are validated and counted but neither listed nor served.","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","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"attachment_not_ready, attachment_storage_unavailable or archive_busy; see Retry-After. archive_busy: this server is already reading as many archives as it allows at once. Nothing is wrong with the archive; retry after Retry-After.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying, as a whole number. Sent with every 503 code this operation lists.","schema":{"type":"integer","minimum":1}}}}},"x-primitive-permission":["mail:read"]}},"\/interactions":{"get":{"operationId":"listInteractions","summary":"List interactions","description":"The organization's interactions, newest first by creation, with their state, whose turn it is and when the last step happened. Reading needs interactions:read and organization-wide mail scope; a credential narrowed to some mailboxes is refused.","tags":["Interactions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"state","in":"query","required":false,"description":"open (not yet finished), completed, failed, or all. Defaults to all.","schema":{"type":"string","enum":["open","completed","failed","all"]}},{"name":"protocol","in":"query","required":false,"description":"Only this protocol, for example ack-request or approval.","schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"wire_id","in":"query","required":false,"description":"Only the interaction with this cross-organization wire id (uuid@domain).","schema":{"type":"string","minLength":1,"maxLength":512}},{"name":"email_id","in":"query","required":false,"description":"Only interactions that have a step carried by this inbound email or sent email.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of items to return.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous page's next_cursor. Omit for the first page.","schema":{"type":"string","minLength":1,"maxLength":512}}],"responses":{"200":{"description":"List interactions","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListInteractionsResult"}}}]}}},"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":"feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization, and details.feature is interactions or wake_dispatch; contact Primitive to turn them on. grant_forbidden: the credential does not hold the permission","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"}}}}}},"x-primitive-permission":["interactions:read"]},"post":{"operationId":"startInteraction","summary":"Start an interaction","description":"Send the first step of a protocol from one of your addresses: a carrier email with the step's interaction.json part. The step is validated against the protocol before anything is sent. An Idempotency-Key makes a retry return the same interaction without sending again.","tags":["Interactions"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/StartInteractionRequest"}}}},"responses":{"200":{"description":"Start an interaction","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/StartInteractionResult"}}}]}}},"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":"feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization, and details.feature is interactions or wake_dispatch; contact Primitive to turn them on. grant_forbidden: the credential does not hold the permission","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"}}}}}},"x-primitive-permission":["interactions:send","mail:send"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key does not perform the action a second time, so a network failure can be retried without duplicating side effects. A reply retried with the same key returns the original result; an interaction start or step retried with the same key returns the interaction as it stands now, which can differ from the first answer if it has moved on since.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/interactions\/{id}":{"get":{"operationId":"getInteraction","summary":"Get an interaction and its steps","description":"One interaction with every step it has processed, oldest first, including rejected steps and their reason. Each step links the inbound email (email_id) or sent email (sent_email_id) that carried it, or null when that message no longer exists. Reading needs interactions:read and organization-wide mail scope; a credential narrowed to some mailboxes is refused.","tags":["Interactions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The interaction id (interaction_id in the send response).","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get an interaction and its steps","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetInteractionResult"}}}]}}},"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":"feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization, and details.feature is interactions or wake_dispatch; contact Primitive to turn them on. grant_forbidden: the credential does not hold the permission","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"}}}}},"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"}}}}}},"x-primitive-permission":["interactions:read"]}},"\/interactions\/{id}\/steps":{"post":{"operationId":"sendInteractionStep","summary":"Send the next step of an interaction","description":"Send the next step from our side of an existing interaction, validated against its current state. An interaction on a mailbox the caller cannot send from is answered as not found.","tags":["Interactions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The interaction id (interaction_id in the send response).","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied idempotency key. Retrying a request with the same key does not perform the action a second time, so a network failure can be retried without duplicating side effects. A reply retried with the same key returns the original result; an interaction start or step retried with the same key returns the interaction as it stands now, which can differ from the first answer if it has moved on since.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SendInteractionStepRequest"}}}},"responses":{"200":{"description":"Send the next step of an interaction","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/SendInteractionStepResult"}}}]}}},"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":"feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization, and details.feature is interactions or wake_dispatch; contact Primitive to turn them on. grant_forbidden: the credential does not hold the permission","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"}}}}},"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"}}}}}},"x-primitive-permission":["interactions:advance","mail:send"]}},"\/stats\/emails\/hourly":{"get":{"operationId":"getInboundHourlyStats","summary":"Received mail per hour","description":"Received mail per UTC hour over the last `days` days, by outcome: accepted, failing (every webhook delivery so far failed) and rejected. Hours with no mail are left out. Needs mail:read. Counts only the mail the credential may read: a credential narrowed to some addresses counts the mail its scope admits.","tags":["Stats"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"days","in":"query","required":false,"description":"How many days back, from 1 to 31; defaults to 8.","schema":{"default":8,"description":"How many days back, from 1 to 31; defaults to 8.","type":"integer","minimum":1,"maximum":31}}],"responses":{"200":{"description":"Received mail per hour","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetInboundHourlyStatsResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/stats\/sent-emails\/hourly":{"get":{"operationId":"getSentHourlyStats","summary":"Sent mail per hour","description":"Sent mail per UTC hour over the last `days` days, by outcome: delivered, bounced (including agent failures, wait timeouts and unknown endings) and denied by a send gate. Sends still in flight are not counted until they end. Hours with no mail are left out. Needs mail:read. Counts only the mail the credential may read: a credential narrowed to some addresses counts the mail its scope admits.","tags":["Stats"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"days","in":"query","required":false,"description":"How many days back, from 1 to 31; defaults to 8.","schema":{"default":8,"description":"How many days back, from 1 to 31; defaults to 8.","type":"integer","minimum":1,"maximum":31}}],"responses":{"200":{"description":"Sent mail per hour","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetSentHourlyStatsResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/stats\/webhook-deliveries":{"get":{"operationId":"getWebhookDeliveryStats","summary":"Webhook delivery outcomes","description":"For each received email with a webhook delivery since `since`, how its latest delivery ended: clean_success, recovered (delivered after a failure), failed, or regressed (failed after a success). Pull deliveries are not counted, and neither are deliveries an email rule granted to a key: they are extra receivers and never change how the email itself was delivered. Needs webhooks:read, which a credential narrowed to some addresses never holds.","tags":["Stats"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"since","in":"query","required":false,"description":"Count emails whose deliveries started at or after this time; defaults to 24 hours ago, and is held to at most 31 days back.","schema":{"description":"Count emails whose deliveries started at or after this time; defaults to 24 hours ago, at most 31 days back.","type":"string","format":"date-time"}}],"responses":{"200":{"description":"Webhook delivery outcomes","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetWebhookDeliveryStatsResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["webhooks:read"]}},"\/activity\/objects":{"get":{"operationId":"listActivityObjects","summary":"List what you created, or what you were asked","description":"The objects the caller created, newest first, each with where it stands now, including ones since disabled, deleted or revoked where the object keeps a row: Functions, endpoints, Function triggers, pull subscriptions, recipient routes, filters, wake schedules, repeating sends and scheduled sends. An API key, an app or an agent sees what it created itself; a webhook given to an API key lists under that key. A person (a dashboard session, or their own Primitive CLI or app login) sees what they created through any credential of theirs, each item naming it, and also their API keys, personal agents, email rules, rule requests and objections to rules over their mail (objections only on the dashboard or the Primitive CLI). Each kind is listed only if the caller could list it through that kind's own route; a kind it could not is named in withheld, with no count. Ids, names and states only: never a webhook URL or mail content. Needs activity:read_self. With relation=asked it lists instead what other members asked of the person, open or answered: the rule requests made to them, and the objections to rules they wrote, under their current membership, each naming the member who asked or objected in created_through. A key, an app or an agent has none, and gets both kinds in withheld. With relation=concerning it lists instead what other members did that concerns the person's own mail: each time a rule started covering one of their mailboxes, saved a new version while covering it, or stopped covering it, named as the rule was then, naming its author in created_through, under their current membership; only on the dashboard or the Primitive CLI. A key, an app or an agent has none, and gets email_rule_coverage in withheld. A notice never reads as ended. Notices are written by a periodic job and can lag: until the notices record a rule starting to cover you, the first page lists that rule as pending (pending_complete says whether that check finished), and as_of says about when the notices last looked. For what covers you now, read GET \/email-rules\/covering-me.","tags":["Activity"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"relation","in":"query","required":false,"description":"created (the default), asked or concerning.","schema":{"default":"created","description":"created (the default): what you created. asked: what other members asked of you, open or answered: rule requests made to you, and objections to rules you wrote; kind may then name only email_rule_request and email_rule_objection. concerning: what other members did that concerns your own mail: an active rule that started covering one of your mailboxes, saved a new version while covering it, or stopped covering it (email_rule_coverage, the only kind it takes). A rule in draft or observe is not told of; GET \/v1\/email-rules\/covering-me lists it. asked and concerning are for a person's own view only.","type":"string","enum":["created","asked","concerning"]}},{"name":"kind","in":"query","required":false,"description":"Only these kinds, comma-separated: function, endpoint, function_trigger, pull_subscription, recipient_route, filter_rule, wake_schedule, repeating_send, scheduled_send, api_key, agent_connection, email_rule, email_rule_request, email_rule_objection, email_rule_coverage. Defaults to every kind the caller can have created (a person's own kinds only for a person). With relation=asked, only email_rule_request and email_rule_objection, which are also the default; with relation=concerning, only email_rule_coverage, the default. email_rule_coverage is listed only with relation=concerning.","schema":{"type":"string","minLength":1,"maxLength":400}},{"name":"state","in":"query","required":false,"description":"all (the default), live (still there, enabled or not) or ended (deleted, replaced or finished).","schema":{"default":"all","type":"string","enum":["all","live","ended"]}},{"name":"limit","in":"query","required":false,"description":"Objects per page, from 1 to 200; defaults to 50. With relation=concerning the first page may also hold pending items beyond it.","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"next_cursor from the preceding page. Omit for the first page.","schema":{"type":"string","maxLength":500}}],"responses":{"200":{"description":"List what you created, or what you were asked","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListActivityObjectsResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["activity:read_self"]}},"\/email-rules\/vocabulary":{"get":{"operationId":"getEmailRuleVocabulary","summary":"Read the column catalog","description":"What a rule tree you write yourself can say: each column of the current vocabulary with its type, source, meaning, operators and the value each takes, options, and whether it takes min_confidence; and the tree's limits, its stored size included. Built from the checks a saved or tested tree must pass. A tree is {version: tree_version, vocabulary: version, when, questions}. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Read the column catalog","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetEmailRuleVocabularyResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rules\/compile":{"post":{"operationId":"compileEmailRule","summary":"Compile a rule description","description":"Turns a plain-English description of which received emails to match into a rule tree over the column contract, with an explanation of what it matches, the assumptions made, and warnings for what the columns cannot express. Where the columns fall short it adds up to five yes\/no questions, asked only after the conditions match; they read the subject, the new text and attachment file names, never attachment contents. Nothing is saved. For a rule with no questions, breadth counts, read only, how much of the recent classified mail in evaluation_scope (by default your own mailboxes) its conditions match; for a grant rule with questions, conditions_breadth counts its conditions alone. include_sql returns the SQL it renders to. kind do_not_share compiles for recall. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CompileEmailRuleRequest"}}}},"responses":{"200":{"description":"Compile a rule description","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CompileEmailRuleResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. email_rule_scope_not_readable: scope_selection names something you cannot read.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_author_unresolvable: with evaluation_scope selected, you cannot act as a rule's author right now (details.reason left, revoked, suspended, membership_ended or no_mail_read).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"email_rule_not_compiled: the description could not be turned into a valid rule (details.reason is refused, invalid or truncated).","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":"email_rule_compiler_unavailable: the rule compiler is not available or could not be reached.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["mail:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/email-rules\/test":{"post":{"operationId":"testEmailRule","summary":"Test a rule against received mail","description":"Runs a rule over received mail and reports what it would decide for each email (matched, near_miss, no_match, unclassified, skipped or not_found), with a summary of how each top-level condition and question decided. Test an unsaved tree, a saved rule by rule_id (its current version, as its author), or a pending rule request by request_id (its rule as asked, as you, over your own mailboxes). It reads only mail its author can read and its scope covers. A rule you see only because it covers your own mailboxes runs as you, over only those, from the dashboard or the Primitive CLI. Without email_ids it tests the newest emails, at most 200 and none older than 30 days, plus any expected ones. Unclassified emails over 2 hours old are classified first unless classify_missing is false. It records no decision and grants nothing, and stops at about 90 seconds. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TestEmailRuleRequest"}}}},"responses":{"200":{"description":"Test a rule against received mail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/TestEmailRuleResult"}}}]}}},"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":"validation_error: give exactly one of tree, rule_id and request_id, at most 200 email_ids (with the expected emails), 1 to 100 expected emails none in both lists (or only expect.samples, up to 20), and a tree that is valid for the column contract. evaluation_scope, scope_selection and kind go only with tree. A saved rule's current version or a request's rule that no longer renders answers details.reason version_not_renderable; email_ids that with its saved expectations name over 200 emails, too_many_emails.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. grant_forbidden with details.reason mail_depth: your access to mail shows headers only, and a rule decides on what emails say. email_rule_scope_not_readable: scope_selection names something you cannot read. forbidden with details.reason covering_view_only: you read this rule only in the covering view, since it decided on your mail before and covers none of your mailboxes now (details.covering_view).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: rule_id names no rule you can see or that covers your mailboxes (details.reason rule_not_found), or request_id no rule request you can see (request_not_found).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_author_unresolvable: the rule cannot run as its author (for an unsaved tree, a request or a rule covering you, the caller). details.reason is left, revoked, suspended, membership_ended, no_mail_read, no_own_mailbox, no_shareable_mailbox or no_selected_mailbox. email_rule_request_not_pending: request_id names a request already decided.","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"}}}}}},"x-primitive-permission":["mail:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/email-rules\/look-alikes":{"post":{"operationId":"generateLookAlikes","summary":"Write look-alike emails a rule should or should not match","description":"Writes made-up emails that come close to what a grant rule describes, and reports what the rule decides for each, as test-sample would. Most should not match (expected no_match): business near misses from its senders and topic (kind other: an all-clear, a reply with nothing new, a related document, another subject) and private mail (a personal medical bill, pay for named people, a personal purchase, personal banking or tax). By default a third, rounded down, are near cases it should match (expected match, kind other). failed means the rule decided one against expected. Nothing is stored and no mail is read. Keep them, with their expected, with add_expectation_samples, or for a request with the accept's expectation_samples. Limited to 20 an hour per member. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/GenerateLookAlikesRequest"}}}},"responses":{"200":{"description":"Write look-alike emails a rule should or should not match","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GenerateLookAlikesResult"}}}]}}},"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":"validation_error: give exactly one of tree, rule_id and request_id, and a tree valid for the column contract; details.reason do_not_share for a do-not-share rule, no_address when no sign-in address was found and no to given, version_not_renderable for a saved version or a request's rule that no longer fits.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. email_rule_scope_not_readable: scope_selection names something you cannot read. forbidden with details.reason covering_view_only: you read this rule only in the covering view, since it decided on your mail before and covers none of your mailboxes now (details.covering_view).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: rule_id names no rule you can see or that covers your mailboxes (details.reason rule_not_found), or request_id no rule request you can see (request_not_found).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_author_unresolvable: the rule cannot run as its author (for an unsaved tree, a request or a rule covering you, you). email_rule_request_not_pending: request_id names a request already decided.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"email_rule_look_alikes_not_written: the model declined, ran out of room or time, or wrote none that fit (details.reason refused, truncated, deadline or invalid).","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":"email_rule_compiler_unavailable: the model, or the classifier that decides each, is not available or could not be reached.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["mail:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/email-rules\/test-sample":{"post":{"operationId":"testEmailRuleOnSample","summary":"Test a rule against a made-up email","description":"Reports what a rule would decide for an email you describe (sender, recipients, subject, plain text, attachment types and file names; optionally automated, from a mailing list, and a spam score), classified and decided as received mail would be. A rule reading a spam score you gave none for decides on none (unset_columns). Nothing is stored. Test an unsaved tree, a saved rule by rule_id, or a pending rule request by request_id (its rule as asked, as you). The answer includes what the classifier read. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TestEmailRuleOnSampleRequest"}}}},"responses":{"200":{"description":"Test a rule against a made-up email","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/TestEmailRuleOnSampleResult"}}}]}}},"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":"validation_error: give exactly one of tree, rule_id and request_id (evaluation_scope, scope_selection and kind only with tree), an email with a sender, 1 to 50 To addresses (and up to 50 Cc), and a text body, and a tree that is valid for the column contract and within the size limits for a saved rule. A saved rule's current version or a request's rule that no longer renders answers details.reason version_not_renderable. With with_protection: not_a_grant_rule, or protection_mailbox_outside_rule when the rule does not run over the first recipient.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. email_rule_scope_not_readable: scope_selection names something you cannot read. forbidden with details.reason covering_view_only: you read this rule only in the covering view, since it decided on your mail before and covers none of your mailboxes now (details.covering_view). grant_forbidden protection_mailbox_not_read: with_protection at a mailbox you do not read in full.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: rule_id names no rule you can see or that covers your mailboxes (details.reason rule_not_found), or request_id no rule request you can see (request_not_found).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_author_unresolvable: the rule cannot run as its author (for an unsaved tree, a request or a rule covering you, you), so it would decide nothing for real. details.reason is left, revoked, suspended, membership_ended, no_mail_read, no_own_mailbox, no_shareable_mailbox or no_selected_mailbox. email_rule_request_not_pending: request_id names a request already decided.","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":"service_unavailable: with_protection could not read the do-not-share rules in time (protection_unavailable), or its limit could not be checked (protection_limit_unavailable).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["mail:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/email-rules":{"post":{"operationId":"createEmailRule","summary":"Create a rule","description":"Saves a rule as version 1 from a compile response or a tree you wrote, checked against the column contract. It starts in observe (recorded, nothing granted or blocked) unless state is draft, or active for a do_not_share rule. You are its author, and it never reaches mail you cannot read. evaluation_scope is own_mailboxes unless you name author or selected (scope_selection names only what you can read). expectations are judged in expectations_check. kind do_not_share blocks sharing what it matches; a key cannot list it (details.reason rule_blocks_sharing). Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateEmailRuleRequest"}}}},"responses":{"201":{"description":"Create a rule","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CreateEmailRuleResult"}}}]}}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. email_rule_scope_not_readable: scope_selection names something you cannot read (details.addresses, details.domains).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_author_unresolvable: the rule could not run as you over the chosen scope. details.reason is left, revoked, suspended, membership_ended, no_mail_read, no_own_mailbox or no_shareable_mailbox. With no evaluation_scope it covers only your own mail, so no_own_mailbox or no_shareable_mailbox there means a scope must be named.","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"}}}}}},"x-primitive-permission":["mail:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]},"get":{"operationId":"listEmailRules","summary":"List rules","description":"The rules the caller can see, newest first: their own, or every member's rules for a caller who reads the whole organization's mail. Each carries its scope (evaluation_scope, and scope_selection for a selected rule) and current_version, but not the version's text: read that under current from GET \/email-rules\/{id}. With matched_email_id, only the rules that matched that email. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum number of items to return.","schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor: the meta.cursor of the previous page. meta.cursor is null on the last page. Omit for the first page.","schema":{"type":"string","maxLength":200}},{"name":"matched_email_id","in":"query","required":false,"description":"Only rules that matched this email.","schema":{"description":"Only rules that matched this email.","type":"string","format":"uuid"}}],"responses":{"200":{"description":"List rules","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRulesResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. grant_forbidden with details.reason mail_depth: matched_email_id with access to mail that shows headers only, since which rules matched an email is read from what it says.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rules\/{id}":{"get":{"operationId":"getEmailRule","summary":"Read a rule","description":"A rule, its scope (evaluation_scope, and scope_selection for a selected rule), its current version and its saved expectations. The current version's text (english, tree, explanation, assumptions, warnings) is under current, and current_version is its number. What the scope comes to for its author now is GET \/email-rules\/{id}\/scope. The SQL the version renders to is included only when include_sql is true. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The rule id.","schema":{"type":"string","format":"uuid"}},{"name":"include_sql","in":"query","required":false,"description":"true to include the SQL the current version renders to.","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Read a rule","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetEmailRuleResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. forbidden with details.reason covering_view_only (your dashboard session or the Primitive CLI only): you may read this rule only because it covers your mailbox or decided on your mail; details.covering_view is where (getEmailRuleCoveringMe).","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"}}}}},"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"}}}}}},"x-primitive-permission":["mail:read"]},"patch":{"operationId":"updateEmailRule","summary":"Change a rule","description":"Renames the rule, saves a new version (english and tree together; earlier versions stay) or restores one (restore_version), changes its state (draft is off; observe; active), its scope (evaluation_scope, with scope_selection when selected) or its saved expectations (judged in expectations_check). Anyone but its author who sees it may only set state draft, and while it is shared only if they manage members from the dashboard. A scope change saves a new version; a shared rule keeps its scope, and a kind never changes. A saved version takes effect at once, on the rule's share too, which goes on under it. if_version refuses a write once a version was saved since you read it; if_revision, once anything changed. Without if_expectations_pass a version is saved even when its expectations fail, except on an active rule keys, webhooks or a share read: that write is checked first unless if_expectations_pass is false. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The rule id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/UpdateEmailRuleRequest"}}}},"responses":{"200":{"description":"Change a rule","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/UpdateEmailRuleResult"}}}]}}},"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":"validation_error: invalid body or tree (details.errors), too large (too_large), a version that no longer renders (version_not_renderable), restore_version names the current version (already_current), over 20 made-up emails (too_many_expectation_samples), or a kind other than the rule's own (kind_mismatch). no_expectations: if_expectations_pass with no expectations to judge, or a write that removes them from an active rule keys, webhooks or a share read; nothing was saved.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. email_rule_author_only: only the rule's author may make this change. forbidden (rule_shared): the rule is shared, so only its author or a member manager can turn it off. forbidden (first_party_required): a new version of a shared rule is saved only by its author on the dashboard or the Primitive CLI, never by an app or agent acting for them. email_rule_scope_not_readable: scope_selection names something you cannot read. grant_forbidden (mail_depth): judging a write (if_expectations_pass, or by default) needs content access to mail when it judges emails by id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no such rule you can see, or restore_version names no version of it (details.reason version_not_found).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_conflict: the rule changed (details.reason version_moved with current_version, revision_moved with current_revision, expectations_moved, rule_changed or readers_changed); read it again and retry. email_rule_scope_locked: the rule is shared. email_rule_author_unresolvable: the rule could not run as you over the chosen scope. expectations_failed, expectations_unreadable (details.unreadable) or expectations_undecided, with if_expectations_pass or by default on an active rule keys, webhooks or a share read (details.read_by): nothing was saved; details carries current_version, state and expectations_check.","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":"service_unavailable (rule_keys_unread or rule_readers_unread): the keys or readers of an active rule could not be read in time; nothing was saved.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["mail:read"]},"delete":{"operationId":"deleteEmailRule","summary":"Delete a rule","description":"Deletes a rule with its versions, its decisions and the access it granted. A share of it ends (end_reason rule_deleted), and the member who shared can still list what it handed over. The answer lists the shares it ended (ended_shares), says whom with (share_notice), and lists the unrevoked keys that still list it, which now read nothing through it (keys, other_keys; left out when they could not be read). The author or a reader of all the organization's mail may delete it; while it is shared, only the author or someone who manages members from the dashboard. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The rule id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Delete a rule","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/DeleteEmailRuleResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. forbidden with details.reason rule_shared: the rule is shared with another member.","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"}}}}},"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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rules\/{id}\/decisions":{"get":{"operationId":"listEmailRuleDecisions","summary":"List a rule's decisions","description":"What the rule decided for each email it was evaluated against, newest first: whether it matched, whether it nearly did, how each part decided and the answers to its questions, with which email it was (from_address, subject, received_at). Only decisions on emails the caller can read are listed. With matched, only matches (true) or only the rest (false). Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The rule id.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of items to return.","schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor: the meta.cursor of the previous page. meta.cursor is null on the last page. Omit for the first page.","schema":{"type":"string","maxLength":200}},{"name":"matched","in":"query","required":false,"description":"true for only matches, false for only the rest.","schema":{"description":"Only decisions that matched (true) or did not (false).","type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"List a rule's decisions","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleDecisionsResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. grant_forbidden with details.reason mail_depth: your access to mail shows headers only, and what a rule decided is read from what emails say. forbidden with details.reason covering_view_only (your dashboard session or the Primitive CLI only): you may read this rule only because it covers your mailbox or decided on your mail; details.covering_view is where (listEmailRuleDecisionsOnMyMail).","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"}}}}},"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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/emails\/{id}\/rule-results":{"get":{"operationId":"listEmailRuleResults","summary":"List every rule's result on an email","description":"What each rule you can see decided about this email (matched, not matched, or not run), in the words of the deciding version. A do_not_share rule that matched it blocks sharing while active; earlier shares stay. Only a caller who can read the email gets an answer. classification is every fact the classifier read, as rule conditions read it. At most 50 rules are listed, results first; not_listed counts the rest. Another member's rule that decided on your own mail is listed read_only (dashboard and CLI only), with who receives what it shares (receivers) while it covers you; hidden counts deciding rules you cannot see. Whether a rule delivered the email by webhook is not in the rule results: on your own mail, rule_deliveries on GET \/v1\/emails\/{id} (primitive emails get <email-id> --json) counts each rule's webhook deliveries; on another member's mail, GET \/v1\/webhooks\/deliveries?email_id=<email-id> (primitive webhook-deliveries list --email-id <email-id>, with webhooks:read) lists them. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The email id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List every rule's result on an email","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleResultsResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. grant_forbidden with details.reason mail_depth: your access to mail shows headers only, and what a rule decided is read from what the email says.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no such email, or not one the caller can read.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rules\/{id}\/versions":{"get":{"operationId":"listEmailRuleVersions","summary":"Read versions of a rule","description":"A rule's versions as they were written: what each one's conditions and questions were, so a decision can be read against the version that made it. Without versions, every version newest first, a page at a time (meta.cursor). With versions, only those named, oldest first; numbers that are not versions of the rule are left out. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The rule id.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Versions per page when versions is not given: 1 to 100, default 50.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor: the meta.cursor of the previous page, null on the last. Omit for the first page. Not with versions.","schema":{"type":"string","maxLength":200}},{"name":"versions","in":"query","required":false,"description":"Read only these version numbers, comma-separated, at most 100. Not with limit or cursor.","schema":{"description":"Read only these version numbers, comma-separated. Without it, the versions listed, newest first, a page at a time.","type":"string","pattern":"^[1-9]\\d{0,8}(,[1-9]\\d{0,8}){0,99}$"}}],"responses":{"200":{"description":"Read versions of a rule","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleVersionsResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta","description":"Absent when versions is given."}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. forbidden with details.reason covering_view_only (your dashboard session or the Primitive CLI only): you may read this rule only because it covers your mailbox or decided on your mail; details.covering_view is where (listEmailRuleVersionsCoveringMe while it covers you).","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"}}}}},"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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rules\/{id}\/scope":{"get":{"operationId":"getEmailRuleScope","summary":"Read what a rule covers","description":"Whose mail the rule runs over, and what that comes to now: a rule never reaches further than what its author can read when it runs, so what a selected rule names and its author can no longer read stops matching. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The rule id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Read what a rule covers","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetEmailRuleScopeResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no such rule, or not one you can see.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-scope-options":{"get":{"operationId":"listEmailRuleScopeOptions","summary":"List what a rule can cover","description":"How much the caller can read, and the inboxes and domains the caller can read, which is what they may name when a rule runs over selected inboxes and domains. For a caller who reads the whole organization, addresses lists the inboxes known as someone's (member addresses, members' verified work emails at the organization's domains, agents' addresses and inboxes with shared access), not every address that receives mail: may_name_unlisted says when an address that is not listed, such as an inbox nobody has claimed, may be named too. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Only inboxes and domains containing this text.","schema":{"description":"Only inboxes and domains containing this text.","type":"string","maxLength":200}}],"responses":{"200":{"description":"List what a rule can cover","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleScopeOptionsResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_author_unresolvable: rules cannot run as you right now.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-requests":{"post":{"operationId":"createEmailRuleRequest","summary":"Ask a member for a rule","description":"Asks another member of the organization for a rule over their own mail, from a compile response or a tree you wrote. Name them by user id, or by their member mailbox address, or by their verified work email if you can list members (at most 30 lookups by address an hour). Nothing is evaluated or shared until they accept, and they may change the rule first. A message (up to 500 characters, fixed once sent) tells them why you ask. With an Idempotency-Key, asking again for exactly what a pending request of yours to the same member asks (name, english, tree, explanation and message, naming them the same way) returns that request with existing: true (200), so a retry never asks them twice. At most 5 pending requests to one member and 50 in all. With members:read the answer says whether they can accept it under their role (target_can_accept, target_warning); POST \/v1\/email-rule-requests\/target-check checks first. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateEmailRuleRequestRequest"}}}},"responses":{"200":{"description":"The pending request this create matched (existing: true): no second one was made.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CreateEmailRuleRequestResult"}}}]}}},"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":"Ask a member for a rule","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CreateEmailRuleRequestResult"}}}]}}}},"400":{"description":"validation_error: give exactly one of target_user_id and target_email, a tree that is valid for the column contract, and a message, if any, of 1 to 500 characters; details.reason self_request when the member named is you.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no current member of this organization matches the member named.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_request_limit: you already have the most pending requests allowed (details.limit, and details.scope target or total).","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":"service_unavailable: looking members up by address is unavailable right now. Try again in a moment, or name them by user id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. With a key, a create identical to a pending request of yours to the same member (name, english, tree, explanation and message, naming the member the same way) returns that request with existing: true (200) instead of making a second one. The key itself is not stored, so any key does this, and a create without one is never matched to an existing request. A key that is empty, longer than 255 characters or not printable ASCII is refused with 400 `validation_error`; a valid one is echoed in the Idempotency-Key response header.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-primitive-permission":["mail:read"]},"get":{"operationId":"listEmailRuleRequests","summary":"List rule requests","description":"Rule requests, newest first: those made to you (box incoming, the default), those you made (outgoing), or every one you can see (all). Pending ones by default; status decided lists accepted, declined and withdrawn ones, and any lists both. A request is visible to the member who made it, the member it was made to, and the organization's owners and admins. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"box","in":"query","required":false,"description":"incoming, outgoing or all.","schema":{"default":"incoming","description":"incoming: requests made to you; outgoing: requests you made; all: every request you can see (owners and admins see the organization's).","type":"string","enum":["incoming","outgoing","all"]}},{"name":"status","in":"query","required":false,"description":"pending, decided or any.","schema":{"default":"pending","type":"string","enum":["pending","decided","any"]}},{"name":"limit","in":"query","required":false,"description":"Maximum number of items to return.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor: the meta.cursor of the previous page. meta.cursor is null on the last page. Omit for the first page.","schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"List rule requests","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleRequestsResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-requests\/target-check":{"post":{"operationId":"checkEmailRuleRequestTarget","summary":"Check whether a member can accept a rule request","description":"Says, before you ask, whether a member could accept a rule request now under their role, and if not, which permission their role lacks. Nothing is sent and nothing is recorded. Needs members:read, as listing members and their roles does. At most 60 checks an hour. Name them by user id, or by their member mailbox address or verified work email (counted with asking: at most 30 lookups by address an hour). Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CheckEmailRuleRequestTargetRequest"}}}},"responses":{"200":{"description":"Check whether a member can accept a rule request","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CheckEmailRuleRequestTargetResult"}}}]}}},"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":"validation_error: give exactly one of target_user_id and target_email; details.reason self_request when the member named is you, answered before either limit is counted.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. grant_forbidden with details.permission members:read: you cannot list members and their roles.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no current member of this organization matches the member named.","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":"service_unavailable: looking members up by address is unavailable right now. Try again in a moment, or name them by user id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["mail:read","members:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/email-rule-requests\/{id}":{"get":{"operationId":"getEmailRuleRequest","summary":"Read a rule request","description":"A rule request: the rule as proposed, who made it and to whom, and whether each of them is still a member. A request is visible to the member who made it, the member it was made to, and the organization's owners and admins. If you are the member asked, you can try the rule as proposed before answering: pass the request's tree to POST \/v1\/email-rules\/test to test it on mail you have received. To try it on an email that has not arrived, pass the request's tree to POST \/v1\/email-rules\/test-sample with one you make up, or have look-alikes written (POST \/v1\/email-rules\/look-alikes with request_id); neither stores anything. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The request id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Read a rule request","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetEmailRuleRequestResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no request you can see has this id.","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"}}}}}},"x-primitive-permission":["mail:read"]},"delete":{"operationId":"deleteEmailRuleRequest","summary":"Delete a withdrawn rule request","description":"Deletes a request you withdrew, for everyone who could see it. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The request id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Delete a withdrawn rule request","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/DeleteEmailRuleRequestResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. email_rule_request_proposer_only: only the member who made the request may delete it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no request you can see has this id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_request_not_withdrawn: it is not withdrawn (details.status).","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-requests\/{id}\/decline":{"post":{"operationId":"declineEmailRuleRequest","summary":"Decline a rule request","description":"Declines a pending request made to you. Nothing is written but the decision and, if you give one, its reason (reason, up to 500 characters), which the member who asked sees with the request. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The request id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/DeclineEmailRuleRequestRequest"}}}},"responses":{"200":{"description":"Decline a rule request","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/DeclineEmailRuleRequestResult"}}}]}}},"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":"validation_error: reason is empty or longer than 500 characters.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. email_rule_request_target_only: only the member the request was made to may decline it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no request you can see has this id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_request_not_pending: the request was already accepted, declined or withdrawn (details.status).","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-requests\/{id}\/withdraw":{"post":{"operationId":"withdrawEmailRuleRequest","summary":"Withdraw a rule request","description":"Withdraws a pending request you made. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The request id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Withdraw a rule request","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/WithdrawEmailRuleRequestResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. email_rule_request_proposer_only: only the member who made the request may withdraw it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no request you can see has this id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_request_not_pending: the request was already accepted, declined or withdrawn (details.status).","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-requests\/{id}\/accept":{"post":{"operationId":"acceptEmailRuleRequest","summary":"Accept a rule request","description":"Writes the rule as yours, over your own mailboxes only, turns it on, and shares what it matches from now on with the member who asked: each email in full with its attachments, but never raw. It does not go back through mail already received. Send no body to accept it as asked, or change it first with english and tree together (and an explanation, or the server writes one). Because this gives your mail to someone else, it is limited to the dashboard and the Primitive CLI: other OAuth apps and agent tokens are refused. Before accepting, test it on your mail: pass the request's tree to POST \/v1\/email-rules\/test. To try it on an email that has not arrived, pass the request's tree to POST \/v1\/email-rules\/test-sample with one you make up, or have look-alikes written (POST \/v1\/email-rules\/look-alikes with request_id); neither stores anything. expectation_samples are kept on the rule it writes. Accepting also needs mail:share_own. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The request id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/AcceptEmailRuleRequestRequest"}}}},"responses":{"201":{"description":"Accept a rule request","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/AcceptEmailRuleRequestResult"}}}]}}}},"400":{"description":"validation_error: a changed rule needs english and tree together, and a tree that is valid for the column contract. The rule as asked is checked the same way, and must be changed first if it no longer passes. Made-up emails in expectation_samples are checked as on a rule write (at most 20, each within the sample bounds). Nothing is accepted.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden. email_rule_request_target_only: only the member the request was made to may accept it. forbidden with details.reason first_party_required: accepted from another OAuth app or with an agent token. grant_forbidden with details.permission mail:share_own: the caller's role does not let them share their own mail through email rules.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no request you can see has this id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_request_not_pending: the request was already decided (details.status). email_rule_request_party_left: one of the two people is no longer a member under the membership recorded (details.party). email_rule_share_limit: the member who asked already receives the most shared rules allowed (details.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"}}}}},"503":{"description":"service_unavailable: another access change in this organization is in progress, and nothing was changed. With details.reason connection_lost it could not be confirmed either way: check the request, or try again.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["mail:read","mail:share_own"]}},"\/email-rule-shares":{"get":{"operationId":"listEmailRuleShares","summary":"List rule shares","description":"Lists rules shared with you, rules you share, or every share you can see. A share is visible to its two people and the organization's owners and admins. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"box","in":"query","required":false,"description":"received, given or all.","schema":{"default":"received","description":"received: rules shared with you; given: rules you share; all: every share you can see (owners and admins see the organization's).","type":"string","enum":["received","given","all"]}},{"name":"live","in":"query","required":false,"description":"true, false or any.","schema":{"default":"true","description":"Whether to list live shares, ended ones, or both.","type":"string","enum":["true","false","any"]}},{"name":"limit","in":"query","required":false,"description":"Maximum number of items to return.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor: the meta.cursor of the previous page. meta.cursor is null on the last page. Omit for the first page.","schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"List rule shares","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleSharesResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta"}}}]}}},"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":"validation_error: a cursor not returned in meta.cursor.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-shares\/given":{"get":{"operationId":"listGivenEmailRuleShares","summary":"List the rule shares you have given","description":"Lists the rules you share with other members, so you can find one and stop it. This needs no mail access and does not depend on the feature being on for the organization, as stopping a share does not. It takes the dashboard or the Primitive CLI.","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"live","in":"query","required":false,"description":"true, false or any.","schema":{"default":"true","description":"Whether to list live shares, ended ones, or both.","type":"string","enum":["true","false","any"]}},{"name":"limit","in":"query","required":false,"description":"Maximum number of items to return.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor: the meta.cursor of the previous page. meta.cursor is null on the last page. Omit for the first page.","schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"List the rule shares you have given","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListGivenEmailRuleSharesResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta"}}}]}}},"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":"validation_error: a cursor not returned in meta.cursor.","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"}}},"403":{"description":"forbidden: you are no longer a member, or details.reason first_party_required: this credential cannot list them.","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"}}}}}}}},"\/email-rule-shares\/{id}":{"get":{"operationId":"getEmailRuleShare","summary":"Get a rule share","description":"One share. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token).","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The share id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get a rule share","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetEmailRuleShareResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no share you can see has this id.","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"}}}}}},"x-primitive-permission":["mail:read"]},"delete":{"operationId":"endEmailRuleShare","summary":"End a rule share","description":"Stops sharing; the rule itself is left unchanged, as the answer's note says. Either person on the share can do this, or an owner or admin who manages members. The share is kept, marked as ended. This needs no mail access and does not depend on the email rules feature. An owner or admin not on the share must use a dashboard session. Otherwise use your own session, or an OAuth or CLI token that acts with your full access: API keys and agent keys are refused.","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The share id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"End a rule share","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/EndEmailRuleShareResult"}}}]}}},"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":"forbidden: you are not someone who may end this share, or details.reason person_credential_required: this credential cannot end one.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no share you can see has this id.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"email_rule_share_ended: this share has already ended.","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":"service_unavailable: the share could not be ended in time. Nothing was changed. Try again in a moment.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}}}},"\/email-rule-shares\/{id}\/emails":{"get":{"operationId":"listEmailRuleShareEmails","summary":"List the emails a rule share has given","description":"The emails a rule share has given, newest first, as the email list shows them, each with shared_at. For the person it was shared with: what they can read through it now, so an ended share lists nothing, and shared_at is when the share's rule first matched the email. For the person sharing their mail: everything it has handed over while it ran, including after it ended and whatever the rule's state now, so they can see what left their mailbox, and shared_at is when it was handed over. Only those two people can list them, from the dashboard or the Primitive CLI. A page can be short or empty and still carry a cursor: keep reading until meta.cursor is null.","tags":["Email Rules"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The share id.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of items to return.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor: the meta.cursor of the previous page. meta.cursor is null on the last page. Omit for the first page.","schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"List the emails a rule share has given","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleShareEmailsResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta"}}}]}}},"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":"validation_error: a cursor not returned in meta.cursor.","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"}}},"403":{"description":"grant_forbidden with details.reason mail_depth: your access to mail shows headers only, and which emails a share's rule gave is read from what they say; forbidden: you are no longer a member; or details.reason not_on_share: you can see this share as an owner or admin but are not one of its two people; or details.reason first_party_required: use the dashboard or the Primitive CLI, signed in as yourself.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no share you can see has this id.","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":"service_unavailable: the list could not be read in time. Try again.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rules\/covering-me":{"get":{"operationId":"listEmailRulesCoveringMe","summary":"List the rules over your mail","description":"Every email rule that covers one of your own mailboxes now, yours included, newest first, in any state. A rule covers you when the scope it runs with now reaches one of your own mailboxes (your member mailbox, or a personal agent's address), and covers.via says how: author_reach, it runs over everything its author can read, which includes your mail (an author who reads the organization's mail or holds an access entry for your mailbox); selected, it names your mailbox or its domain; own_mailboxes, it is your own rule over your own mail. Another member's own_mailboxes rule never covers you. You see its name, wording, current tree, state, kind and author, and which of your own mailboxes it reaches. Its wording and tree are shown as written, so they can name other addresses (a condition on who an email is from or to). While it covers you, you also see who receives what it matches (receivers): how many API keys, webhooks and shares, the member each belongs to (or the organization), and the sites the webhooks deliver to. You do not see which keys, webhooks or shares they are (no ids, names, prefixes, or URLs beyond the site), the other inboxes and domains its scope names, or its saved expectations. Each listed rule carries its current version's english and explanation; its tree is on GET \/v1\/email-rules\/covering-me\/{id}. A page can hold fewer rules than limit, or none, and still carry a cursor (one request reads a bounded number of rules): keep reading until meta.cursor is null. For what happened to one email of yours, every rule's decision on it and, for another member's rule covering you, who receives what it shares: GET \/v1\/emails\/{id}\/rule-results (primitive email-rules list-email-rule-results --email-id <email-id>). Whether a rule delivered the email by webhook is not in the rule results: on your own mail, rule_deliveries on GET \/v1\/emails\/{id} (primitive emails get <email-id> --json) counts each rule's webhook deliveries; on another member's mail, GET \/v1\/webhooks\/deliveries?email_id=<email-id> (primitive webhook-deliveries list --email-id <email-id>, with webhooks:read) lists them. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Rules per page, from 1 to 100; defaults to 50.","schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"meta.cursor from the preceding page. Omit for the first page.","schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"List the rules over your mail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRulesCoveringMeResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta"}}}]}}},"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_cursor: a cursor this list did not give you (each is sealed to you and your organization); pass meta.cursor unchanged.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rules\/covering-me\/{id}":{"get":{"operationId":"getEmailRuleCoveringMe","summary":"Get a rule over your mail","description":"One rule that covers one of your own mailboxes, read-only, with its current version; or one that no longer does but decided on mail at your own mailbox (covers_now false), shown as the version that last decided on your mail, without its current name. You see its name, wording, current tree, state, kind and author, and which of your own mailboxes it reaches. Its wording and tree are shown as written, so they can name other addresses (a condition on who an email is from or to). While it covers you, you also see who receives what it matches (receivers): how many API keys, webhooks and shares, the member each belongs to (or the organization), and the sites the webhooks deliver to. You do not see which keys, webhooks or shares they are (no ids, names, prefixes, or URLs beyond the site), the other inboxes and domains its scope names, or its saved expectations. For what happened to one email of yours, every rule's decision on it and, for another member's rule covering you, who receives what it shares: GET \/v1\/emails\/{id}\/rule-results (primitive email-rules list-email-rule-results --email-id <email-id>). Whether a rule delivered the email by webhook is not in the rule results: on your own mail, rule_deliveries on GET \/v1\/emails\/{id} (primitive emails get <email-id> --json) counts each rule's webhook deliveries; on another member's mail, GET \/v1\/webhooks\/deliveries?email_id=<email-id> (primitive webhook-deliveries list --email-id <email-id>, with webhooks:read) lists them. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The rule id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get a rule over your mail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetEmailRuleCoveringMeResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no rule with this id covers your mailboxes or decided on your mail; details.reason rule_deleted (with details.rule_name) for one deleted after you were told it covered you.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rules\/covering-me\/{id}\/decisions":{"get":{"operationId":"listEmailRuleDecisionsOnMyMail","summary":"List what a rule decided about your mail","description":"What a rule that covers your mailboxes, or decided on your mail, decided about mail at your own mailboxes, newest first: never about anyone else's mail. Each decision is in the words of the version that made it, which can be earlier than the current one: the questions it asked about that email (question_answers) and the columns its conditions read (parts). Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The rule id.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Decisions per page, from 1 to 100; defaults to 50.","schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"meta.cursor from the preceding page. Omit for the first page.","schema":{"type":"string","maxLength":200}},{"name":"matched","in":"query","required":false,"description":"true for matches only, false for the rest.","schema":{"description":"Only decisions that matched (true) or did not (false).","type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"List what a rule decided about your mail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleDecisionsOnMyMailResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta"}}}]}}},"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":"validation_error: a cursor not returned in meta.cursor.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI. grant_forbidden with details.reason mail_depth: your access to mail shows headers only, and what a rule decided is read from what emails say.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no rule with this id covers your mailboxes or decided on your mail; details.reason rule_deleted (with details.rule_name) for one deleted after you were told it covered you.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rules\/covering-me\/{id}\/versions":{"get":{"operationId":"listEmailRuleVersionsCoveringMe","summary":"Read versions of a rule over your mail","description":"The versions of a rule that covers one of your own mailboxes now that applied to your mail: its current version and each earlier one that decided on mail at your own mailboxes, so a decision can be read in the words of the version that made it. Each has what the covering view shows of a version: its wording, tree, explanation and assumptions. A version that did neither is not listed. Without versions, newest first, a page at a time (meta.cursor); with versions, only those named that are listed, oldest first. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The rule id.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Versions per page when versions is not given: 1 to 100, default 50.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"meta.cursor from the preceding page. Omit for the first page. Not with versions.","schema":{"type":"string","maxLength":200}},{"name":"versions","in":"query","required":false,"description":"Read only these version numbers, comma-separated, at most 100. Not with limit or cursor.","schema":{"description":"Read only these version numbers, comma-separated. Without it, the versions listed, newest first, a page at a time.","type":"string","pattern":"^[1-9]\\d{0,8}(,[1-9]\\d{0,8}){0,99}$"}}],"responses":{"200":{"description":"Read versions of a rule over your mail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleVersionsCoveringMeResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta","description":"Absent when versions is given."}}}]}}},"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":"validation_error: a cursor not returned in meta.cursor, or versions with limit or cursor.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI. forbidden with details.reason no_longer_covers: it decided on your mail before but covers none of your mailboxes now; details.covering_view (getEmailRuleCoveringMe) shows the version that last decided on your mail.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no rule with this id covers your mailboxes or decided on your mail; details.reason rule_deleted (with details.rule_name) for one deleted after you were told it covered you.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-objections":{"post":{"operationId":"createEmailRuleObjection","summary":"Object to a rule over your mail","description":"Objects to a rule that covers one of your own mailboxes now: say in the message what you ask its author, to turn it off, narrow it, or explain who receives what it matches. The author answers by accepting, which turns it off, or by declining, optionally with a reason you see; the rule keeps running unless they accept. Only a rule that covers you now: one you see only because it once decided on your mail cannot be objected to. An objection is seen by the member who made it, the rule's author and the organization's owners and admins. It stays pending until the rule's author accepts it, turning the rule off, or declines it (optionally with a reason the objector sees), or the member who made it withdraws it, which clears its message. It never changes after that. You may have one pending objection per rule and 50 in all, and object to the same rule again only 10 minutes after you last did, whatever became of it. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateEmailRuleObjectionRequest"}}}},"responses":{"200":{"description":"Object to a rule over your mail","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CreateEmailRuleObjectionResult"}}}]}}},"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":"validation_error; details.reason own_rule: it is your own rule, which you can turn off yourself.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no rule with this id covers your mailboxes now.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: details.reason objection_pending, you already have a pending objection to this rule (details.objection_id); objection_limit, you have 50 pending objections.","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":"service_unavailable: checking which rules cover you, or the hourly limit, could not be done; nothing was written (Retry-After).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["mail:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]},"get":{"operationId":"listEmailRuleObjections","summary":"List objections","description":"Objections you made and objections to rules you wrote (by default), only one of the two, or every objection you can see, newest first. An objection is seen by the member who made it, the rule's author and the organization's owners and admins. It stays pending until the rule's author accepts it, turning the rule off, or declines it (optionally with a reason the objector sees), or the member who made it withdraws it, which clears its message. It never changes after that. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"box","in":"query","required":false,"description":"mine (the default): objections you made and objections to rules you wrote, each saying which (is_objector, is_author). outgoing: only those you made. incoming: only those to rules you wrote. all: every objection you can see.","schema":{"default":"mine","description":"mine: objections you made and objections to rules you wrote (is_objector or is_author says which); outgoing: only those you made; incoming: only those to rules you wrote; each in your current membership. all: every objection you can see (owners and admins see the organization's).","type":"string","enum":["mine","outgoing","incoming","all"]}},{"name":"status","in":"query","required":false,"description":"pending (the default), decided, or any.","schema":{"default":"pending","type":"string","enum":["pending","decided","any"]}},{"name":"limit","in":"query","required":false,"description":"Objections per page, from 1 to 100; defaults to 25.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"meta.cursor from the preceding page. Omit for the first page.","schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"List objections","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListEmailRuleObjectionsResult"},"meta":{"$ref":"#\/components\/schemas\/CursorPageMeta"}}}]}}},"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":"validation_error: a cursor not returned in meta.cursor.","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"}}},"403":{"description":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-objections\/{id}":{"get":{"operationId":"getEmailRuleObjection","summary":"Get an objection","description":"One objection you can see. An objection is seen by the member who made it, the rule's author and the organization's owners and admins. It stays pending until the rule's author accepts it, turning the rule off, or declines it (optionally with a reason the objector sees), or the member who made it withdraws it, which clears its message. It never changes after that. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The objection id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get an objection","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetEmailRuleObjectionResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no objection with this id that you can see.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-objections\/{id}\/withdraw":{"post":{"operationId":"withdrawEmailRuleObjection","summary":"Withdraw an objection","description":"Withdraws a pending objection you made, and clears its message. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The objection id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Withdraw an objection","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/WithdrawEmailRuleObjectionResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI. forbidden with details.reason objector_only: only the member who made it can withdraw it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no objection with this id that you can see.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"not_pending: it was already decided or withdrawn (details.status).","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-objections\/{id}\/decline":{"post":{"operationId":"declineEmailRuleObjection","summary":"Decline an objection to your rule","description":"Declines a pending objection to a rule you wrote, optionally saying why (reason), which the member who objected sees; the rule stays as it is. When its message asks you to narrow the rule or explain who receives it, the reason is your answer; narrow it with a new version as well if you agree. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The objection id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/DeclineEmailRuleObjectionRequest"}}}},"responses":{"200":{"description":"Decline an objection to your rule","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/DeclineEmailRuleObjectionResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI. forbidden with details.reason author_only: only the rule's author can decline it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no objection with this id that you can see.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"not_pending: it was already decided or withdrawn (details.status).","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/email-rule-objections\/{id}\/accept":{"post":{"operationId":"acceptEmailRuleObjection","summary":"Accept an objection to your rule","description":"Accepts a pending objection to a rule you wrote: turns the rule off (state draft), as updating it would, and records the objection accepted, together. You can turn the rule on again later. Needs mail:read, the email rules feature, and a member's own full-access credential (no API key, agent key or narrowed token). Only on your own dashboard session or the Primitive CLI.","tags":["Email Rule Oversight"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The objection id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Accept an objection to your rule","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/AcceptEmailRuleObjectionResult"}}}]}}},"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":"feature_not_enabled: this organization lacks the email rules feature; permission refusals answer forbidden or grant_forbidden; details.reason first_party_required means this credential is not your dashboard session or the Primitive CLI. forbidden with details.reason author_only: only the rule's author can accept it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: no objection with this id that you can see.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"not_pending: it was already decided or withdrawn (details.status). conflict with details.reason rule_deleted: the rule was deleted; decline it instead.","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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/domains\/{id}\/agent-authorization":{"get":{"operationId":"getDomainAgentAuthorization","summary":"Get a domain's Agent Authorization status","description":"Whether the domain publishes an Agent Authorization (`_agents`) TXT record that lets agents on Primitive email it, read from DNS with the same engine the send path uses (an answer may be up to five minutes old): published (a record authorizes them), published_unauthorized (a record exists but does not), not_set_up (no record, the default) or inconclusive (the lookup could not tell; retry). The domain is one of the organization's, verified or pending; a credential confined to some mailboxes sees only the domains its scope covers. Needs domains:read.","tags":["Domains"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The domain id from listDomains.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get a domain's Agent Authorization status","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetDomainAgentAuthorizationResult"}}}]}}},"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":"validation_error: the id is not a domain id.","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"}}},"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"}}}}}},"x-primitive-permission":["domains:read"]}},"\/managed-inbox\/vanity\/{name}":{"get":{"operationId":"checkVanityInboxName","summary":"Check a vanity inbox name","description":"Whether a name can be claimed for the managed inbox (name.primitive.email): available, taken, reserved (a protected word) or invalid (5 to 30 lowercase letters and digits, starting with a letter). Any plan may check; claiming needs Power. Rate limited per organization together with claims. Needs domains:read.","tags":["Domains"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"description":"The name to check, without the domain.","schema":{"type":"string","minLength":1,"maxLength":64}}],"responses":{"200":{"description":"Check a vanity inbox name","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CheckVanityInboxNameResult"}}}]}}},"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":"validation_error: the name parameter is missing or too long.","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"}}},"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"}}}}}},"x-primitive-permission":["domains:read"]}},"\/managed-inbox\/vanity":{"post":{"operationId":"claimVanityInboxName","summary":"Claim a vanity inbox name","description":"Make the name the organization's current managed inbox. The previous inbox stops being current but keeps receiving mail. Needs the Power plan and domains:manage, which a credential confined to some mailboxes never holds. Sending records are published in the same request when they can be. outbound_ready false means they could not be yet: mail to the new name still arrives, but sending from it is refused until they are published.","tags":["Domains"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ClaimVanityInboxNameRequest"}}}},"responses":{"201":{"description":"Claim a vanity inbox name","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ClaimVanityInboxNameResult"}}}]}}}},"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":"plan_required: renaming the inbox needs the Power plan.","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":"conflict: the name is already taken, or another change to the organization's inbox committed meanwhile (retry).","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"}}}}}},"x-primitive-permission":["domains:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/mail-imports\/google\/messages":{"post":{"operationId":"importGoogleMailMessage","summary":"Import one historical Google Workspace message","description":"Requires the organization email_import entitlement, granted by staff. Only the configured current owner, using a full personal session or verified first-party CLI, may import into the configured primary mailbox. Primitive verifies the transient provider token and fetches RAW content itself. No raw uploads or client provenance are accepted. Sent, draft, spam and trash messages are skipped. Historical mail appears in normal history without live delivery effects. An indeterminate response requires retrying the same provider ID. User provider credentials are never stored or queued.","tags":["Emails"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ImportGoogleMailMessageRequest"}}}},"responses":{"200":{"description":"Import one historical Google Workspace message","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ImportGoogleMailMessageResult"}}}]}}},"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":"mail_import_provider_auth: reauthorize the provider.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"feature_disabled: the organization lacks email_import; or mail_import_forbidden: identity or current access differs.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"mail_import_cap_exceeded or mail_import_storage_limit: the fixed pilot budget is exhausted.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"Mail import input, content, storage or publication fence could not be accepted.","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":"Provider acquisition failed or exceeded its deadline.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"mail_import_disabled or mail_import_unavailable; retry the same ID after resolving the cause.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"parameters":[{"in":"header","name":"X-Primitive-Google-Access-Token","required":true,"description":"Transient provider user access token. Send only in this redacted header, never in a URL or JSON body. Uses the dedicated configured desktop client and readonly\/identity scopes.","schema":{"type":"string","minLength":1,"maxLength":8192,"writeOnly":true}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-primitive-permission":["mail:read"]}},"\/mail-imports\/google\/reconcile":{"post":{"operationId":"reconcileGoogleMailImport","summary":"Reconcile scoped historical import attempts","description":"The same configured owner may recover expired attempts after the email_import entitlement is revoked. Recovery does not require that entitlement. Selects only server-owned manifests for the configured mailbox\/account\/policy. No provider token, object keys, prefixes or email IDs are accepted. Unsettled uploads remain unresolved and charged; this operation does not promise automatic cleanup after a process dies.","tags":["Emails"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ReconcileGoogleMailImportRequest"}}}},"responses":{"200":{"description":"Reconcile scoped historical import attempts","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ReconcileGoogleMailImportResult"}}}]}}},"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":"mail_import_forbidden: current owner access is required.","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":"mail_import_disabled: no valid policy is configured; or mail_import_unavailable.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["mail:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/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,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Used only for `read`: a read retried with the same key rebuilds the identical signal and replays the first send instead of sending again. `working` and `typing` are a new signal on every call, whatever the key. A key that is empty, longer than 255 characters or not printable ASCII is refused with 400 `validation_error`. The Idempotency-Key response header names the key of the signal's own send, not this one."}],"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"}}}}}},"x-primitive-permission":["mail:signal"]}},"\/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"}}}}}},"x-primitive-permission":["mail:send"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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"}}}}}},"x-primitive-permission":["mail:read"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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"}}}}}},"x-primitive-permission":["threads:mute"]}},"\/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"}}}}}},"x-primitive-permission":["threads:mute"]},"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"}}}}}},"x-primitive-permission":["threads:mute"]}},"\/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`. A thread the caller can see no message in answers 404, exactly as an unknown thread. 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"}}}}}},"x-primitive-permission":["threads:read"]}},"\/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. In both modes a thread the caller can see no message in is left out, exactly as an unknown thread. 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"}}}}}},"x-primitive-permission":["threads:read"]}},"\/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"}}}}}},"x-primitive-permission":["mail:read"]}},"\/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."},{"name":"expand","in":"query","required":false,"schema":{"type":"string","enum":["message"]},"description":"`message` adds `message` to every change: the current list row of the message a message change names, so a reader needs no follow-up read. Ignored for a baseline request."},{"name":"address","in":"query","required":false,"schema":{"type":"string","minLength":1},"description":"Up to 20 email addresses, comma-separated (or the parameter repeated). Keeps only the message changes whose message is to or from one of them, matched case-insensitively. Inbound mail matches on the address it was delivered to, the envelope sender and the From header address; sent mail on the From address and the To and Cc addresses, and Bcc only for a credential that can read the sender side. Deletions, thread, agent and access changes are never filtered, because a deleted message's addresses can no longer be read: ignore a deletion for an id you do not hold. The cursor still moves past every change the filter drops, so a filtered page can be empty with a new cursor."}],"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"}}}}}},"x-primitive-permission":["changes:read"]}},"\/whoami\/grant":{"get":{"operationId":"getOwnGrant","tags":["Account"],"security":[{"BearerAuth":[]}],"summary":"Check what this credential can do","description":"The grant of the credential that calls it: its permissions, mail scope, resources, template, credential kind and expiry. A credential may call it about itself without any permission, including a narrowed key without org:read, so a key can see its own permissions and mail scope. It answers nothing about any other credential. grant is null when the credential carries no grant.","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":{"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}$"},"auth_method":{"type":["string","null"]},"key_id":{"type":["string","null"]},"grant":{"anyOf":[{"type":"object","properties":{"credential_kind":{"type":"string"},"template":{"type":["string","null"]},"permissions":{"type":"array","items":{"type":"string"}},"mail_scope":{"anyOf":[{"type":"string","const":"org"},{"type":"string","const":"unreadable","description":"unreadable: the stored scope could not be read, so the credential reaches no mail."},{"type":"object","properties":{"addresses":{"type":"array","items":{"type":"string"}},"domains":{"type":"array","items":{"type":"string"}},"direction":{"type":"string","enum":["inbound","outbound","both"]},"depth":{"type":"string","enum":["metadata","content","raw"],"description":"Deprecated: raw for every scope that reaches mail, which reads the full email; raw MIME follows mail.raw:read. Metadata only for a scope that reaches no mail."},"excluded_personal_mailboxes":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["addresses","domains","depth"],"additionalProperties":{}}]},"send_scope":{"anyOf":[{"type":"string","const":"org"},{"type":"string","const":"unreadable","description":"unreadable: the stored scope could not be read, so the credential reaches no mail."},{"type":"object","properties":{"addresses":{"type":"array","items":{"type":"string"}},"domains":{"type":"array","items":{"type":"string"}},"direction":{"type":"string","enum":["inbound","outbound","both"]},"depth":{"type":"string","enum":["metadata","content","raw"],"description":"Deprecated: raw for every scope that reaches mail, which reads the full email; raw MIME follows mail.raw:read. Metadata only for a scope that reaches no mail."},"excluded_personal_mailboxes":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["addresses","domains","depth"],"additionalProperties":{}}]},"scoped_permissions":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string","const":"org"},{"type":"string","const":"unreadable","description":"unreadable: the stored scope could not be read, so the credential reaches no mail."},{"type":"object","properties":{"addresses":{"type":"array","items":{"type":"string"}},"domains":{"type":"array","items":{"type":"string"}},"direction":{"type":"string","enum":["inbound","outbound","both"]},"depth":{"type":"string","enum":["metadata","content","raw"],"description":"Deprecated: raw for every scope that reaches mail, which reads the full email; raw MIME follows mail.raw:read. Metadata only for a scope that reaches no mail."},"excluded_personal_mailboxes":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["addresses","domains","depth"],"additionalProperties":{}}]}},"resources":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}},{"type":"null"}]},"expires_at":{"type":["string","null"]}},"required":["credential_kind","template","permissions","mail_scope","resources","expires_at"],"additionalProperties":{}},{"type":"null"}]}},"required":["org_id","auth_method","key_id","grant"],"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"}}},"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"}}}}}}}},"\/whoami\/send-from":{"get":{"operationId":"getSendFrom","tags":["Account"],"security":[{"BearerAuth":[]}],"summary":"List the addresses this credential can send from","description":"The From addresses the calling credential may send from with POST \/send-mail, and the one a send that names no From should use. Every address listed is one that send would accept as From for this credential, recipients aside. A connected agent's default is its own address. A person's default is their own member address, for a session and for every OAuth login of theirs, an agent's included. Any other credential has a default only when exactly one address and no domain is listed, and never a person's own member address. When there is no default, `reason` says why. `domains` lists the domains at which the credential may send from any address that is not reserved to someone else or left out of its grant; each is one GET \/outbound\/status lists as sendable for it. A credential that cannot send new mail, or one in an account where outbound mail is not enabled, gets no addresses and no domains. Every credential may ask about itself, a connected agent's key included, except a Function-bound key limited to its Function's own operations (403 function_scope_forbidden). It answers nothing about any other credential.","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":{"default_from":{"description":"The From a send that names none should use. Null when there is no single right answer; `reason` says why.","type":["string","null"]},"default_source":{"anyOf":[{"type":"string","enum":["agent_address","member_address","granted_address"]},{"type":"null"}],"description":"Where default_from comes from. Null when default_from is null."},"addresses":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"kind":{"type":"string","enum":["agent_address","member_address","granted"],"description":"agent_address: this connected agent's own address. member_address: your own member address. granted: an address this credential's grant names."}},"required":["address","kind"],"additionalProperties":false},"description":"Addresses this credential may send from, each one a send would accept as From for it, recipients aside."},"domains":{"type":"array","items":{"type":"string"},"description":"Domains at which this credential may send from any address, except one reserved to someone else (a member's own address, or an agent's) or left out of its grant. Never listed for a connected agent."},"reason":{"description":"Why default_from is null, in plain English. Null when there is a default.","type":["string","null"]}},"required":["default_from","default_source","addresses","domains","reason"],"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"}}},"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"}}}}}}}},"\/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-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"}}}}}},"x-primitive-permission":["mail_sync:manage"]},"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"}}}}}},"x-primitive-permission":["mail_sync:manage"]}},"\/functions\/lookup":{"get":{"operationId":"lookupFunction","summary":"Find a function by name","description":"Resolve a function name to its id and deploy state. Answers null data when no function has the name. Carries no invocation counts; use listFunctions or getFunctionUsage for those.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"name","in":"query","required":true,"description":"The function name: lowercase letters, digits, hyphen and underscore, at most 64 characters.","schema":{"type":"string","pattern":"^[a-z0-9_-]{1,64}$"}}],"responses":{"200":{"description":"Find a function by name","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/LookupFunctionResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:read"]}},"\/functions\/usage":{"get":{"operationId":"getOrgFunctionUsage","summary":"Get usage across all functions","description":"Invocations, runtime and webhook delivery outcomes across every function in the organization, for the current month and the function's lifetime, plus the number of active functions.","tags":["Functions"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Get usage across all functions","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetOrgFunctionUsageResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:read"]}},"\/functions\/invocation-series":{"get":{"operationId":"getOrgFunctionInvocationSeries","summary":"Get daily invocation counts","description":"Invocations and failures per function per day, for the dashboard's activity chart. Optionally limited to one function.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"days","in":"query","required":false,"description":"How many days back to include.","schema":{"type":"number"}},{"name":"function_id","in":"query","required":false,"description":"Only this function.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get daily invocation counts","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetOrgFunctionInvocationSeriesResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:read"]}},"\/functions\/fallback":{"get":{"operationId":"getOrgFunctionFallback","summary":"Get the organization fallback target","description":"Where mail that no domain route claims is delivered: an HTTP endpoint or a function. Null data when no fallback is set.","tags":["Functions"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Get the organization fallback target","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetOrgFunctionFallbackResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:read"]}},"\/functions\/{id}\/redeploy":{"post":{"operationId":"redeployFunction","summary":"Redeploy a function's stored code","description":"Push the function's stored source again as a new deployment, for example after changing its secrets or runtime key. Takes no body: to ship new code, use updateFunction. A body carrying code is refused rather than ignored.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Redeploy a function's stored code","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/RedeployFunctionResult"}}}]}}},"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"}}}}},"424":{"description":"deploy_failed: the function could not be deployed; details.reason owner_or_admin_stop when an owner or admin stopped it, which only reaffirmFunctionRuntimeKey from a dashboard session of that rank lifts","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"}}}}}},"x-primitive-permission":["functions:deploy"]}},"\/functions\/{id}\/tags":{"patch":{"operationId":"updateFunctionTags","summary":"Replace a function's tags","description":"Set the function's tags to exactly the given list.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/UpdateFunctionTagsRequest"}}}},"responses":{"200":{"description":"Replace a function's tags","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/UpdateFunctionTagsResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:manage"]}},"\/functions\/{id}\/deployments":{"get":{"operationId":"listFunctionDeployments","summary":"List a function's recent deployments","description":"The function's most recent deployments, newest first, with build and bundle details: limit of them (default 50, at most 200). This list is not paged; read an older deployment by id with getFunctionDeployment. is_current marks the one serving traffic.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"How many of the newest deployments to return: default 50, at most 200.","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"List a function's recent deployments","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListFunctionDeploymentsResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:read"]}},"\/functions\/{id}\/deployments\/{deploymentId}":{"get":{"operationId":"getFunctionDeployment","summary":"Get one deployment","description":"One deployment of the function, with its status, error, timings and bundle fingerprints.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"deploymentId","in":"path","required":true,"description":"A deployment id from listFunctionDeployments.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get one deployment","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetFunctionDeploymentResult"}}}]}}},"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"}}}}},"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"}}}}}},"x-primitive-permission":["functions:read"]}},"\/functions\/{id}\/source":{"get":{"operationId":"getFunctionSource","summary":"Get a function's source files","description":"The source files of a managed-build deployment: the current one, or the one named by deployment_id. A function deployed as a single bundled module returns its deploymentId with an empty files list. deploymentId is null only when the function has no deployment.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"deployment_id","in":"query","required":false,"description":"A deployment of this function. Defaults to the current deployment.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get a function's source files","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetFunctionSourceResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:manage"]}},"\/functions\/{id}\/usage":{"get":{"operationId":"getFunctionUsage","summary":"Get a function's usage","description":"Invocations, runtime and webhook delivery outcomes for this function, for the current month and its lifetime.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get a function's usage","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetFunctionUsageResult"}}}]}}},"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"}}}}},"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"}}}}}},"x-primitive-permission":["functions:read"]}},"\/functions\/{id}\/users":{"get":{"operationId":"getFunctionUsers","summary":"List the addresses a function has exchanged mail with","description":"Each address that sent mail the function handled or received a reply from it, with inbound and outbound counts and first and last seen times, up to cap entries; truncated is true when there were more.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List the addresses a function has exchanged mail with","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetFunctionUsersResult"}}}]}}},"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"}}}}},"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"}}}}}},"x-primitive-permission":["functions:read"]}},"\/functions\/{id}\/activity":{"get":{"operationId":"listFunctionActivity","summary":"List mail a function sent","description":"The sends and replies the function made, newest first, each linked to the delivery and email that triggered it. Filter by delivery, email or type.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous page's next_cursor. Omit for the first page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Items per page: default 50, at most 200.","schema":{"type":"integer","minimum":1}},{"name":"delivery_id","in":"query","required":false,"description":"Only effects of this webhook delivery.","schema":{"anyOf":[{"type":"string","maxLength":19,"pattern":"^\\d+$"},{"type":"string","format":"uuid"}]}},{"name":"email_id","in":"query","required":false,"description":"Only effects triggered by this inbound email.","schema":{"type":"string","format":"uuid"}},{"name":"type","in":"query","required":false,"description":"Only sends (email.sent) or only replies (email.replied).","schema":{"type":"string","enum":["email.sent","email.replied"]}}],"responses":{"200":{"description":"List mail a function sent","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListFunctionActivityResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:manage"]}},"\/functions\/{id}\/invocations":{"get":{"operationId":"listFunctionInvocations","summary":"List a function's invocations","description":"Each run of the function, newest first, with its outcome, timings, response status and exception. Lists runs only: a webhook delivery that never reached the function (for example one refused before it ran) is not an invocation; find those with listDeliveries and its endpoint_id filter.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous page's next_cursor. Omit for the first page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Items per page: default 50, at most 200.","schema":{"type":"integer","minimum":1}},{"name":"outcome","in":"query","required":false,"description":"Only runs with this outcome, for example ok or exception. Outcomes are an open set reported by the runtime.","schema":{"type":"string","maxLength":64}},{"name":"since","in":"query","required":false,"description":"Only runs started after this instant.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"List a function's invocations","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListFunctionInvocationsResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:manage"]}},"\/functions\/{id}\/invocations\/{invocationId}":{"get":{"operationId":"getFunctionInvocation","summary":"Get one invocation and its logs","description":"One run of the function with its exception stack and the log lines it wrote, oldest first. logs_truncated is true when the run wrote more lines than one response carries.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"invocationId","in":"path","required":true,"description":"An invocation id from listFunctionInvocations.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get one invocation and its logs","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetFunctionInvocationResult"}}}]}}},"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"}}}}},"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"}}}}}},"x-primitive-permission":["functions:manage"]}},"\/functions\/runtime-grant-presets":{"get":{"operationId":"listFunctionRuntimeKeyPresets","summary":"List the runtime key choices","description":"The keys a new or existing function can run with: the organization's shared function key, or a per-function key narrowed by a preset. Each preset says whether it is available now and, if not, why.","tags":["Functions"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List the runtime key choices","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListFunctionRuntimeKeyPresetsResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:read"]}},"\/functions\/runtime-grant-presets\/runner-request":{"post":{"operationId":"requestFunctionRuntimeKeyRunner","summary":"Prepare a narrowed runtime key","description":"Ask for the capacity a per-function key needs before creating a function with a narrowed preset, then answer the presets as they now stand. Needs the same permission as deploying a function.","tags":["Functions"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/RequestFunctionRuntimeKeyRunnerRequest"}}}},"responses":{"200":{"description":"Prepare a narrowed runtime key","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/RequestFunctionRuntimeKeyRunnerResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:deploy"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/functions\/{id}\/runtime-key":{"get":{"operationId":"getFunctionRuntimeKey","summary":"Get a function's runtime key","description":"Which key the function runs with (the organization's shared key or its own), the preset and permissions it is narrowed to, the secrets attached to it, and whether it is suspended and why. Never returns the key itself.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Get a function's runtime key","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetFunctionRuntimeKeyResult"}}}]}}},"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"}}}}},"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"}}}}}},"x-primitive-permission":["functions:read"]},"put":{"operationId":"setFunctionRuntimeKey","summary":"Change a function's runtime key","description":"Change the key the function runs with, or the secrets attached to it. A grant wider than the caller's own is refused. A custom runtime_grant must list addresses or send mail_scope \"org\" for every mailbox; one that does neither is refused (400 validation_error, details.reason mail_scope_required). The function is redeployed with the new key.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SetFunctionRuntimeKeyRequest"}}}},"responses":{"200":{"description":"Change a function's runtime key","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/SetFunctionRuntimeKeyResult"}}}]}}},"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":"conflict, runtime_grant_unavailable, function_suspended (details.reason suspended, access_stop or owner_or_admin_stop), suspension_undecided, rotation_in_progress or rotation_unconfirmed: the key could not be changed now; the message says why and whether a retry finishes it","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"}}}}}},"x-primitive-permission":["functions:deploy"]}},"\/functions\/{id}\/runtime-key\/suspend":{"post":{"operationId":"suspendFunctionRuntimeKey","summary":"Suspend a function","description":"Stop the function from running by suspending its key. Its code, bindings and secrets stay stored; reaffirmFunctionRuntimeKey brings it back as it was. A stop an owner or admin places (suspended_by owner_or_admin) records their role, is lifted only in a dashboard session by someone whose role is at least that one (an owner's stop, only an owner), and replaces any stop but an owner's or admin's of at least that role, an access decision's included: keeping that decision on the waiting list then leaves the function stopped.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SuspendFunctionRuntimeKeyRequest"}}}},"responses":{"200":{"description":"Suspend a function","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/SuspendFunctionRuntimeKeyResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["functions:deploy"]}},"\/functions\/{id}\/runtime-key\/reaffirm":{"post":{"operationId":"reaffirmFunctionRuntimeKey","summary":"Reaffirm a suspended function","description":"Lift the suspensions the caller may decide on the function's key (for example one left by a member removal or a grant reduction) and redeploy it. key_suspension_reasons lists any that remain. A function transferred to a new owner while it was stopped is not redeployed: lifting that stop places the transfer's own stop (transfer_stop_placed), which a redeploy lifts, or a further reaffirm by someone who covers its key that names it in the body (suspended_at and suspended_by 'transfer'): that reaffirm accepts the code the previous owner deployed. The body may name any stop the caller saw; a stop that is not the function's stop now lifts nothing.","tags":["Functions"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ReaffirmFunctionRuntimeKeyRequest"}}}},"responses":{"200":{"description":"Reaffirm a suspended function","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ReaffirmFunctionRuntimeKeyResult"}}}]}}},"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":"forbidden: an owner or admin placed the stop (details.reason owner_or_admin_stop, details.stop_rank owner or admin), which only someone of at least that role lifts, signed in to the dashboard; or an access decision did (access_stop_needs_owner), which only an owner or admin lifts there","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"}}}}},"409":{"description":"stop_changed: the stop is not the one the request named, or a transfer's stop was not named; details.stop is the runtime key as it is now","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"}}}}}},"x-primitive-permission":["functions:deploy"]}},"\/functions\/{id}\/template-update":{"post":{"operationId":"updateFunctionFromTemplate","summary":"Update a template function to the latest version","description":"Re-pull a function installed from a template to the template's current version. Refused for a function whose code was edited after install. The path id is the function id.","tags":["Templates"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The function id.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Update a template function to the latest version","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/UpdateFunctionFromTemplateResult"}}}]}}},"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"}}}}},"409":{"description":"function_forked, not_a_template_install, template_unavailable or update_needs_input: the function cannot be updated from its template as it stands","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"template_not_installable or scaffold_only: the template has no version this function can move to","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"}}}}}},"x-primitive-permission":["templates:install","functions:deploy"]}},"\/sendability":{"post":{"operationId":"checkSendability","summary":"Check whether a send would be allowed","description":"Answer whether the caller could send to the given recipient (from the given address, if any) without sending anything. summary explains a refusal in plain words.","tags":["Sending"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CheckSendabilityRequest"}}}},"responses":{"200":{"description":"Check whether a send would be allowed","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CheckSendabilityResult"}}}]}}},"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"}}}}}},"x-primitive-permission":["mail:send"],"x-primitive-permission-alternatives":[["mail:reply"]],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/webhooks\/deliveries\/{id}":{"get":{"operationId":"getDelivery","summary":"Get a webhook delivery","description":"One webhook delivery attempt record, by the id listDeliveries returns, with every attempt of the same delivery (a retry is recorded as a new attempt with its own id).","tags":["Webhook Deliveries"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The delivery id from listDeliveries.","schema":{"anyOf":[{"type":"string","maxLength":19,"pattern":"^\\d+$"},{"type":"string","format":"uuid"}]}}],"responses":{"200":{"description":"Get a webhook delivery","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetDeliveryResult"}}}]}}},"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"}}}}},"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"}}}}}},"x-primitive-permission":["webhooks:read"]}},"\/organization\/invitations":{"get":{"operationId":"listOrganizationInvitations","tags":["Organizations"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"cursor","description":"cursor for project collaboration.","in":"query","required":false,"schema":{"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)$"}},{"name":"limit","description":"limit for project collaboration.","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":100}}],"description":"Requires a current human organization owner or administrator. Machine credentials are refused. Invitations grant the chosen role, Member by default, only after verified acceptance through the sign-in or sign-up flow. Other organization memberships and project access are unchanged. Delivery accepted means submitted, not inbox delivery. Reuse request_id after an uncertain response.","responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_listOrganizationInvitations_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["invitations:manage"]},"post":{"operationId":"inviteOrganizationMember","tags":["Organizations"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `invitations.create` (Invite a person to the organization or a project), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"description":"Requires a current human organization owner or administrator. Machine credentials are refused. Invitations grant the chosen role, Member by default, only after verified acceptance through the sign-in or sign-up flow. Other organization memberships and project access are unchanged. Delivery accepted means submitted, not inbox delivery. Reuse request_id after an uncertain response.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Projects_inviteOrganizationMember_Input"}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_inviteOrganizationMember_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"service_unavailable: the address could not be checked, or invitations cannot be sent right now. Try again.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["invitations:manage"]}},"\/organization\/invitations\/{invitation_id}":{"delete":{"operationId":"revokeOrganizationInvitation","tags":["Organizations"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"invitation_id","description":"invitation_id for project collaboration.","in":"path","required":true,"schema":{"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)$"}}],"description":"Requires a current human organization owner or administrator. Machine credentials are refused. Invitations grant the chosen role, Member by default, only after verified acceptance through the sign-in or sign-up flow. Other organization memberships and project access are unchanged. Delivery accepted means submitted, not inbox delivery. Reuse request_id after an uncertain response.","responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_inviteOrganizationMember_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"invitation_unconfirmed: the identity provider did not confirm the invitation's state. Nothing was changed locally that could send a second invitation; retry the same request to refresh it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["invitations:manage"]}},"\/organization\/members":{"get":{"operationId":"listOrganizationPeople","tags":["Projects"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"cursor","description":"cursor for project collaboration.","in":"query","required":false,"schema":{"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)$"}},{"name":"limit","description":"limit for project collaboration.","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"query","description":"query for project collaboration.","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"description":"Requires a current human organization membership, or an org-wide API key: a super admin key, or a custom key with no addresses or domains whose grant holds members:read. A key lists members as the person it belongs to, at that person's live grant, and only while that person still holds members:read. Narrowed, Function and connected keys are refused. A caller holding members:read also gets each person's mail_access. No local paths or existing conversation history are shared.","responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_listOrganizationPeople_Result"}}}}},"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":"forbidden: not a signed-in member, or a key that is not org-wide (details.reason key_not_org_wide), or one whose person no longer stands behind it. grant_forbidden: the person the key belongs to does not hold members:read.","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"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"directory_limit: the organization agent directory is larger than projects can read. Narrow the directory before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["members:read"]}},"\/projects":{"get":{"operationId":"listProjects","tags":["Projects"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"cursor","description":"cursor for project collaboration.","in":"query","required":false,"schema":{"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)$"}},{"name":"limit","description":"limit for project collaboration.","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":100}}],"description":"Requires a current human organization membership. Machine credentials are refused. Results are scoped to current project access. No local paths or existing conversation history are shared.","responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_listProjects_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"directory_limit: the organization agent directory is larger than projects can read. Narrow the directory before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["projects:manage"]},"post":{"operationId":"createProject","tags":["Projects"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"description":"Requires a current human organization membership. Machine credentials are refused. Results are scoped to current project access. No local paths or existing conversation history are shared.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Projects_createProject_Input"}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_createProject_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"directory_limit: the organization agent directory is larger than projects can read. Narrow the directory before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["projects:manage"]}},"\/projects\/{project_id}":{"get":{"operationId":"getProject","tags":["Projects"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"project_id","description":"project_id for project collaboration.","in":"path","required":true,"schema":{"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)$"}}],"description":"Requires a current human organization membership. Machine credentials are refused. Results are scoped to current project access. No local paths or existing conversation history are shared.","responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_createProject_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"directory_limit: the organization agent directory is larger than projects can read. Narrow the directory before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["projects:manage"]},"put":{"operationId":"updateProject","tags":["Projects"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"project_id","description":"project_id for project collaboration.","in":"path","required":true,"schema":{"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)$"}}],"description":"Requires a current human organization membership. Machine credentials are refused. Results are scoped to current project access. No local paths or existing conversation history are shared.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Projects_updateProject_Input"}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_createProject_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"directory_limit: the organization agent directory is larger than projects can read. Narrow the directory before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["projects:manage"]},"delete":{"operationId":"deleteProject","tags":["Projects"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"project_id","description":"project_id for project collaboration.","in":"path","required":true,"schema":{"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)$"}},{"name":"version","description":"version for project collaboration.","in":"query","required":true,"schema":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}}],"description":"Requires a current human organization membership. Machine credentials are refused. Results are scoped to current project access. No local paths or existing conversation history are shared.","responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_deleteProject_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"directory_limit: the organization agent directory is larger than projects can read. Narrow the directory before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["projects:manage"]}},"\/projects\/{project_id}\/invitations":{"get":{"operationId":"listProjectInvitations","tags":["Projects"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"project_id","description":"project_id for project collaboration.","in":"path","required":true,"schema":{"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)$"}}],"description":"Requires a current human organization membership. Machine credentials are refused. Results are scoped to current project access. No local paths or existing conversation history are shared.","responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_listProjectInvitations_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"directory_limit: the organization agent directory is larger than projects can read. Narrow the directory before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["invitations:manage"]},"post":{"operationId":"inviteProjectMember","tags":["Projects"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"project_id","description":"project_id for project collaboration.","in":"path","required":true,"schema":{"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)$"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `invitations.create` (Invite a person to the organization or a project), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"description":"Requires a current human organization membership. Machine credentials are refused. Results are scoped to current project access. No local paths or existing conversation history are shared.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Projects_inviteProjectMember_Input"}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_inviteProjectMember_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"invitation_unconfirmed: the identity provider did not confirm the invitation's state. Nothing was changed locally that could send a second invitation; retry the same request to refresh it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"directory_limit: the organization agent directory is larger than projects can read. Narrow the directory before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["invitations:manage"]}},"\/projects\/{project_id}\/invitations\/{invitation_id}":{"delete":{"operationId":"revokeProjectInvitation","tags":["Projects"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"project_id","description":"project_id for project collaboration.","in":"path","required":true,"schema":{"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)$"}},{"name":"invitation_id","description":"invitation_id for project collaboration.","in":"path","required":true,"schema":{"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)$"}}],"description":"Requires a current human organization membership. Machine credentials are refused. Results are scoped to current project access. No local paths or existing conversation history are shared.","responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Projects_inviteProjectMember_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":"invitation_unconfirmed: the identity provider did not confirm the invitation's state. Nothing was changed locally that could send a second invitation; retry the same request to refresh it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"directory_limit: the organization agent directory is larger than projects can read. Narrow the directory before retrying.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["invitations:manage"]}},"\/mailbox-access":{"get":{"operationId":"listMailboxAccess","summary":"List mailbox access","description":"Every access entry in the organization: who (a member, group, agent or address) may read, draft or send for each mailbox or domain. An entry that reads a mailbox reads the full email; raw MIME follows the person's mail.raw:read. `depth` is deprecated: it is accepted and ignored, and new entries record content. With no entries, `note` says where each member's mail access comes from instead. Needs members:read: a person, or an org-wide API key whose person holds it.","tags":["Mailbox access"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List mailbox access","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListMailboxAccessResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["members:read"]}},"\/mailboxes\/{address}\/access":{"put":{"operationId":"setMailboxAccess","summary":"Replace a mailbox's access","description":"Replaces the whole access list of one mailbox address, or of a domain (`@example.com` or the bare domain). Needs delegations:manage: a dashboard session, the person's own Primitive CLI, or an org-wide API key, which acts for the person it belongs to and works only while that person is still an owner or admin holding the permission. Any other OAuth app answers `step_up_required` until the person confirms in the dashboard and the request carries the Primitive-Step-Up header. Narrowed, Function and connected keys are refused. Nobody grants access beyond their own (rule 1): each added or widened entry must be within the caller's own mail access and permissions; an API key never gives access to a member's personal mailbox. Every change is recorded in the credential audit log.","tags":["Mailbox access"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"A mailbox address, or a domain (`@example.com` or `example.com`) for every mailbox in it. URL-encode the `@`.","schema":{"type":"string","minLength":1,"maxLength":330}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `delegations.write` (Change mailbox access), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SetMailboxAccessRequest"}}}},"responses":{"200":{"description":"Replace a mailbox's access","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/SetMailboxAccessResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["delegations:manage"]}},"\/groups":{"get":{"operationId":"listGroups","summary":"List groups","description":"Every group, with its members and role. A group can be named in a mailbox access entry. Needs members:read: a person, or an org-wide API key whose person holds it.","tags":["Mailbox access"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List groups","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListGroupsResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["members:read"]},"post":{"operationId":"createGroup","summary":"Create a group","description":"Creates a group. Needs roles:assign. With an org-wide API key (acting for its person) or the person's own Primitive CLI the group must have no role: a group with a role is role assignment, done in a dashboard session or with a session step-up. Adding members to a group that holds mailbox access gives them that access, so each of its entries is checked as for a mailbox edit. A group whose role names a member's personal mailbox holds only that member: giving the role to, or adding, anyone else is refused with 403 `grant_forbidden` and `details.reason` `personal_mailbox_member_only`.","tags":["Mailbox access"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateGroupRequest"}}}},"responses":{"201":{"description":"Create a group","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CreateGroupResult"}}}]}}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["roles:assign"],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/groups\/{id}":{"patch":{"operationId":"updateGroup","summary":"Change a group","description":"Renames a group or replaces its members. With an org-wide API key or the person's own Primitive CLI only role-free groups are changed. As on creation, a role naming a member's personal mailbox is never given through the group to anyone else (`personal_mailbox_member_only`).","tags":["Mailbox access"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/UpdateGroupRequest"}}}},"responses":{"200":{"description":"Change a group","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/UpdateGroupResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["roles:assign"]},"delete":{"operationId":"deleteGroup","summary":"Delete a group","description":"Deletes a group; its mailbox access entries go with it. With an org-wide API key or the person's own Primitive CLI only role-free groups are deleted.","tags":["Mailbox access"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Delete a group","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/DeleteGroupResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["roles:assign"]}},"\/credentials":{"get":{"operationId":"listCredentials","summary":"List every credential","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"Every credential that can act in the organization: API keys, Function keys, agent connection keys, connected apps (OAuth), agent delegates and, where enabled, CLI logins. Secrets are never returned. A key's grant.mail_scope.depth is raw only when it holds mail.raw:read and its scope lists an address or a domain, otherwise content. A rule the key lists that reads nothing any more (a deleted rule, or a share that ended) is listed under grant.mail_scope.ended_rules, each with its id and reason (rule_deleted or share_ended), and not under rules. Filter with kind. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session"},"parameters":[{"name":"kind","in":"query","required":false,"description":"Only credentials of this kind.","schema":{"type":"string","enum":["org_key","function_key","connected_key","oauth","agent_delegate","cli_user_access"]}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_listCredentials_Result"}}}}},"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"}}}}}},"x-primitive-permission":["credentials:read"]}},"\/credentials\/revoke":{"post":{"operationId":"revokeCredential","summary":"Revoke a credential","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked by the person who holds it, whatever it reaches, or by a person whose own access covers the key's access (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. Revoking an API key also revokes the keys it replaced in its rotation that are still in their overlap and that you may revoke (predecessors_revoked_count; predecessors_left counts the rest), so its webhooks stop at once; the keys that replaced it carry its work on. Revoking a key already revoked does the same for those keys. What the credential set up or owns is listed in standing_objects for review, and for an API key also what it carries on for the keys it was rotated from, each saying whether anything still acts for it (still_acting, with the key in acting_key_id) and, for a webhook, whether it is still delivering. An organization-owned object (org_owned true) keeps working; any other keeps working only while someone can still act for it, such as a key of the same rotation that still works, or whoever it was given to, except that a block filter an app made keeps blocking, within the reach the app had, unless the person revoking chose to stop it. An API key (kind org_key or connected_key) that is already revoked, and whose record is kept, answers 200 with already_revoked true, to a caller who could have revoked it, and revoked counts only the keys it replaced that were revoked now; anyone else gets the answer they would get for a key that does not exist. The person's own Primitive CLI login revokes an API key (kind org_key) as the person, with no step-up unless the organization's credential policy sets cli_writes_require_approval (then it takes a keys.create step-up); every other kind (a connected key named as org_key included, 403 grant_forbidden target_not_covered), and every other app even with a step-up, is refused outside the dashboard (403). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:manage. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session_or_cli"},"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `keys.create` (Create, rotate, change or revoke an API key), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session. Here only the person's own Primitive CLI login revoking an API key (kind `org_key`) uses it, when the route answers it 403 `step_up_required`: where the organization's credential policy sets `cli_writes_require_approval`, for example. Otherwise it needs no token. Every other app is refused here (403, `details.reason` `dashboard_only`), with or without a token.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_revokeCredential_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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\/Credentials_revokeCredential_Input"}}}},"x-primitive-permission":["keys:manage"]}},"\/credentials\/revoke-all":{"post":{"operationId":"revokeAllCredentials","summary":"Revoke every credential (emergency)","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"The emergency response to a leak: revokes every API key, agent connection key and connected app created since a time, rotates the organization Function key and every running Function's own key, and optionally suspends Functions. Owners only, from a dashboard session, and the body must confirm with \"revoke all\". Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session. The person's role must hold credentials:revoke_all. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session"},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_revokeAllCredentials_Result"}}}}},"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"}}}}}},"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Credentials_revokeAllCredentials_Input"}}}},"x-primitive-permission":["credentials:revoke_all"]}},"\/credential-events":{"get":{"operationId":"listCredentialEvents","summary":"Read the credential audit trail","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"Credential events (created, rotated, edited, revoked, used with a step-up, policy changes), newest first, with a cursor. format=csv returns the same page as CSV. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session. The person's role must hold credential_events:read. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session"},"parameters":[{"name":"cursor","in":"query","required":false,"description":"Opaque next_cursor from the preceding page. Omit for the first page.","schema":{"type":"string","maxLength":256}},{"name":"limit","in":"query","required":false,"description":"Events per page, from 1 to 500.","schema":{"type":"integer","minimum":1,"maximum":500}},{"name":"actor_id","in":"query","required":false,"description":"Only events this person caused.","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":"credential_id","in":"query","required":false,"description":"Only events about this credential.","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":"event_type","in":"query","required":false,"description":"Only events of this type, for example key.created.","schema":{"type":"string","maxLength":100,"pattern":"^[a-z][a-z_]*(\\.[a-z][a-z_]*)+$"}},{"name":"since","in":"query","required":false,"description":"Only events at or after this time (ISO 8601).","schema":{"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)))$"}},{"name":"until","in":"query","required":false,"description":"Only events before this time (ISO 8601).","schema":{"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)))$"}},{"name":"format","in":"query","required":false,"description":"json (the default) or csv.","schema":{"type":"string","enum":["json","csv"]}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_listCredentialEvents_Result"}}}},"text\/csv":{"schema":{"type":"string"}}},"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"}}}}}},"x-primitive-permission":["credential_events:read"]}},"\/credential-policy":{"get":{"operationId":"getCredentialPolicy","summary":"Read the credential policy","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"The organization credential policy: key lifetime and expiry rules, who may create super admin keys, whether members may create keys and the other rules every new key is held to. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session"},"parameters":[],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_getCredentialPolicy_Result"}}}}},"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"}}}}}},"x-primitive-permission":["credentials:read"]},"patch":{"operationId":"updateCredentialPolicy","summary":"Change the credential policy","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"Change one or more policy rules. Admins may only tighten the policy; loosening any rule needs an owner. Owners and admins are emailed about every change. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session. The person's role must hold credential_policy:manage. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session"},"parameters":[],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_updateCredentialPolicy_Result"}}}}},"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"}}}}}},"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Credentials_updateCredentialPolicy_Input"}}}},"x-primitive-permission":["credential_policy:manage"]}},"\/keys\/builder":{"get":{"operationId":"getKeyBuilder","summary":"Read the key builder","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"The key presets the caller may use (each with whether it is available and why not), the permission matrix for custom keys and the policy in force. An area marked org_wide_only (Members) is listed only to a person who holds its permission, and only a custom key with no addresses or domains may hold it. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session"},"parameters":[],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_getKeyBuilder_Result"}}}}},"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"}}}}}},"x-primitive-permission":["keys:read"]}},"\/keys":{"post":{"operationId":"createKey","summary":"Create an API key","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"Create an API key, super admin or narrowed to a preset or a custom permission set, optionally with an expiry and conditions. The secret is returned once. A key never holds more than the person creating it; the credential policy decides who may create which keys. grant is required: a request without it is refused (400 validation_error, details.reason grant_required), never treated as super admin. A custom key must say which mail it reaches: the addresses and domains it lists (grant.addresses, grant.domains), or every mailbox in the organization with grant.mail_scope \"org\", which cannot be combined with them; one that says neither is refused (400 validation_error, details.reason mail_scope_required). Super admin, Send only and Read only reach every mailbox by definition. members:read is only for an org-wide custom key: naming it with grant.addresses or grant.domains is refused (400). Owners and admins are emailed about every super admin key. Send an Idempotency-Key to retry safely: a second create by the same person under the same key makes no second key and is refused (409 idempotency_key_reused) with the first key's id in error.details.key_id, so a caller whose first answer was lost can revoke that key. The onboarding field is for the dashboard only. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. While the organization's keys API is not enabled, this answers 404 feature_disabled (details.feature keys_api; contact Primitive to turn it on), except that a dashboard session may still create a super admin key (the dashboard's own create).\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.","x-primitive-person-only":{"access":"step_up","step_up_purpose":"keys.create","cli_without_step_up":true},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 200 printable characters. A second create by the same person under the same key makes no second key: it answers 409 idempotency_key_reused naming the first key in error.details.key_id.","schema":{"type":"string","maxLength":200}},{"name":"Primitive-Not-After","in":"header","required":false,"description":"An ISO 8601 time, with an Idempotency-Key. A request that reaches the server after it creates nothing itself (409 request_expired), so a caller that lost an answer can wait past it and then revoke whatever the attempt made, knowing no late copy can still create a key. A key an earlier copy made is still reported as 409 idempotency_key_reused.","schema":{"type":"string","format":"date-time"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `keys.create` (Create, rotate, change or revoke an API key), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session. The Primitive app is refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`). The person's own Primitive CLI login needs no token for this purpose while the organization's keys API is on (a token it sends is still checked), unless the organization's credential policy sets `cli_writes_require_approval`; while the keys API is off, a token it sends is refused the same way.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"201":{"description":"Created","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_createKey_Result"}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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\/Credentials_createKey_Input"}}}},"x-primitive-permission":["keys:manage"]},"get":{"operationId":"listKeys","summary":"List API keys","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"Key metadata, never a secret: name, prefix, grant, creator, last use, expiry, suspension and whether each key works now (status). Revoked keys are left out unless include_revoked is true, which lists them too with status revoked, revoked_at and revoked_by, under the same access rules as keys that work. A grant's mail_scope.depth is raw only when the key holds mail.raw:read and its scope lists an address or a domain, otherwise content. A rule the key lists that reads nothing any more (a deleted rule, or a share that ended) is listed under grant.mail_scope.ended_rules, each with its id and reason (rule_deleted or share_ended), and not under rules. grant.mail_scope.rule_versions gives each rule it lists directly its current_version, state and version_saved_at. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session_or_cli"},"parameters":[{"name":"cursor","in":"query","required":false,"description":"cursor (optional filter).","schema":{"type":"string","maxLength":200}},{"name":"include_revoked","in":"query","required":false,"description":"true to list revoked keys as well, newest first among the rest; false (the default) lists only keys that are not revoked. The cursor does not carry it: send it with every page.","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_listKeys_Result"}}}}},"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"}}}}}},"x-primitive-permission":["keys:read"]}},"\/keys\/{id}\/rotate":{"post":{"operationId":"rotateKey","summary":"Rotate an API key","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"Mint a successor with a new id and the same name, grant, expiry and conditions, and return its secret once, with its name and grant so you can see they carried over. The old key keeps working for overlap_seconds, then stops: 24 hours when overlap_seconds is omitted, at most 7 days, never past its own expires_at, and at once with 0 (which is how to rotate a key whose secret leaked). previous_key_expires_at says when the old key stops. Revoking the successor during the overlap also revokes the old key when you may revoke that one too; the revoke's predecessors_left counts any left working. Rotating does not extend a key: the successor keeps the old expiry unless expires_at gives it a new one. That needs keys:manage (403 forbidden otherwise), is held to the credential policy (400 when past its maximum lifetime, with the latest expiry it allows now in the message), and someone limited to their own keys can only keep or bring it forward (403 grant_forbidden, details.reason own_key_tighten_only); the old key then never outlives the new one. The person rotating must cover the key's access (403 grant_forbidden, details.reason target_not_covered), and a key an owner holds is rotated by an owner only (403 forbidden, details.reason owner_key_rotation), since rotating hands over the new secret. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.","x-primitive-person-only":{"access":"step_up","step_up_purpose":"keys.create","cli_without_step_up":true},"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the object this operation acts on.","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":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `keys.create` (Create, rotate, change or revoke an API key), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session. The Primitive app is refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`). The person's own Primitive CLI login needs no token for this purpose while the organization's keys API is on (a token it sends is still checked), unless the organization's credential policy sets `cli_writes_require_approval`; while the keys API is off, a token it sends is refused the same way.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_rotateKey_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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\/Credentials_rotateKey_Input"}}}},"x-primitive-permission":["keys:manage"]}},"\/keys\/{id}":{"patch":{"operationId":"updateKey","summary":"Change an API key","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"Rename a key, change its expiry, its grant or its conditions, or suspend it. A later expires_at extends the key, which is how to keep a key working past its expiry (rotating keeps it, unless the rotation gives the new key an expires_at): it is held to the credential policy (400 when past its maximum lifetime, with the latest expiry it allows now in the message), and like any loosening needs the person to be allowed to create a key with the key's access. An expired key's expiry cannot be changed (409 conflict, details.reason key_expired); make a new key instead. Any change needs the person's own access to cover the key's access now (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. The one exception is suspending a key (suspended true with no other change; expected_grant_ref may accompany it), which the person who holds it may do whatever the key reaches, calling as themself rather than through another app; lifting a suspension is a change like any other. A grant change is a new version of the key and is held to the same rules as creating one (a custom grant that does not say which mail it reaches is refused with details.reason mail_scope_required), so it takes the same step-up. It replaces the whole grant: send expected_grant_ref (grant.ref from GET \/v1\/keys\/{id}) to have it refused (409 conflict, details.reason grant_changed) if the grant changed since that read; a grant change sent without it answers grant_unguarded: true. On a key limited to selected resources (grant.resources), a grant change must drop the limits on purpose with a custom grant and grant.resources {}; one that leaves them out is refused (409 conflict, details.reason key_has_resource_limits) and nothing changes. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.","x-primitive-person-only":{"access":"step_up","step_up_purpose":"keys.create","cli_without_step_up":true},"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the object this operation acts on.","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":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `keys.create` (Create, rotate, change or revoke an API key), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session. The Primitive app is refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`). The person's own Primitive CLI login needs no token for this purpose while the organization's keys API is on (a token it sends is still checked), unless the organization's credential policy sets `cli_writes_require_approval`; while the keys API is off, a token it sends is refused the same way.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_updateKey_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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\/Credentials_updateKey_Input"}}}},"x-primitive-permission":["keys:manage"]},"get":{"operationId":"getKey","summary":"Get an API key","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"One key's metadata, never its secret, with whether it works now (status). A revoked key whose record is kept is answered too, with status revoked, revoked_at and revoked_by, under the same access rules as a key that works: one the caller may not read is 404, revoked or not. Its grant's mail_scope.depth is raw only when the key holds mail.raw:read and its scope lists an address or a domain, otherwise content. A rule the key lists that reads nothing any more (a deleted rule, or a share that ended) is listed under grant.mail_scope.ended_rules, each with its id and reason (rule_deleted or share_ended), and not under rules. grant.mail_scope.rule_versions gives each rule it lists directly its current_version, state and version_saved_at. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session_or_cli"},"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the object this operation acts on.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_getKey_Result"}}}}},"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"}}}}}},"x-primitive-permission":["keys:read"]}},"\/keys\/{id}\/suggestion":{"get":{"operationId":"getKeySuggestion","summary":"Suggest a least-privilege grant for a key","tags":["Credentials"],"security":[{"BearerAuth":[]}],"description":"The permissions a key used over the last 30 days, as a custom grant that would have served every successful call (also as builder areas), the narrowest mail scope its calls support (depth is deprecated: raw when it downloaded raw MIME, otherwise content) (the mailboxes of the messages it named and sent, with notes on what the request log cannot account for), and whether it is unused. Calls to routes only the super admin key can use (groups and mailbox access) derive no permission, since a custom key is refused there; super_admin_only_calls counts them. usage gives the same window anonymized: successful and failed requests per UTC day, the routes called by their declared template (never the called path) with the permission each needed, and how many mailboxes the key read and sent from (counts only, and only for a person with access to the whole organization's mail). Nothing is applied: narrowing is a change to the key (PATCH \/v1\/keys\/:id). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session"},"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the object this operation acts on.","schema":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/Credentials_getKeySuggestion_Result"}}}}},"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"}}}}}},"x-primitive-permission":["credentials:read"]}},"\/me\/grants":{"get":{"operationId":"listMyGrants","summary":"List my connected apps and CLI logins","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"The apps the calling person connected to this organization (OAuth) and, where enabled, their CLI logins. A CLI login signs the person in to every organization they belong to. A connection signed out while it still owns scheduled mail or other items stays listed with logged_out true, so it can still be revoked. Other apps the person connected cannot list them.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session_or_cli"},"parameters":[{"name":"cursor","in":"query","required":false,"description":"Opaque next_cursor from the preceding page. Omit for the first page.","schema":{"type":"string","maxLength":120}},{"name":"limit","in":"query","required":false,"description":"Connections per page, from 1 to 200; defaults to 100.","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_listMyGrants_Result"}}}}},"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"}}}}}}}},"\/me\/grants\/{id}\/revoke":{"post":{"operationId":"revokeMyGrant","summary":"Revoke one of my connected apps or CLI logins","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"Sign out one of the calling person's own connections, for example a lost laptop. Revoking a CLI login ends it in every organization. Another person's connection is answered 404. Every role may end its own connections. From a dashboard session or the person's Primitive CLI any of them; any other app the person connected may end only its own connection (403 otherwise).\n\nPerson only: a dashboard session or the person's own OAuth token (the CLI or an app they connected). API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"person"},"parameters":[{"name":"id","in":"path","required":true,"description":"The connection id from listMyGrants.","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":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_revokeMyGrant_Result"}}}}},"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"}}}}}},"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/OrgSettings_revokeMyGrant_Input"}}}}}},"\/oauth-grants":{"get":{"operationId":"listOAuthGrants","summary":"List the organization's connected apps","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"Every app a person connected to this organization through OAuth, with who connected it.\n\nPerson only: a dashboard session. The person's role must hold oauth_grants:manage. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session"},"parameters":[{"name":"cursor","in":"query","required":false,"description":"Opaque next_cursor from the preceding page. Omit for the first page.","schema":{"type":"string","maxLength":120}},{"name":"limit","in":"query","required":false,"description":"Connections per page, from 1 to 200; defaults to 100.","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_listOAuthGrants_Result"}}}}},"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"}}}}}},"x-primitive-permission":["oauth_grants:manage"]}},"\/oauth-grants\/{id}\/revoke":{"post":{"operationId":"revokeOAuthGrant","summary":"Revoke a connected app","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"Revoke one connected app for the whole organization. Where the app has revocation rights on, only the person who connected it, an owner or an admin may revoke it. Scheduled and repeating mail it created is stopped.\n\nPerson only: a dashboard session. The person's role must hold oauth_grants:manage. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"session"},"parameters":[{"name":"id","in":"path","required":true,"description":"The connected app id from listOAuthGrants.","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":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_revokeOAuthGrant_Result"}}}}},"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"}}}}}},"x-primitive-permission":["oauth_grants:manage"]}},"\/organization":{"get":{"operationId":"getOrganization","summary":"Get the organization name and logo","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"The organization's name and logo as members and invitees see them.\n\nPerson only: a dashboard session or the person's own OAuth token (the CLI or an app they connected). API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"person"},"parameters":[],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_getOrganization_Result"}}}}},"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":"conflict: the change conflicts with the organization profile as it stands.","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":"upstream_unavailable: the organization profile could not be reached, or (after a write) the change was saved but could not be read back. Retryable for a read; read the profile again before repeating a write.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"upstream_unavailable: the organization profile is temporarily unavailable. Retry-After says when to retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":1}}},"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}}},"patch":{"operationId":"updateOrganization","summary":"Rename the organization","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"Change the organization's name.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `org_settings.write` in the Primitive-Step-Up header. Without one an OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role could not do it anyway). The person's role must hold org:manage (or members:manage). API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.","x-primitive-person-only":{"access":"step_up","step_up_purpose":"org_settings.write"},"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_updateOrganization_Result"}}}}},"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":"conflict: the change conflicts with the organization profile as it stands.","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":"upstream_unavailable: the organization profile could not be reached, or (after a write) the change was saved but could not be read back. Retryable for a read; read the profile again before repeating a write.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"upstream_unavailable: the organization profile is temporarily unavailable. Retry-After says when to retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":1}}},"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/OrgSettings_updateOrganization_Input"}}}},"x-primitive-permission":["org:manage"],"x-primitive-permission-alternatives":[["members:manage"]]}},"\/organization\/logo":{"put":{"operationId":"updateOrganizationLogo","summary":"Replace the organization logo","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"Upload a PNG, JPEG, GIF or WebP logo of at most 900 KB, base64 encoded.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `org_settings.write` in the Primitive-Step-Up header. Without one an OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role could not do it anyway). The person's role must hold org:manage (or members:manage). API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.","x-primitive-person-only":{"access":"step_up","step_up_purpose":"org_settings.write"},"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_updateOrganizationLogo_Result"}}}}},"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":"conflict: the change conflicts with the organization profile as it stands.","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":"upstream_unavailable: the organization profile could not be reached, or (after a write) the change was saved but could not be read back. Retryable for a read; read the profile again before repeating a write.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"upstream_unavailable: the organization profile is temporarily unavailable. Retry-After says when to retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":1}}},"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/OrgSettings_updateOrganizationLogo_Input"}}}},"x-primitive-permission":["org:manage"],"x-primitive-permission-alternatives":[["members:manage"]]},"delete":{"operationId":"removeOrganizationLogo","summary":"Remove the organization logo","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"Remove the organization's logo.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `org_settings.write` in the Primitive-Step-Up header. Without one an OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role could not do it anyway). The person's role must hold org:manage (or members:manage). API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.","x-primitive-person-only":{"access":"step_up","step_up_purpose":"org_settings.write"},"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_removeOrganizationLogo_Result"}}}}},"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":"conflict: the change conflicts with the organization profile as it stands.","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":"upstream_unavailable: the organization profile could not be reached, or (after a write) the change was saved but could not be read back. Retryable for a read; read the profile again before repeating a write.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"upstream_unavailable: the organization profile is temporarily unavailable. Retry-After says when to retry.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":1}}},"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["org:manage"],"x-primitive-permission-alternatives":[["members:manage"]]}},"\/search-settings":{"get":{"operationId":"getSearchSettings","summary":"Get the semantic search setting","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"Whether the organization consents to semantic search, and whether it is active: it also needs a paid plan, the feature to be available to the organization, and HIPAA mode off.\n\nPerson only: a dashboard session or the person's own OAuth token (the CLI or an app they connected). API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"person"},"parameters":[],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_getSearchSettings_Result"}}}}},"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"}}}}}},"x-primitive-permission":["org:read"]},"put":{"operationId":"updateSearchSettings","summary":"Change the semantic search setting","tags":["Organization settings"],"security":[{"BearerAuth":[]}],"description":"Consent to, or withdraw consent from, semantic search. Consenting sends the organization's mail content to an embedding provider, so it is a person's decision. Turning it on is refused with 409 under HIPAA mode (hipaa_mode), without a paid plan (plan_required) or while it is not available to the organization (not_available). Turning it off is always allowed.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `org_settings.write` in the Primitive-Step-Up header. Without one an OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role could not do it anyway). The person's role must hold search_settings:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.","x-primitive-person-only":{"access":"step_up","step_up_purpose":"org_settings.write"},"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/OrgSettings_updateSearchSettings_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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\/OrgSettings_updateSearchSettings_Input"}}}},"x-primitive-permission":["search_settings:manage"]}},"\/email-notification-settings":{"get":{"operationId":"getEmailNotificationSettings","summary":"Get the organization's email notification settings","tags":["Email notifications"],"security":[{"BearerAuth":[]}],"description":"Every kind of notification email Primitive sends the organization's members, and who may turn it off. Each kind lists its label and description, its class, whether it needs an entitlement and whether the organization has it, the organization's mode (null for platform_required) and the modes it may choose, and whether the calling member gets it and may change that.\n\nPerson only: a dashboard session or the person's own OAuth token (the CLI or an app they connected). API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"person"},"parameters":[],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/EmailNotifications_getEmailNotificationSettings_Result"}}}}},"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"}}}}}},"x-primitive-permission":["org:read"]},"put":{"operationId":"updateEmailNotificationSettings","summary":"Change the organization's email notification settings","tags":["Email notifications"],"security":[{"BearerAuth":[]}],"description":"Set the organization's mode for one or more kinds: off (nobody gets it), optional_on (each member gets it unless they turn it off), optional_off (only members who turn it on get it) or required (every member gets it). An org_only kind takes only off or required (on for every owner and admin). Kinds left out keep their mode. An unknown kind is refused with 400 (validation_error, details.reason unknown_kind). A platform_required kind is refused with 409 (conflict, details.reason platform_required), and a mode the kind does not take (required for an optional kind, optional_on or optional_off for an org_only kind) with 409 (conflict, details.reason mode_not_allowed); nothing is changed. Answers the settings as they now stand. Each kind lists its label and description, its class, whether it needs an entitlement and whether the organization has it, the organization's mode (null for platform_required) and the modes it may choose, and whether the calling member gets it and may change that.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `org_settings.write` in the Primitive-Step-Up header. Without one an OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role could not do it anyway). The person's role must hold notification_policy:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.","x-primitive-person-only":{"access":"step_up","step_up_purpose":"org_settings.write"},"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/EmailNotifications_updateEmailNotificationSettings_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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\/EmailNotifications_updateEmailNotificationSettings_Input"}}}},"x-primitive-permission":["notification_policy:manage"]}},"\/email-notification-preferences":{"get":{"operationId":"getEmailNotificationPreferences","summary":"Get my email notification preferences","tags":["Email notifications"],"security":[{"BearerAuth":[]}],"description":"The notification emails the calling member gets, and which of them they may turn on or off. The org_only kinds, which the organization alone turns on or off, are not listed. Each kind lists its label and description, its class, whether it needs an entitlement and whether the organization has it, the organization's mode (null for platform_required) and the modes it may choose, and whether the calling member gets it and may change that.\n\nPerson only: a dashboard session or the person's own OAuth token (the CLI or an app they connected). The person's role must hold notifications:manage. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"person"},"parameters":[],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/EmailNotifications_getEmailNotificationPreferences_Result"}}}}},"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"}}}}}},"x-primitive-permission":["notifications:manage"]},"put":{"operationId":"updateEmailNotificationPreferences","summary":"Change my email notification preferences","tags":["Email notifications"],"security":[{"BearerAuth":[]}],"description":"Turn one or more kinds on or off for the calling member. Only a kind whose organization mode is optional_on or optional_off can be changed: a platform_required kind, an org_only kind, one the organization requires, and one it turned off are refused with 409 (conflict, details.reason not_member_choice, details.org_mode saying which: platform_required, org_only, required or off), and nothing is changed. An unknown kind is refused with 400. Answers the preferences as they now stand. Each kind lists its label and description, its class, whether it needs an entitlement and whether the organization has it, the organization's mode (null for platform_required) and the modes it may choose, and whether the calling member gets it and may change that.\n\nPerson only: a dashboard session or the person's own OAuth token (the CLI or an app they connected). The person's role must hold notifications:manage. API keys, agent connection keys and agents are refused.","x-primitive-person-only":{"access":"person"},"parameters":[],"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#\/components\/schemas\/EmailNotifications_updateEmailNotificationPreferences_Result"}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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\/EmailNotifications_updateEmailNotificationPreferences_Input"}}}},"x-primitive-permission":["notifications:manage"]}},"\/roles":{"get":{"operationId":"listRoles","summary":"List roles","description":"Every role in the organization (built-in, template copies and custom roles), with how many members and groups hold each, the templates not yet copied, and whether roles can be assigned now. `unassigned` describes role restricted on a member with no role_id, which is not a role and is never in `roles`. Needs members:read (owners, admins, members and roles that grant it); API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List roles","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListRolesResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["members:read"],"x-primitive-builtin-role-keys":{"$ref":"#\/components\/schemas\/BuiltinRoleKey"}},"post":{"operationId":"createRole","summary":"Create a custom role","description":"A role nobody can use to gain access they do not hold: its permissions and mail scope must be within the author's own. mail_scope is \"org\" (the default) or { addresses, domains }; any other value is refused with 400 `validation_error`. Every role that reaches mail reads it in full, and raw MIME follows the role's mail.raw:read; `depth` is deprecated and ignored. Its mail scope names no other member's personal mailbox: otherwise 403 `grant_forbidden` with `details.reason` `personal_mailbox_member_only`. Each permission must have what it requires in the same role: mail:read for mail.raw:read, mail.search:semantic, mail:share_own, changes:read and webhooks:read; mail:send for mail:send_batch and mail:send_as_member; functions:deploy for templates:install; agent_connections:manage for agent_connections:custom_domains. Otherwise 400 `validation_error` with `details.reason` `unmet_requirement`, `details.permission` and `details.requires`. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreateRoleRequest"}}}},"responses":{"201":{"description":"Create a custom role","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/CreateRoleResult"}}}]}}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["roles:assign"],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/roles\/{id}":{"patch":{"operationId":"updateRole","summary":"Change a custom role","description":"A new version of the role, with a new `id`: the version you edited is retired, the response is the new one (same `key`, `version` one higher), and every member and group holding the role moves to it. The old id answers 404 `not_found` from then on, so store the returned `id` (or look the role up by `key` in GET \/roles) before editing or assigning it again. Every holder whose access shrinks goes through the reduction path. mail_scope follows the same rules as creating a role; a role whose stored scope cannot be read must be sent a new mail_scope, otherwise 400 `validation_error` with `details.reason` `invalid_mail_scope`.  A personal mailbox the new version adds must be the author's own, and every holder must be the member of each personal mailbox it names: otherwise 403 `grant_forbidden` with `details.reason` `personal_mailbox_member_only`. Each permission in the new version must have what it requires in the same role, as when creating one, including permissions carried over unchanged. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/UpdateRoleRequest"}}}},"responses":{"200":{"description":"Change a custom role","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/UpdateRoleResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["roles:assign"]},"delete":{"operationId":"deleteRole","summary":"Delete a custom role","description":"Only a role nobody holds. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Delete a custom role","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/DeleteRoleResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["roles:assign"]}},"\/members\/{id}\/role":{"put":{"operationId":"setMemberRole","summary":"Assign a member's role","description":"A built-in role (owner, admin, member), a template key or a role id. Only an owner assigns or removes owner; nobody changes someone wider than themselves. A member whose role was changed several times in a row may be refused with 429 `rate_limited` for a few seconds; nothing was changed, so retry after `Retry-After`. A change the identity provider refuses answers 409 `conflict` with `details.reason` `role_change_refused`; nothing was changed. A role naming a member's personal mailbox goes to that member only: otherwise 403 `grant_forbidden` with `details.reason` `personal_mailbox_member_only`. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SetMemberRoleRequest"}}}},"responses":{"200":{"description":"Assign a member's role","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/SetMemberRoleResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["roles:assign"]}},"\/members\/{id}":{"delete":{"operationId":"removeMember","summary":"Remove a member","description":"Removes a member from the organization, after transferring what they own (POST \/members\/{id}\/transfer). Only an owner removes an owner; nobody removes someone whose access is wider than their own, or themselves. What they still own is then flagged or suspended for a decision (GET \/access-suspensions). When the identity provider no longer has the membership, the local membership is ended here with the same cleanup; a retry while the removal is still finishing completes it. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `members.remove` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client and the member, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for and only for the member it was confirmed for). Needs members:manage. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `members.remove` (Remove a member from the organization), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. It must have been confirmed for the member the path names, and works for that member only. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Remove a member","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/RemoveMemberResult"}}}]}}},"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":"validation_error: you cannot remove yourself.","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"}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the member was removed, or removed and added back, while you were removing them.","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":"cleanup_incomplete: the membership ended, but revoking what remains of the person's access did not finish.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"502":{"description":"removal_failed: the identity provider refused the removal. Nothing was changed.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"service_unavailable: the removal could not start, or did not finish; retry to finish it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"504":{"description":"removal_timeout: the identity provider did not confirm the removal in time. It is recorded here (the person loses access and no longer counts as an owner) and may still complete; if they are still listed, remove them again to finish.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["members:manage"]}},"\/organization\/leave":{"post":{"operationId":"leaveOrganization","summary":"Leave the organization","description":"Removes the caller from the organization: the same removal, and the same cleanup, as an owner or admin removing them, with what they still own flagged or suspended for a decision (GET \/access-suspensions). Any member may leave, whatever their role, and no permission is needed; the organization's only owner cannot, and is refused with 409 `last_owner` (make someone else an owner first). Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `members.remove` for themselves (confirm at the link a 403 `step_up_required` answer carries in `details.step_up_url`, which names this client and you, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). API keys are refused. You do not need to look up your user id: call this route once without the header, and the 403 `step_up_required` refusal names it (`details.target_user_id`) and gives the confirmation link (`details.step_up_url`). GET \/whoami also returns it as `user_id`.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Leave the organization","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/LeaveOrganizationResult"}}}]}}},"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":"forbidden (`details.reason` `dashboard_only`): an API key cannot leave for a person. step_up_required: an OAuth, CLI or agent token needs a `members.remove` step-up confirmed for yourself; `details` names your user id and the confirmation link. step_up_invalid: the Primitive-Step-Up token is unknown, expired, already used, or was confirmed for another purpose, member or client (`details.reason` `step_up_client_mismatch` for another client, or none). step_up_refused: a step-up token is accepted only with your own OAuth or CLI token (never with an API key or a dashboard session, nor with a token issued for MCP).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"not_found: you are not a member of this organization (you have already left).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"last_owner: you are the only owner. conflict: you were removed, or removed and added back, while leaving.","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":"cleanup_incomplete: you have left, but revoking what remains of your access did not finish.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"502":{"description":"removal_failed: the identity provider refused the removal. Nothing was changed.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"service_unavailable: leaving could not start, or did not finish; retry to finish it.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"504":{"description":"removal_timeout: the identity provider did not confirm in time. Leaving is recorded here (you lose access and no longer count as an owner) and may still complete; if the organization is still listed for you, ask an owner to remove you.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `members.remove` (Remove a member from the organization), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. It must have been confirmed for yourself, the person leaving. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/access-mode":{"get":{"operationId":"getAccessMode","summary":"Get the mailbox access mode","description":"Whole-org or delegated mailbox access. Holders of roles:assign also get the preview of a switch (every item that needs a decision, each with the decision_options it takes, worded for what the switch does) and the decisions made, plus notices: member and group domain entries that delegated mode keeps off personal mailboxes, each with how many it leaves out. Notices are informational, never decided, and never hold the switch (notices_truncated past 500, notices_unavailable when they could not be read). In whole-org mode it also lists narrowed_agents: every live agent connection the switch narrows to an own-address key (personal agents of members, and shared agents), informational in the same way (narrowed_agents_truncated past 500, narrowed_agents_unavailable when they could not be read). Needs members:read (owners, admins, members and roles that grant it); API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Get the mailbox access mode","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetAccessModeResult"}}}]}}},"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":"feature_disabled: the access mode feature is not enabled for the organization. For a person, details.effective_mode names the mode that applies meanwhile: whole_org, or delegated if the organization switched to it earlier.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["members:read"]},"put":{"operationId":"setAccessMode","summary":"Switch the mailbox access mode","description":"Delegated mode is refused until every preview item has a decision; switching back to whole-org is always allowed. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `access_mode.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs access_mode:manage (owners). API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SetAccessModeRequest"}}}},"responses":{"200":{"description":"Switch the mailbox access mode","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/SetAccessModeResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["access_mode:manage"],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `access_mode.write` (Change the mailbox access mode), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}]}},"\/access-mode\/decisions":{"post":{"operationId":"decideAccessMode","summary":"Decide access mode preview items","description":"Decide items the switch to delegated mode would change. Keep exempts an item; revoke revokes, deletes or stops it; narrow holds an item listed for a person (suspended or flagged) to be decided on the waiting list, where a key or Function can be narrowed; on an unattributed object, which no reduction holds, narrow leaves it as keep does, and the preview does not offer it. A mailbox loss or a connected app is only acknowledged (keep); a controlled key or Function and a self-given access entry take keep or revoke; another decision is refused with 400. Revoking a filter needs confirm: true. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `access_mode.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs access_mode:manage (owners). API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/DecideAccessModeRequest"}}}},"responses":{"200":{"description":"Decide access mode preview items","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/DecideAccessModeResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["access_mode:manage"],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `access_mode.write` (Change the mailbox access mode), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/relay-auto-join":{"get":{"operationId":"getRelayAutoJoin","summary":"Get the auto-join setting","description":"Whether someone who proves an address on the organization's mail relay domain with Google Workspace joins automatically. Auto-joined people have no mail access until an owner or admin assigns a role. `option` is null when auto-join is off and an invitation is required; legacy mail-grant settings read as `no_access`. `options` lists the choices. Answers 404 `feature_disabled` unless the organization has mail relay.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Get the auto-join setting","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/GetRelayAutoJoinResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["members:read"]},"put":{"operationId":"setRelayAutoJoin","summary":"Change the auto-join setting","description":"`option` is a choice's id from the GET, or null to require an invitation (turning it off needs only members:manage). Auto-joined people have no mail access until an owner or admin assigns a role; legacy mail-grant choices cannot be selected. Choosing an option answers 404 `feature_disabled` unless the organization has mail relay, 400 `validation_error` for an id outside the current catalog, 403 `grant_forbidden` without owner or admin assignment authority, and 409 `role_assignment_unavailable` when the restricted role cannot be assigned yet. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `relay_auto_join.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs members:manage, and roles:assign to turn it on. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SetRelayAutoJoinRequest"}}}},"responses":{"200":{"description":"Change the auto-join setting","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/SetRelayAutoJoinResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["members:manage"],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `relay_auto_join.write` (Change who can join the organization automatically), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}]}},"\/break-glass":{"get":{"operationId":"listBreakGlass","summary":"List break-glass grants","description":"Every break-glass grant, active or ended. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `break_glass.read` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs access_mode:manage (owners). API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List break-glass grants","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListBreakGlassResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["access_mode:manage"],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `break_glass.read` (List break-glass access), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}]},"post":{"operationId":"startBreakGlass","summary":"Open break-glass access","description":"Time-limited (at most 24 hours), audited access to every mailbox for the owner opening it; every other owner is told. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `break_glass.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs access_mode:manage (owners). API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/StartBreakGlassRequest"}}}},"responses":{"201":{"description":"Open break-glass access","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/StartBreakGlassResult"}}}]}}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["access_mode:manage"],"parameters":[{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `break_glass.write` (Open or end break-glass access), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}]}},"\/break-glass\/{id}\/end":{"post":{"operationId":"endBreakGlass","summary":"End break-glass access","description":"Ends a grant early. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `break_glass.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs access_mode:manage (owners). API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `break_glass.write` (Open or end break-glass access), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"End break-glass access","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/EndBreakGlassResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["access_mode:manage"]}},"\/members\/{id}\/owned-items":{"get":{"operationId":"listOwnedItems","summary":"List a member's items","description":"Keys, Functions, endpoints and other items a member owns, with whether the chosen transferee (`to`) covers each. Only an owner lists an owner's items. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `members.items_read` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client and the member, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for and only for the member it was confirmed for). Needs members:manage. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"to","in":"query","required":false,"description":"The member who would receive the items, to show whether they cover each one.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `members.items_read` (List a member's items), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. It must have been confirmed for the member the path names, and works for that member only. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"List a member's items","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListOwnedItemsResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["members:manage"]}},"\/members\/{id}\/transfer":{"post":{"operationId":"transferOwnedItems","summary":"Transfer a member's items","description":"Moves items to another member, who must cover each; keys can be rotated as they move. Attribution of what was created is unchanged. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `members.transfer` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client and the member, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for and only for the member it was confirmed for). Needs members:manage. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `members.transfer` (Transfer a member's items), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. It must have been confirmed for the member the path names, and works for that member only. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransferOwnedItemsRequest"}}}},"responses":{"200":{"description":"Transfer a member's items","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/TransferOwnedItemsResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["members:manage"]}},"\/access-suspensions":{"get":{"operationId":"listSuspensions","summary":"List suspended and flagged access","description":"Items a removal or a narrowed role left suspended or flagged for a decision. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `suspensions.read` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs members:manage. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"include_decided","in":"query","required":false,"description":"With `true`, also list items already decided.","schema":{"type":"string","enum":["true","false"]}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `suspensions.read` (Review suspended access), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"List suspended and flagged access","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/ListSuspensionsResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["members:manage"]}},"\/access-suspensions\/{id}\/decision":{"post":{"operationId":"decideSuspension","summary":"Decide a suspension","description":"Keep, narrow or revoke one suspended or flagged item. Keeping it needs the decider to cover it, and so does revoking an API key unless the decider holds it and decides as themself rather than through another app (revoking an uncovered key someone else holds is refused with `grant_forbidden`, `target_not_covered`). Keeping an API key rotated (`rotate: true`, other than the shared Function key) also revokes its older secrets still in a rotation overlap, and needs keys:manage on both the credential and the person: without it, or if the decider lost members:manage meanwhile, it is refused with `grant_forbidden` (`keys_manage_required` or `actor_access_changed`) and nothing is rotated. A rule share paused by a role change (`email_rule_share`) takes keep, which leaves it paused, or revoke, which ends the share and needs confirm: true (the person it was shared with loses the emails already shared; without it, 409 `confirmation_required`, as for revoking a filter). Neither of the share's two people may keep it (`grant_forbidden`, `share_decision_forbidden`). Access a person gave on their own mailbox (`mailbox_access`) takes keep or revoke, and both need delegations:manage; revoke removes only what that person gave on that mailbox. Nobody the access reaches, nor the person who gave it, may keep it (`grant_forbidden`, `mailbox_access_decision_forbidden`). Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `suspensions.decide` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs members:manage. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of the group, role, member, grant or suspension the path names.","schema":{"type":"string","format":"uuid"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `suspensions.decide` (Decide suspended access), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/DecideSuspensionRequest"}}}},"responses":{"200":{"description":"Decide a suspension","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/DecideSuspensionResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you decided, or a member was removed and added back; look again, then retry. confirmation_required: revoking a filter or a rule share needs confirm: true. keep_refused: an item of a deleted account that could not be moved to you, or one a revoked app left that could not be kept. rotation_in_progress: another rotation of the shared Function key finished first, and nothing was revoked by this one. rotation_refused: the key cannot be rotated (for example, it was rotated already). feature_disabled: rotating the Function key is not turned on for the organization (details.feature function_key_rotation). Narrowing passes on the refusals of the key edit (PATCH \/keys\/{id}) and the Function key change (PUT \/functions\/{id}\/runtime-key) it makes.","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":"rotation_incomplete: some Functions could not take the new Function key yet, so the old one is kept; retry to finish. redeploy_failed: the item was kept, but redeploying its Function failed; redeploy the Function.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"service_unavailable: another access change is in progress, or the credential policy could not be read; retry in a moment.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"x-primitive-permission":["members:manage"]}},"\/me\/mailboxes\/{address}\/access\/agents\/{connection_id}":{"delete":{"operationId":"removeOwnAgentAccess","summary":"Remove an agent from your own mailbox","description":"Removes an agent from the access list of the caller's member mailbox. Agent access is given when they connect their own agent, and is not edited through the list while that feature is on. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `delegations.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs member_address:manage. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"A mailbox address, or a domain (`@example.com` or `example.com`) for every mailbox in it. URL-encode the `@`.","schema":{"type":"string","minLength":1,"maxLength":330}},{"name":"connection_id","in":"path","required":true,"description":"The id of the agent connection to take off the mailbox.","schema":{"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)$"}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `delegations.write` (Change mailbox access), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"responses":{"200":{"description":"Remove an agent from your own mailbox","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/RemoveOwnAgentAccessResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["member_address:manage"]}},"\/me\/mailboxes\/{address}\/access":{"put":{"operationId":"setOwnMailboxAccess","summary":"Share your own mailbox","description":"Replaces the access list of the caller's own member mailbox (or their personal agent's address); needs no delegations:manage. Nobody grants access beyond their own. A role that can only read this mailbox is refused. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `delegations.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs member_address:manage and mail:send_as_member. API keys are refused.","tags":["Roles and members"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"description":"A mailbox address, or a domain (`@example.com` or `example.com`) for every mailbox in it. URL-encode the `@`.","schema":{"type":"string","minLength":1,"maxLength":330}},{"name":"Primitive-Step-Up","in":"header","required":false,"description":"A single-use step-up token of purpose `delegations.write` (Change mailbox access), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.","schema":{"type":"string","pattern":"^pstep_[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SetOwnMailboxAccessRequest"}}}},"responses":{"200":{"description":"Share your own mailbox","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/SuccessEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#\/components\/schemas\/SetOwnMailboxAccessResult"}}}]}}},"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":"feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.","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"}}}}}},"x-primitive-permission":["member_address:manage","mail:send_as_member"]}},"\/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-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"}}}}}},"x-primitive-permission":["mail_sync:manage"]},"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"}}}}}},"x-primitive-permission":["mail_sync:manage"]}},"\/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-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"}}}}}},"x-primitive-permission":["mail_sync:manage"]},"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"}}}}}},"x-primitive-permission":["mail_sync:manage"]}},"\/sent-emails\/{id}\/release":{"post":{"operationId":"releaseSentEmail","summary":"Release a held scheduled send","tags":["Sending"],"description":"Releases a held scheduled send (it carries `hold`) back to its schedule, as the releasing person's. For owners, admins and the person whose grant scheduled it; not API keys. More than an hour past its time it needs a new `scheduled_at` (else 409 scheduled_at_required); a send that is not held gets 409 not_held.","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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"scheduled_at":{"type":"string","format":"date-time","description":"A new time for the send, in the future."}}}}}},"responses":{"200":{"description":"Released","content":{"application\/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#\/components\/schemas\/SentEmailDetail"},"meta":{"type":"object","properties":{"request_id":{"type":"string","description":"This release request's id, also sent as the X-Request-Id header. `data.request_id` stays the id of the request that created the send."}}}}}}},"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":"The request conflicts with the current state of the resource"}}}}},"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"}}}}}},"x-primitive-permission":["sent:manage"]}},"\/endpoints\/{id}\/signing-secret":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"get":{"operationId":"getEndpointSigningSecret","summary":"Get a webhook's own signing secret","description":"The secret deliveries to this webhook are signed with, when it signs with its own (signed_with is own_secret). Verify the Primitive-Signature header with it. A webhook a person owns is given its secret the first time it is asked for. A webhook that signs with the organization's secret answers 409 conflict: rotate its signing secret to give it its own. Answered to the person who owns the webhook; for a webhook an API key owns, to the key that acts for it and the person who holds that key; for one a third-party app or an agent delegate owns, to that app or delegate; and for a webhook the organization owns, to a caller who may change it and holds webhook_secret:read.","tags":["Endpoints"],"responses":{"200":{"description":"The signing secret","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/EndpointSigningSecretResponse"}}},"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":"The request conflicts with the current state of the resource"}}}}},"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":[]}],"x-primitive-permission":["endpoints:manage"]}},"\/endpoints\/{id}\/signing-secret\/rotate":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}],"post":{"operationId":"rotateEndpointSigningSecret","summary":"Rotate a webhook's signing secret, or give it its own","description":"Replaces the webhook's own signing secret, or gives a webhook that signs with the organization's secret one of its own, and returns it. Deliveries are signed with the new secret at once, so update the handler at the same time. Answered to the person who owns the webhook; for a webhook an API key owns, to the key that acts for it and the person who holds that key; for one a third-party app or an agent delegate owns, to that app or delegate; and for a webhook the organization owns, to a caller who may change it and holds webhook_secret:read.","tags":["Endpoints"],"responses":{"200":{"description":"The new signing secret","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/EndpointSigningSecretResponse"}}},"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"}}}}},"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":[]}],"x-primitive-permission":["endpoints:manage"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Not used to deduplicate the batch: the header is removed before each message is sent, so a retry of the batch with the same key is not deduplicated by it. Each message is handled as `POST \/send-mail` without an Idempotency-Key, so an identical message sent again soon after usually replays the first (counted in `replayed_count`); an `fyi` message is always sent again. A key that is empty, longer than 255 characters or not printable ASCII is refused with 400 `validation_error`; a valid one is echoed in the Idempotency-Key response header.","schema":{"type":"string","minLength":1,"maxLength":255}}],"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` collapsed onto an earlier reply to the same inbound email made in the last 5 minutes (at any time for a retry from the same Function answering the same inbound email) with the same credential, the same `from`, the same set of recipients (`to`, `cc`, `bcc`) and the same `in_reply_to`, even though its body differs. Any other difference is sent. Pass an `Idempotency-Key` to send a second reply. Absent or null on a request that made a send of its own."},"deduplicated":{"type":"boolean","const":true,"description":"Present and `true` when nothing was sent for this request because it collapsed onto an earlier send (`original_sent_email_id`). Absent on a request that made a send of its own."},"original_sent_email_id":{"type":"string","format":"uuid","description":"With `deduplicated: true`: the earlier send this request collapsed onto. Same value as `id`."}}}}}}}},"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`, `deduplicated: true`, `original_sent_email_id` 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"}}}}}},"x-primitive-permission":["mail:send","mail:send_batch"]}},"\/ask":{"servers":[{"url":"https:\/\/www.primitive.dev","description":"Web origin, the NLWeb endpoint host (not the v1 API base)."}],"get":{"operationId":"askGet","summary":"Ask about Primitive with an NLWeb query (no authentication)","description":"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 with an NLWeb query (no authentication)","description":"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, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","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)."},"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","dependencies"]},"example":{"status":"ok","env":"production","build_sha":"0c0f184","ts":"2026-06-10T10:00:00.000Z","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 does not perform the action a second time, so a network failure can be retried without duplicating side effects. A reply retried with the same key returns the original result; an interaction start or step retried with the same key returns the interaction as it stands now, which can differ from the first answer if it has moved on since.","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"}},"IdempotencyKeyNotReplayed":{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.","schema":{"type":"string","minLength":1,"maxLength":255}}},"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":"The request conflicts with the current state of the resource"}}}}},"UnprocessableEntity":{"description":"The request is well-formed but cannot be processed. `error.code` says why.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"},"example":{"success":false,"error":{"code":"validation_error","message":"Request cannot be processed"}}}}},"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"}}}}},"AttachmentNameAmbiguous":{"description":"attachment_name_ambiguous: more than one attachment has this filename, or a digits-only path value matches no part_index but is the name of an attachment; error.details.part_indexes lists every match, so fetch by part_index or by ?filename=. Or attachment_changed: attachment metadata changed during the read.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"ArchiveLimitExceeded":{"description":"archive_limit_exceeded, or attachment_limit_exceeded for the attachment itself. Limits: at most 1000 entries of any kind (files, directories and links all count); each file at most 31457280 bytes decoded; all files together (for tar.gz, the whole decoded tar stream) at most 33554432 bytes; a compression ratio above 100:1 is refused once decoded output passes 1048576 bytes; member paths at most 1024 UTF-8 bytes. Any limit exceeded is 413 archive_limit_exceeded.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"ArchiveUnprocessable":{"description":"attachment_not_archive, archive_unsafe_path, archive_invalid or archive_unsupported. The format is detected from the content, not the filename: zip, tar, or tar inside gzip. An archive with an absolute, drive-letter, backslash, control-character, Unicode format or separator character (bidirectional controls included), empty, \".\" or \"..\" path segment is refused whole (422 archive_unsafe_path) rather than listed without the entry. Any symbolic or hard link is refused the same way (422 archive_unsafe_path), wherever it points. Paths that differ only by letter case or Unicode normalization count as duplicates. Duplicate paths, overlapping zip data, a zip local header whose name or method disagrees with the central directory, and truncated or corrupt structures are 422 archive_invalid; encryption, zip64, multi-volume archives, compression other than stored or deflate, sparse tar files, and device or other special files are 422 archive_unsupported. Directories are validated and counted but neither listed nor served.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"PullUnavailableOrBusy":{"description":"Local receiving is unavailable (pull_unavailable), or the subscription was too busy to claim from in time (service_unavailable): nothing was claimed, so retry the pull after Retry-After","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"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)."},"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","dependencies"]},"example":{"status":"degraded","env":"production","build_sha":"0c0f184","ts":"2026-06-10T10:00:00.000Z","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\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*[\\x00-\\x7f]*[\\x09-\\x0d\\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*$","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\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*[\\x00-\\x7f]*[\\x09-\\x0d\\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*$","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"]},"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","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","ownership_proof_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","access_mode_unavailable","agent_account_managed_domain_only","agent_changed","agent_limit","agent_misconfigured","agent_work_agreement_replay_conflict","agent_work_deadline_not_future","agent_work_inactive","agent_work_not_found","agent_work_replay_conflict","agent_work_version_conflict","already_claimed","already_member","always_sent","archive_busy","archive_invalid","archive_limit_exceeded","archive_unsafe_path","archive_unsupported","attachment_name_ambiguous","attachment_not_archive","authorization_unavailable","bad_request","body_not_supported","build_failed","builder_unavailable","builtin_role","chunk_size_mismatch","chunk_too_large","chunks_missing","claim_failed","claim_not_pending","cleanup_incomplete","cli_access_refused","code_expired","confirmation_required","connection_already_claimed","connection_cleanup_pending","connection_create_request_conflict","connection_owner_address_required","connection_removed","connection_revoked","contact_request_invalid","contact_request_not_ready","contact_request_unauthenticated","contact_request_unavailable","cursor_expired","delete_failed","delivery_held","delivery_unavailable","deploy_failed","deploy_outcome_unknown","direct_delivery_failed","direct_delivery_unavailable","directory_limit","dns_publication_failed","dns_publication_pending","dns_verification_failed","download_token_expired","download_token_invalid","email_admission_unavailable","email_in_use","email_rule_author_only","email_rule_author_unresolvable","email_rule_compiler_unavailable","email_rule_conflict","email_rule_look_alikes_not_written","email_rule_not_compiled","email_rule_request_limit","email_rule_request_not_pending","email_rule_request_not_withdrawn","email_rule_request_party_left","email_rule_request_proposer_only","email_rule_request_target_only","email_rule_scope_locked","email_rule_scope_not_readable","email_rule_share_ended","email_rule_share_limit","embedding_provider_unavailable","entitlement","expectations_failed","expectations_undecided","expectations_unreadable","expired","feature_not_enabled","fresh_send_not_authorized","function_cannot_receive_mail","function_delivery_refused","function_effect_email_mismatch","function_effect_mismatch","function_forked","function_github_linked","function_key_missing","function_key_runtime_only","function_not_deployed","function_scope_forbidden","function_signing_secret_missing","function_suspended","github_binding_owner_changed","github_binding_paused","grant_forbidden","grant_not_available","hash_mismatch","hipaa_mode","hipaa_mode_check_unavailable","hipaa_mode_external_send_blocked","host_key_limit","idempotency_conflict","idempotency_key_invalid","invalid_code","invalid_cursor","invalid_email","invalid_manifest","invalid_payload_reference","invalid_request","invalid_signature","invalid_step","invalid_token","invalid_topic_sources","invitation_accepted","invitation_accepting","invitation_address_not_allowed","invitation_changed","invitation_pending","invitation_sending","invitation_unconfirmed","invitations_pending","ip_not_allowed","keep_refused","key_change_superseded","key_recently_created","last_owner","latest_message_mismatch","latest_message_thread_unresolved","load_test_recipient_not_allowed","mail_import_cap_exceeded","mail_import_content_mismatch","mail_import_deadline","mail_import_disabled","mail_import_fenced","mail_import_forbidden","mail_import_invalid_input","mail_import_invalid_provider_response","mail_import_out_of_window","mail_import_policy_mismatch","mail_import_provider_auth","mail_import_provider_unavailable","mail_import_storage_failed","mail_import_storage_limit","mail_import_storage_not_ready","mail_import_too_large","mail_import_unavailable","mail_relay_unavailable","mail_routing_conflict","mail_topic_edit_conflict","mail_topic_generation_conflict","mailbox_not_claimable","mailbox_not_found","mailbox_shared_with_others","managed_build_failed","managed_domain","member_address_domain_unavailable","member_address_history_confirmation_required","member_address_immutable","member_address_reserved","member_address_unavailable","member_changed","message_not_found","name_taken","network_member_excluded","network_member_not_found","network_peer_not_found","network_view_denied","no_code","no_endpoint","no_expectations","no_mail_read","not_a_template_install","not_available","not_claimable","not_held","not_pending","object_suspended","onboarding_key_in_use","outbound_edge_rejected","outbound_email_limit_exceeded","outbound_relay_timeout","outbound_relay_unavailable","outside_key_scope","path_invalid","payload_too_large","pending_owner_confirmation","plan_required","policy_forbidden","presence_admission_busy","presence_idempotency_conflict","presence_probe_stale","pro_plan_required","project_changed","pull_subscription_requires_agent_connection","query_invalid","recipient_changed","recipient_suppressed","redeploy_failed","removal_failed","removal_timeout","request_expired","required_by_organization","resend_limit_exceeded","resend_too_soon","role_assigned","role_assignment_unavailable","role_key_taken","rotate_instead","rotation_in_progress","rotation_incomplete","rotation_refused","rotation_unconfirmed","runtime_grant_unavailable","schedule_allowlist_not_provided","schedule_sender_collision","schedule_wake_disabled","scheduled_at_required","scheduled_payload_invalid","search_unavailable","semantic_search_not_enabled","send_failed","sender_not_authorized","sending_ip_warmup_cap","sent_email_tail_unavailable","sent_mail_search_unavailable","shopping_binding_conflict","shopping_decision_conflict","shopping_identity_conflict","shopping_not_found","shopping_owner_mail_required","shopping_package_not_found","shopping_proposal_expired","shopping_replay_conflict","shopping_source_unavailable","shopping_too_many_shipments","shopping_tracking_invalid","shopping_version_conflict","shopping_wake_unavailable","shredded","sign_in_email_change_required","signal_not_allowed","signal_parent_not_ready","signal_sender_unauthenticated","signing_secret_unavailable","signup_failed","standing_owner_revoked","step_up_invalid","step_up_not_accepted","step_up_refused","step_up_required","stop_changed","storage_error","storage_limit_exceeded","suspension_undecided","template_role","template_unavailable","template_update_failed","terms_not_accepted","thread_busy","too_many_attempts","too_many_concurrent_builds","too_many_resends","topic_edit_too_large","topic_generation_too_large","unavailable","unknown_chunk","update_needs_input","upstream_unavailable","usage_limit_exceeded"]},"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."},"feature":{"type":"string","description":"On a feature_disabled error: a stable id for the feature that is not turned on for the organization."},"feature_name":{"type":"string","description":"On a feature_disabled error: the name the message gives the feature."},"enabled_by":{"type":"string","description":"On a feature_disabled error: who turns the feature on. primitive: Primitive does, when asked at contact_email. Treat an unfamiliar value as one added after your client was built."},"contact_email":{"type":"string","format":"email","description":"On a feature_disabled error: the address to ask for the feature to be turned on."}}},"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"},"funding":{"type":"null","deprecated":true,"description":"Retired. Always null."}},"required":["api_key","org_id","address","plan","limits","upgrade","funding"]},"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","null"],"description":"The signup email of the person who opened the account. Null when your credential may not see it: it is shown to a caller that may read the organization's members' emails, and to that person signed in as themself. Also null when the account has none."},"plan":{"type":"string"},"limits":{"anyOf":[{"$ref":"#\/components\/schemas\/PlanLimits"},{"type":"null"}],"description":"Null when your credential does not hold org:read."},"entitlements":{"type":["array","null"],"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. Null when your credential does not hold org:read."},"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"]},"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","null"],"description":"The signup email of the person who opened the account. Null when your credential may not see it: it is shown to a caller that may read the organization's members' emails, and to that person signed in as themself. Also null when the account has none."},"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":"Emails still holding their stored content, counted up to 100,000. When `emails_count_capped` is true the organization holds more, and this is that floor."},"emails_count_capped":{"type":"boolean","description":"True when more than 100,000 stored emails exist and `emails_count` is a floor."}},"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":"Always null. Agent spending budgets were retired; the field is kept so existing clients keep working.","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 (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 rest of the balance 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\u2019s 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\u2019s 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."},"managed_zone":{"type":["string","null"],"description":"For a platform-managed domain, the zone Primitive operates on your behalf (for example `primitive.email`); you publish no DNS records for it. Null for a domain you own."}},"required":["id","org_id","domain","verified","is_active","created_at","dns_health"]},"DomainDnsRecord":{"type":"object","additionalProperties":false,"properties":{"type":{"type":"string","enum":["MX","TXT","CNAME"],"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"},"dkim":{"type":"object","description":"The DKIM key to publish for this domain, once it is ready. Absent while the key is still being prepared.","properties":{"selector":{"type":"string","description":"The DKIM selector."},"dnsRecordValue":{"type":"string","description":"The DKIM public key TXT record value."},"cnameNamespace":{"type":"string","pattern":"^[a-f0-9]{32}$","description":"Present when the key is published through delegated CNAME records rather than a TXT record at the selector."}},"required":["selector","dnsRecordValue"]}},"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":{"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\u2019s 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."},"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"verification_token":{"type":["string","null"]},"managed_zone":{"type":["string","null"],"description":"For a platform-managed domain, the zone Primitive operates on your behalf (for example `primitive.email`); you publish no DNS records for it. Null for a domain you own."},"is_active":{"type":"boolean"},"spam_threshold":{"type":["number","null"],"minimum":0,"maximum":15},"created_at":{"type":"string","format":"date-time"}},"required":["verified","dns_health","id","org_id","domain","is_active","created_at"],"description":"The verified domain. A check that does not pass is a 400 `dns_verification_failed` error instead."},"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","description":"Only this email's routed delivery (the webhook or Function routing sent it to), never a rule's. On mail routing sent nowhere it stays pending, 0 or null unless the email is sent later (POST \/emails\/{id}\/replay), and says nothing about rule deliveries: those are in `rule_deliveries` on GET \/emails\/{id}, for the mailbox's owner. Its delivery state. `pending` with 0 attempts can also mean no route webhook took it, and then it changes only if the email is sent again."},"webhook_attempt_count":{"type":"integer","description":"Only this email's routed delivery (the webhook or Function routing sent it to), never a rule's. On mail routing sent nowhere it stays pending, 0 or null unless the email is sent later (POST \/emails\/{id}\/replay), and says nothing about rule deliveries: those are in `rule_deliveries` on GET \/emails\/{id}, for the mailbox's owner. Its attempts."},"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-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":{"type":["string","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`, `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"]},"from_verified":{"type":["boolean","null"],"description":"True when the From header address, `from_header_email`, passed DMARC for its domain on arrival (aligned SPF or DKIM); decided once, at receipt. It is about the From header, never the envelope: `sender`, the `from` parameter and `from:` term, and the change feed's `address` match the envelope sender, which can differ. It proves the domain, not the mailbox. False with no From header address, when authentication failed or was not attempted (a delivery between Primitive mailboxes counts only when it passed DMARC too), and for mail stored before the verdict was recorded. Null on sent rows in search results. Not a spam or safety judgement."},"from_header_email":{"type":["string","null"],"description":"The From header address, lowercased, that `from_verified` is about; null when the message had none. Unlike `sender` it is never the envelope address."},"from_name":{"type":["string","null"],"description":"Display name from the `From:` header, decoded for display: RFC 2047 encoded words are decoded in their declared charset and folded lines are joined (e.g. `Dana R\u00faiz` for `=?UTF-8?Q?Dana_R=C3=BAiz?= <dana@example.com>`). Null when the header carries no display name. For display only: identify the sender by `from_email`, never by this name, which the sender chooses freely."},"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`. The display name in this raw value may be RFC 2047 encoded (`=?UTF-8?Q?...?=`); use `from_name` for the decoded name. Present on `GET \/emails` rows and on the messages `GET \/changes` inlines; search results do not carry it."},"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). Present on `GET \/emails` rows and on the messages `GET \/changes` inlines; search results do not carry it."},"to_email":{"type":"string","description":"Parsed to address (same as recipient). Present on `GET \/emails` rows and on the messages `GET \/changes` inlines; search results do not carry it."},"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"},"in_reply_to":{"type":["array","null"],"items":{"type":"string"},"description":"Message-IDs from the `In-Reply-To` header, as `parsed.in_reply_to` on GET \/emails\/{id}. Lets a client attach a reply to its conversation from the list alone, including while `thread_id` is still null because the message has not finished processing. Null when the header was absent, and wherever `snippet` is null because the content is withheld: content not yet available, a failed parse, mail pending sender authentication, or a sender-blocked rejection."},"references":{"type":["array","null"],"items":{"type":"string"},"maxItems":20,"description":"Message-IDs from the `References` header, oldest first, as `parsed.references` on GET \/emails\/{id}, shortened to at most 20 entries on the list: a longer chain keeps its first entry (the thread root) and the 19 most recent. Read GET \/emails\/{id} for the full chain. Null when the header was absent, and wherever `snippet` is null because the content is withheld: content not yet available, a failed parse, mail pending sender authentication, or a sender-blocked rejection."},"parse_status":{"type":["string","null"],"enum":["pending","processing","complete","failed",null],"description":"Parsing state on inbox, history, arrival and detail reads. Failed is terminal and differs from valid empty content (complete). Null covers legacy rows. Optional on shared summaries for compatibility with older payloads and other summary endpoints."},"delivery_report":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["permanent","transient","undetermined"]},"sent_email_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"}]},"failed_recipients":{"type":"array","items":{"type":"string"}},"status_code":{"type":["string","null"]},"diagnostic":{"type":["string","null"]}},"required":["type","sent_email_id","failed_recipients","status_code","diagnostic"],"additionalProperties":false},{"type":"null"}],"description":"Classified inbound delivery notification, or null. The linked sent_email_id is resolved within the receiving organization; null means uncorrelated. This is a reported outcome, not a rewrite of the original send status. Transient reports may still be retried. Optional for older payloads and other summary endpoints."},"body_text":{"type":["string","null"],"description":"With `include=body_text`: the plain-text body, cut as `truncated_fields` says."},"body_html":{"type":["string","null"],"description":"With `include=body_html`: the HTML body, cut as `truncated_fields` says."},"headers":{"anyOf":[{"$ref":"#\/components\/schemas\/IncludedMessageHeaders"},{"type":"null"}],"description":"With `include=headers`: stored addressing and threading headers."},"attachments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"With `include=attachments`: attachment metadata as the detail read reports it. Never the bytes."},"truncated":{"type":"boolean","description":"With `include`: true when a body on this row was cut or left out."},"truncated_fields":{"type":"array","items":{"type":"string","enum":["body_text","body_html"]},"description":"With `include`: the body fields on this row that were cut or left out."},"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."},"copies":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"recipient":{"type":"string"}},"required":["id","recipient"]},"description":"With `group=message`: every inbound copy of this message you can read, in arrival order, this row included. At most 50; past that, the ones listed are your own mailboxes' copies first, then the earliest."},"copies_truncated":{"type":"boolean","enum":[true],"description":"With `group=message`: present when `copies` may not list every copy you can read."},"sent_email_id":{"type":"string","format":"uuid","description":"With `group=message`: the send of this message, when you can read it (under a connected agent key, only its own sends)."},"imported":{"type":["object","null"],"description":"Historical import; never sender or SMTP delivery proof. EML uploads are unverified even when downloaded from a provider. Null for live mail. Excluded from live tails and deltas.","additionalProperties":false,"required":["from","method","verification","at"],"properties":{"from":{"type":"string","enum":["google_workspace","unknown"],"description":"Original mail provider. Unknown when Primitive has not established the provider of an uploaded file."},"method":{"type":"string","enum":["primitive_direct","api_upload"],"description":"How Primitive acquired the historical message."},"verification":{"type":"string","enum":["provider_fetched","unverified"]},"at":{"type":"string","format":"date-time","description":"When Primitive acquired this historical message."}},"oneOf":[{"type":"null"},{"type":"object","required":["from","method","verification","at"],"properties":{"from":{"const":"google_workspace"},"method":{"const":"primitive_direct"},"verification":{"const":"provider_fetched"},"at":{"type":"string","format":"date-time"}}},{"type":"object","required":["from","method","verification","at"],"properties":{"from":{"const":"unknown"},"method":{"const":"api_upload"},"verification":{"const":"unverified"},"at":{"type":"string","format":"date-time"}}}]},"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","from_verified","from_header_email","imported","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":{"description":"May be null: this field carries message content, which is withheld when the content is not available to your credential.","anyOf":[{"$ref":"#\/components\/schemas\/EmailSearchHighlights"},{"type":"null"}]},"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. Only `direction=outbound` or `direction=any` returns `outbound` results. Treat an unfamiliar value as one added after your client was built. An `outbound` result also carries `delivery_status`."},"delivery_status":{"$ref":"#\/components\/schemas\/SentEmailStatus"}},"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 search matched more than 1,000 emails, so `facets` aggregate only the most recent 1,000 matches, 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","description":"Only this email's routed delivery (the webhook or Function routing sent it to), never a rule's. On mail routing sent nowhere it stays pending, 0 or null unless the email is sent later (POST \/emails\/{id}\/replay), and says nothing about rule deliveries: those are in `rule_deliveries` on GET \/emails\/{id}, for the mailbox's owner. Its delivery state. `pending` with 0 attempts can also mean no route webhook took it, and then it changes only if the email is sent again."},"webhook_attempt_count":{"type":"integer","description":"Only this email's routed delivery (the webhook or Function routing sent it to), never a rule's. On mail routing sent nowhere it stays pending, 0 or null unless the email is sent later (POST \/emails\/{id}\/replay), and says nothing about rule deliveries: those are in `rule_deliveries` on GET \/emails\/{id}, for the mailbox's owner. Its attempts."},"webhook_last_attempt_at":{"type":["string","null"],"format":"date-time","description":"Only this email's routed delivery (the webhook or Function routing sent it to), never a rule's. On mail routing sent nowhere it stays pending, 0 or null unless the email is sent later (POST \/emails\/{id}\/replay), and says nothing about rule deliveries: those are in `rule_deliveries` on GET \/emails\/{id}, for the mailbox's owner. Its last attempt."},"webhook_last_status_code":{"type":["integer","null"],"description":"Only this email's routed delivery (the webhook or Function routing sent it to), never a rule's. On mail routing sent nowhere it stays pending, 0 or null unless the email is sent later (POST \/emails\/{id}\/replay), and says nothing about rule deliveries: those are in `rule_deliveries` on GET \/emails\/{id}, for the mailbox's owner. What it last answered."},"webhook_last_error":{"type":["string","null"],"description":"Only this email's routed delivery (the webhook or Function routing sent it to), never a rule's. On mail routing sent nowhere it stays pending, 0 or null unless the email is sent later (POST \/emails\/{id}\/replay), and says nothing about rule deliveries: those are in `rule_deliveries` on GET \/emails\/{id}, for the mailbox's owner. Its last error."},"webhook_fired_at":{"type":["string","null"],"format":"date-time","description":"Only this email's routed delivery (the webhook or Function routing sent it to), never a rule's. On mail routing sent nowhere it stays pending, 0 or null unless the email is sent later (POST \/emails\/{id}\/replay), and says nothing about rule deliveries: those are in `rule_deliveries` on GET \/emails\/{id}, for the mailbox's owner. When it was delivered."},"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`. The display name in this raw value may be RFC 2047 encoded (`=?UTF-8?Q?...?=`); use `from_name` for the decoded name."},"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":{"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. May be null: this field carries message content, which is withheld when the content is not available to your credential.","anyOf":[{"allOf":[{"$ref":"#\/components\/schemas\/ParsedEmailData"}]},{"type":"null"}]},"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-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":{"type":["string","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`, `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"]},"from_verified":{"type":"boolean","description":"Whether the From header domain was authenticated when the message arrived. See EmailSummary.from_verified."},"from_header_email":{"type":["string","null"],"description":"The From header address, lowercased, that `from_verified` is about; null when the message had none. Unlike `sender` it is never the envelope address."},"understanding":{"type":["object","null"],"description":"Classification facts for this email. Present only for an org with email rules, where the body is shown. May be null: this field carries message content, which is withheld when the content is not available to your credential.","required":["version","status"],"properties":{"version":{"type":"integer","description":"The block's shape version."},"status":{"type":"string","description":"`ready`, `pending` (not classified yet) or `unavailable` (will not be classified). Treat an unfamiliar value as added after your client was built."},"classification_id":{"type":"string","format":"uuid","description":"The classification, when `ready`."},"vocabulary":{"type":"string","description":"The fact vocabulary, when `ready`."},"classified_at":{"type":"string","format":"date-time","description":"When it was classified, when `ready`."},"facts":{"type":"object","description":"Facts by name, when `ready`. A fact the classification did not produce is absent.","additionalProperties":{"type":"object","required":["value","confidence"],"properties":{"value":{"description":"The fact's value.","oneOf":[{"type":"boolean"},{"type":"number"},{"type":"string"},{"type":"array","items":{"type":"string"}}]},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"From 0 to 1."}}}}}},"shared_through":{"type":["object","null"],"description":"Only for a reader who has this email through a colleague's share of their mail, when that share is their only way to it: the share and who gave it. Null for everyone else, including on your own mail when a rule or share of yours gave it to someone: rule_deliveries counts the webhooks rules delivered it to, and GET \/v1\/email-rule-shares\/{id}\/emails lists what a share you gave has given.","required":["share_id","rule_name","sharer"],"properties":{"share_id":{"type":"string","format":"uuid","description":"The share."},"rule_name":{"type":"string","description":"The name of the rule that was shared."},"sharer":{"type":"object","description":"Who shared it.","required":["name","email"],"properties":{"name":{"type":["string","null"]},"email":{"type":["string","null"]}}}}},"rule_deliveries":{"type":["object","null"],"description":"Where Email Rules delivered this email by webhook, for the owner of the mailbox it is at, on their own dashboard session or the Primitive CLI. Null for everyone else, including on another member's mail even for an organization owner or admin (GET \/v1\/webhooks\/deliveries?email_id=<email-id> lists its deliveries, a rule's included), and when it could not be read in time. A key reading it through a rule is not a delivery.","required":["rules","hidden","complete"],"properties":{"rules":{"type":"array","description":"Each rule over your mail that queued a webhook delivery of this email, newest rule first.","items":{"type":"object","required":["rule_id","name","delivered","pending","failed","not_sent","hosts","hosts_not_listed"],"properties":{"rule_id":{"type":"string","format":"uuid","description":"The rule. A webhook sent it through several rules counts under one."},"name":{"type":"string"},"delivered":{"type":"integer","minimum":0,"description":"Webhooks it was delivered to."},"pending":{"type":"integer","minimum":0,"description":"Webhooks still owed it."},"failed":{"type":"integer","minimum":0,"description":"Webhooks whose attempts gave up."},"not_sent":{"type":"integer","minimum":0,"description":"Webhooks it was not sent to after all."},"hosts":{"type":"array","items":{"type":"string"},"description":"The sites it was delivered to at the time, named as the covering view names sites; they may differ from the rule's current receivers there."},"hosts_not_listed":{"type":"integer","minimum":0,"description":"Sites past the listed ones."}}}},"hidden":{"type":"integer","minimum":0,"description":"Rules that queued a delivery of it that are not listed: they do not cover your mailboxes now, or are past the 50 one answer judges."},"complete":{"type":"boolean","description":"False when a delivery may be missing: the email is older than 14 days, or it has more deliveries or rules than one answer reads."}}},"from_name":{"type":["string","null"],"description":"Display name from the `From:` header, decoded for display: RFC 2047 encoded words are decoded in their declared charset and folded lines are joined (e.g. `Dana R\u00faiz` for `=?UTF-8?Q?Dana_R=C3=BAiz?= <dana@example.com>`). Null when the header carries no display name. For display only: identify the sender by `from_email`, never by this name, which the sender chooses freely."},"parse_status":{"type":["string","null"],"enum":["pending","processing","complete","failed",null],"description":"Parsing state on inbox, history, arrival and detail reads. Failed is terminal and differs from valid empty content (complete). Null covers legacy rows. Optional on shared summaries for compatibility with older payloads and other summary endpoints."},"delivery_report":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["permanent","transient","undetermined"]},"sent_email_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"}]},"failed_recipients":{"type":"array","items":{"type":"string"}},"status_code":{"type":["string","null"]},"diagnostic":{"type":["string","null"]}},"required":["type","sent_email_id","failed_recipients","status_code","diagnostic"],"additionalProperties":false},{"type":"null"}],"description":"Classified inbound delivery notification, or null. The linked sent_email_id is resolved within the receiving organization; null means uncorrelated. This is a reported outcome, not a rewrite of the original send status. Transient reports may still be retried. Optional for older payloads and other summary endpoints."},"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 messages in this thread you can read that arrived after this one, each counted once however many copies of it you can read; a later copy of this email's own message is not newer. 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."}}}}},"attachments":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailAttachment"},"description":"Attachment metadata (file name, type, size), the same items in the same order as `parsed.attachments`. Kept for a credential that reads headers only, where `parsed` is null. Empty when the email has none, its content is withheld (read `status` and `rejection_reason`), or it is not parsed yet or its parse failed (read `parse_status`). No bytes; download a part with `\/emails\/{id}\/attachments\/{part_index}`."},"imported":{"type":["object","null"],"description":"Historical import; never sender or SMTP delivery proof. EML uploads are unverified even when downloaded from a provider. Null for live mail. Excluded from live tails and deltas.","additionalProperties":false,"required":["from","method","verification","at"],"properties":{"from":{"type":"string","enum":["google_workspace","unknown"],"description":"Original mail provider. Unknown when Primitive has not established the provider of an uploaded file."},"method":{"type":"string","enum":["primitive_direct","api_upload"],"description":"How Primitive acquired the historical message."},"verification":{"type":"string","enum":["provider_fetched","unverified"]},"at":{"type":"string","format":"date-time","description":"When Primitive acquired this historical message."}},"oneOf":[{"type":"null"},{"type":"object","required":["from","method","verification","at"],"properties":{"from":{"const":"google_workspace"},"method":{"const":"primitive_direct"},"verification":{"const":"provider_fetched"},"at":{"type":"string","format":"date-time"}}},{"type":"object","required":["from","method","verification","at"],"properties":{"from":{"const":"unknown"},"method":{"const":"api_upload"},"verification":{"const":"unverified"},"at":{"type":"string","format":"date-time"}}}]},"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","from_verified","from_header_email","from_name","attachments","imported","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."},"tar_path":{"type":"string","description":"This part's file name inside the message's attachments archive. Absent on attachments stored before it was recorded."}},"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. Any field may be absent or null on mail stored before that field was recorded.","properties":{"domain":{"type":["string","null"]},"selector":{"type":["string","null"]},"result":{"type":["string","null"],"description":"Verification result (e.g. `pass`, `fail`, `none`)."},"aligned":{"type":["boolean","null"],"description":"Whether the signing domain aligns with the From domain (for DMARC)."},"keyBits":{"type":["integer","null"]},"algo":{"type":["string","null"]}}},"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":"Messages in the thread you can read, each counted once however many mailboxes it reached. Exact unless the thread has more than 200 visible messages in one direction; then the value is greater than the messages the read returns, so `message_count > messages.length` marks an incomplete read. Past that point it is not a count: it can be the thread's stored total, which counts each mailbox copy, or only a marker that more exist."},"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 messages you can read in this thread arrived after that message, each counted once however many copies of it you can read. 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-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":{"type":["string","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"},"copies":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"recipient":{"type":"string"}},"required":["id","recipient"]},"description":"Every inbound copy of this message you can read (a message to several of the org's mailboxes is stored once per mailbox), in arrival order. At most 50; past that, the ones listed are your own mailboxes' copies first, then the earliest. The entry's `id` is one of them unless the entry is the send. A later read can return the message again with more copies listed, or under another id when a better copy becomes visible: upsert entries by `id`, and drop any entry you hold whose id is in a newer entry's `copies` or `sent_email_id`."},"copies_truncated":{"type":"boolean","enum":[true],"description":"Present when `copies` may not list every copy you can read."},"sent_email_id":{"type":"string","format":"uuid","description":"The org's send of this message, when the entry is an inbound copy and you can read the send. The entry's `status` stays the inbound copy's."}},"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":"Messages in the thread you can read, each counted once however many mailboxes it reached. Exact unless the thread has more than 200 visible messages in one direction; then `truncated` is true and the value is greater than the messages a full read returns, so on a full read `message_count > messages.length` marks an incomplete read. Past that point it is not a count: it can be the thread's stored total, which counts each mailbox copy, or only a marker that more exist. Full, `since` and window reads report the same value."},"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"},"latest_message_id":{"type":["string","null"],"format":"uuid","description":"The id to pass as if_latest_message_id when replying in this conversation: the message that joined the thread last among those this credential can read, by the rule a conditional send checks. It can differ from the last message in `messages`, which is ordered by timestamp, when mail joined the thread after a newer-timestamped message. Present on a full read and a window read of a threaded conversation; null when no message in the thread is visible; absent on a `since` delta."},"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","null"],"description":"Plain-text body. Empty string when the message has no text\npart or its content was discarded by retention. May be null: this field carries message content, which is withheld when the content is not available to your credential."},"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-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":{"type":["string","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}`."},"imported":{"type":["object","null"],"description":"Historical import; never sender or SMTP delivery proof. EML uploads are unverified even when downloaded from a provider. Null for live mail. Excluded from live tails and deltas.","additionalProperties":false,"required":["from","method","verification","at"],"properties":{"from":{"type":"string","enum":["google_workspace","unknown"],"description":"Original mail provider. Unknown when Primitive has not established the provider of an uploaded file."},"method":{"type":"string","enum":["primitive_direct","api_upload"],"description":"How Primitive acquired the historical message."},"verification":{"type":"string","enum":["provider_fetched","unverified"]},"at":{"type":"string","format":"date-time","description":"When Primitive acquired this historical message."}},"oneOf":[{"type":"null"},{"type":"object","required":["from","method","verification","at"],"properties":{"from":{"const":"google_workspace"},"method":{"const":"primitive_direct"},"verification":{"const":"provider_fetched"},"at":{"type":"string","format":"date-time"}}},{"type":"object","required":["from","method","verification","at"],"properties":{"from":{"const":"unknown"},"method":{"const":"api_upload"},"verification":{"const":"unverified"},"at":{"type":"string","format":"date-time"}}}]},"copies":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"recipient":{"type":"string"}},"required":["id","recipient"]},"description":"Every inbound copy of this message you can read (a message to several of the org's mailboxes is stored once per mailbox), in arrival order. At most 50; past that, the ones listed are your own mailboxes' copies first, then the earliest. The entry's `id` is one of them unless the entry is the send. A later read can return the message again with more copies listed, or under another id when a better copy becomes visible: upsert entries by `id`, and drop any entry you hold whose id is in a newer entry's `copies` or `sent_email_id`."},"copies_truncated":{"type":"boolean","enum":[true],"description":"Present when `copies` may not list every copy you can read."},"sent_email_id":{"type":"string","format":"uuid","description":"The org's send of this message, when the entry is an inbound copy and you can read the send. On a `since` read, when you read the send as its sender, the entry's `status` and `hold` describe the send."},"hold":{"type":"object","description":"Only on a scheduled send that is not going out yet; status stays scheduled. credential_reuse_hold: its OAuth grant was revoked after refresh token reuse, so it waits until released (POST \/sent-emails\/{id}\/release) or canceled; canceled after 7 days. sending_ip_warmup_cap: your dedicated sending address is at today's warm-up limit or paused, so it goes out on its own within the hour after the limit resets at 00:00 UTC (fails with sending_ip_warmup_cap after 7 such holds); no release needed. On sent-email reads and conversation messages.","required":["reason","since"],"properties":{"reason":{"type":"string","description":"Open: credential_reuse_hold, sending_ip_warmup_cap."},"since":{"type":["string","null"],"format":"date-time"}}}},"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."},"content_id":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[\\x21-\\x3B\\x3D\\x3F-\\x7E]+$","description":"Content-ID for an inline attachment, without angle brackets. When set the part is sent inline so an HTML body can reference it as `cid:<content_id>`. Printable ASCII with no whitespace and no `<` or `>`."}},"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"},"if_latest_message_id":{"type":"string","format":"uuid","description":"Send only if this message id is still the newest message in the thread this send joins, as this credential can see it. Newest means the message that joined the thread last, in the order messages were attached to it, not by timestamp: a message accepted earlier but attached later is newer. Messages stored before join order was recorded count as older than every message that has one, and among themselves sort by timestamp (received_at for mail received, created_at for mail sent), ties going to the larger id. Pass latest_message_id from GET \/emails\/{id}\/conversation: it is read by the same rule, whereas the last message listed there is ordered by timestamp and can differ when mail joined the thread late. An organization credential sees every message in the thread; a mailbox-scoped agent connection key sees only mail received at its address and mail sent from it. Mail awaiting sender authentication never counts. Otherwise nothing is sent and the response is 409 latest_message_mismatch with error.details.latest_message_id, latest_message_at and thread_id. If in_reply_to resolves to no single thread this credential can check, the response is 422 latest_message_thread_unresolved; that does not change on retry. If the thread stays locked by another writer for longer than the send waits, the response is 503 thread_busy with Retry-After; nothing was sent and the same request can be retried. Checked inside the send transaction under a lock on the thread, so two sends conditioned on the same id cannot both go out. The value is part of the request a replay is matched on: a replay of an already accepted request with the same idempotency key, or the identical request within the automatic deduplication window, returns the original result without checking again, and a request that differs only in if_latest_message_id is not a replay. On \/send-mail it requires in_reply_to; it cannot be combined with scheduled_at or repeat, because it is checked when the request is accepted."},"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`."},"reply_to":{"description":"Reply-To addresses for the message. Either a single address or an array of at most 10 addresses; each entry must be a single address and may carry a display name. Reply-To is a header, not a recipient, so it does not count toward the recipient limit.","oneOf":[{"type":"string","minLength":3,"maxLength":998},{"type":"array","minItems":1,"maxItems":10,"items":{"type":"string","minLength":3,"maxLength":998}}]},"tags":{"type":"array","maxItems":10,"description":"Metadata tags stored with the send for correlation and filtering, at most 10, with unique names. Tags never affect delivery and are not added to the message.","items":{"type":"object","additionalProperties":false,"required":["name","value"],"properties":{"name":{"type":"string","maxLength":64,"pattern":"^[A-Za-z0-9_-]+$","description":"ASCII letters, digits, underscores or dashes."},"value":{"type":"string","maxLength":256,"pattern":"^[A-Za-z0-9_-]+$","description":"ASCII letters, digits, underscores or dashes."}}}},"unsubscribe":{"type":"object","additionalProperties":false,"required":["url"],"description":"One-click unsubscribe for the message. Primitive writes a `List-Unsubscribe` header naming `url` (and `mailto` when given) and, unless `one_click` is false, `List-Unsubscribe-Post: List-Unsubscribe=One-Click` (RFC 8058), and DKIM-signs both. A receiver that offers one-click unsubscribe sends an HTTPS POST to `url` with the form body `List-Unsubscribe=One-Click`; your endpoint records the unsubscribe. Every recipient of a message would get the same `url`, so another recipient could unsubscribe whoever it identifies: a send with `unsubscribe` must have exactly one recipient, one entry across `to`, `cc` and `bcc` (an address given twice counts twice), and a send to more is refused with a `validation_error`. Send one message per recipient, for example with `POST \/v1\/send-mail\/batch`. The `headers` map refuses both header names, so this field is the only way to set them, and the headers it adds count toward the 25 entries `headers` may hold. A field whose rendered `List-Unsubscribe` would pass 980 characters is refused. Not accepted on a repeating send.","properties":{"url":{"type":"string","minLength":1,"maxLength":700,"pattern":"^[Hh][Tt][Tt][Pp][Ss]:\/\/(?:[A-Za-z0-9\\-._~:\/?#\\[\\]@!$&'()*+;=]|%[0-9A-Fa-f]{2})+$","description":"Absolute https URL that unsubscribes the recipient, at most 700 characters of URL characters only: printable ASCII with no spaces, double quotes, angle brackets or commas (percent-encode a comma as %2C), `%` only as a percent-encoded byte, `[` and `]` only around an IPv6 address host, a port from 1 to 65535, no `=?` (which mail readers can decode as an encoded word), and no user name or password. The host must be public: not a single-label name, `localhost` or a name under `.localhost`, `.internal`, `.local`, `.home.arpa` or `.localdomain`, or a loopback, link-local, private, shared, reserved, multicast or unspecified IP address, including an IPv6 address that embeds one. The scheme is written in lower case."},"one_click":{"type":"boolean","default":true,"description":"Whether to also write `List-Unsubscribe-Post: List-Unsubscribe=One-Click`, which tells the receiver it may unsubscribe with a single POST to `url`. Set false when `url` needs the recipient to open a page and confirm."},"mailto":{"type":"string","minLength":3,"maxLength":254,"pattern":"^[A-Za-z0-9!$'*+=_~-]+(?:\\.[A-Za-z0-9!$'*+=_~-]+)*@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$","description":"An address to also offer as a `mailto:` unsubscribe, written after `url`. The address alone, without the `mailto:` prefix: at most 64 letters, digits and `.!$'*+=_~-` before the `@`, and a dotted domain after it. An `=` is written as `%3D` in the header."}}},"headers":{"type":"object","maxProperties":25,"propertyNames":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9]+(-[A-Za-z0-9]+)*$"},"additionalProperties":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[\\x20-\\x7E]+$"},"description":"Extra headers, as `{ \"Name\": \"value\" }`, at most 25. A name is one of `Auto-Submitted`, `Precedence`, `X-Auto-Response-Suppress`, `X-Primitive-Interaction`, `X-Primitive-Interaction-Ref`, `X-Primitive-Function-Hop`, or a custom `X-` name of letters, digits and hyphens that is not credential-like (a word such as `auth`, `token`, `key`, `secret`, `session` or `bearer`, joined forms included) and not in a reserved family: X-Primitive*, X-Original-*, X-Originating-*, X-Forwarded-*, X-Received*, X-Authenticated*, X-Envelope-*, X-Delivered-To, X-Apparently-To, X-Failed-Recipients, X-Loop, X-Priority, X-MSMail-*, X-MS-*, X-Microsoft-*, X-Forefront-*, X-Exchange-*, X-Google-*, X-GM-*, X-Apple-*, X-Yahoo-*, X-YMail-*, X-AOL-*, X-Spam*, X-Virus-*, X-Proofpoint-*, X-Barracuda-*, X-Rspamd-*, X-Mimecast-*, X-IronPort-*, X-Sophos-*, X-Report-Abuse*, X-CSA-*. Names compare case-insensitively (`_` and `.` count as `-` for families); a repeated or any other name is a `validation_error`. Values: 1 to 200 printable ASCII characters. Custom headers are sent as spelled, are not DKIM-signed, and can be searched with `header` on `GET \/sent-emails`.","example":{"X-Campaign-Id":"spring-sale","Auto-Submitted":"auto-generated"}}},"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","null"],"description":"Null once the content of a send refused by a gate has been discarded (`content_discarded_at` set)."},"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 the relay'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-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":{"type":["string","null"]}},"required":["address","user_id","name"],"additionalProperties":false},{"type":"null"}]},"delivery_recipients":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","anyOf":[{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"result":{"type":"string","enum":["delivered","bounced","deferred"]},"smtp_response_text":{"type":["string","null"]},"smtp_enhanced_status_code":{"type":["string","null"]}},"required":["address","result","smtp_response_text","smtp_enhanced_status_code"],"additionalProperties":false}},{"type":"null"}],"description":"Per-recipient SMTP outcomes, including partial failures even when the aggregate send status is delivered. Sender-only; empty when withheld from another recipient or when legacy metadata has no outcomes. Null means stored delivery metadata is unreadable. Optional on older payloads."},"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"},"headers":{"anyOf":[{"$ref":"#\/components\/schemas\/IncludedMessageHeaders"},{"type":"null"}],"description":"With `include=headers`: stored addressing and threading headers."},"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":{"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"],"additionalProperties":{}}},"truncated":{"type":"boolean","description":"With `include`: true when a body on this row was cut or left out."},"truncated_fields":{"type":"array","items":{"type":"string","enum":["body_text","body_html"]},"description":"With `include`: the body fields on this row that were cut or left out."},"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`, `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."},"hold":{"type":"object","description":"Only on a scheduled send that is not going out yet; status stays scheduled. credential_reuse_hold: its OAuth grant was revoked after refresh token reuse, so it waits until released (POST \/sent-emails\/{id}\/release) or canceled; canceled after 7 days. sending_ip_warmup_cap: your dedicated sending address is at today's warm-up limit or paused, so it goes out on its own within the hour after the limit resets at 00:00 UTC (fails with sending_ip_warmup_cap after 7 such holds); no release needed. On sent-email reads and conversation messages.","required":["reason","since"],"properties":{"reason":{"type":"string","description":"Open: credential_reuse_hold, sending_ip_warmup_cap."},"since":{"type":["string","null"],"format":"date-time"}}}},"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":{"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"],"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-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":{"type":["string","null"]}},"required":["address","user_id","name"],"additionalProperties":false},{"type":"null"}]},"hold":{"type":"object","description":"Only on a scheduled send that is not going out yet; status stays scheduled. credential_reuse_hold: its OAuth grant was revoked after refresh token reuse, so it waits until released (POST \/sent-emails\/{id}\/release) or canceled; canceled after 7 days. sending_ip_warmup_cap: your dedicated sending address is at today's warm-up limit or paused, so it goes out on its own within the hour after the limit resets at 00:00 UTC (fails with sending_ip_warmup_cap after 7 such holds); no release needed. On sent-email reads and conversation messages.","required":["reason","since"],"properties":{"reason":{"type":"string","description":"Open: credential_reuse_hold, sending_ip_warmup_cap."},"since":{"type":["string","null"],"format":"date-time"}}}}},"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\u2019s From address is one of the send\u2019s `to`, `cc` or `bcc` recipients and either it passed DMARC, its domain is the DMARC domain or a subdomain of it, and the whole message body is signed by a passing DKIM signature aligned with that domain, or it is a verified direct delivery from Primitive\u2019s own outbound. A DMARC pass that rests on SPF alone, or on a DKIM signature that covers only part of the body, is not verified. 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\u2019s From address is one of the send\u2019s `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"},"if_latest_message_id":{"type":"string","format":"uuid","description":"Send only if this message id is still the newest message in the thread this send joins, as this credential can see it. Newest means the message that joined the thread last, in the order messages were attached to it, not by timestamp: a message accepted earlier but attached later is newer. Messages stored before join order was recorded count as older than every message that has one, and among themselves sort by timestamp (received_at for mail received, created_at for mail sent), ties going to the larger id. Pass latest_message_id from GET \/emails\/{id}\/conversation: it is read by the same rule, whereas the last message listed there is ordered by timestamp and can differ when mail joined the thread late. An organization credential sees every message in the thread; a mailbox-scoped agent connection key sees only mail received at its address and mail sent from it. Mail awaiting sender authentication never counts. Otherwise nothing is sent and the response is 409 latest_message_mismatch with error.details.latest_message_id, latest_message_at and thread_id. If in_reply_to resolves to no single thread this credential can check, the response is 422 latest_message_thread_unresolved; that does not change on retry. If the thread stays locked by another writer for longer than the send waits, the response is 503 thread_busy with Retry-After; nothing was sent and the same request can be retried. Checked inside the send transaction under a lock on the thread, so two sends conditioned on the same id cannot both go out. The value is part of the request a replay is matched on: a replay of an already accepted request with the same idempotency key, or the identical request within the automatic deduplication window, returns the original result without checking again, and a request that differs only in if_latest_message_id is not a replay. On \/send-mail it requires in_reply_to; it cannot be combined with scheduled_at or repeat, because it is checked when the request is accepted."},"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`."},"unsubscribe":{"type":"object","additionalProperties":false,"required":["url"],"description":"One-click unsubscribe for the message. Primitive writes a `List-Unsubscribe` header naming `url` (and `mailto` when given) and, unless `one_click` is false, `List-Unsubscribe-Post: List-Unsubscribe=One-Click` (RFC 8058), and DKIM-signs both. A receiver that offers one-click unsubscribe sends an HTTPS POST to `url` with the form body `List-Unsubscribe=One-Click`; your endpoint records the unsubscribe. Every recipient of a message would get the same `url`, so another recipient could unsubscribe whoever it identifies: a reply with `unsubscribe` must go to the one address it answers, and one with `reply_all` that adds a cc is refused with a `validation_error`. The `headers` map refuses both header names, so this field is the only way to set them, and the headers it adds count toward the 25 entries `headers` may hold. A field whose rendered `List-Unsubscribe` would pass 980 characters is refused. Not accepted on a repeating send.","properties":{"url":{"type":"string","minLength":1,"maxLength":700,"pattern":"^[Hh][Tt][Tt][Pp][Ss]:\/\/(?:[A-Za-z0-9\\-._~:\/?#\\[\\]@!$&'()*+;=]|%[0-9A-Fa-f]{2})+$","description":"Absolute https URL that unsubscribes the recipient, at most 700 characters of URL characters only: printable ASCII with no spaces, double quotes, angle brackets or commas (percent-encode a comma as %2C), `%` only as a percent-encoded byte, `[` and `]` only around an IPv6 address host, a port from 1 to 65535, no `=?` (which mail readers can decode as an encoded word), and no user name or password. The host must be public: not a single-label name, `localhost` or a name under `.localhost`, `.internal`, `.local`, `.home.arpa` or `.localdomain`, or a loopback, link-local, private, shared, reserved, multicast or unspecified IP address, including an IPv6 address that embeds one. The scheme is written in lower case."},"one_click":{"type":"boolean","default":true,"description":"Whether to also write `List-Unsubscribe-Post: List-Unsubscribe=One-Click`, which tells the receiver it may unsubscribe with a single POST to `url`. Set false when `url` needs the recipient to open a page and confirm."},"mailto":{"type":"string","minLength":3,"maxLength":254,"pattern":"^[A-Za-z0-9!$'*+=_~-]+(?:\\.[A-Za-z0-9!$'*+=_~-]+)*@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$","description":"An address to also offer as a `mailto:` unsubscribe, written after `url`. The address alone, without the `mailto:` prefix: at most 64 letters, digits and `.!$'*+=_~-` before the `@`, and a dotted domain after it. An `=` is written as `%3D` in the header."}}},"headers":{"type":"object","maxProperties":25,"propertyNames":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9]+(-[A-Za-z0-9]+)*$"},"additionalProperties":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[\\x20-\\x7E]+$"},"description":"Extra headers, as `{ \"Name\": \"value\" }`, at most 25. A name is one of `Auto-Submitted`, `Precedence`, `X-Auto-Response-Suppress`, `X-Primitive-Interaction`, `X-Primitive-Interaction-Ref`, `X-Primitive-Function-Hop`, or a custom `X-` name of letters, digits and hyphens that is not credential-like (a word such as `auth`, `token`, `key`, `secret`, `session` or `bearer`, joined forms included) and not in a reserved family: X-Primitive*, X-Original-*, X-Originating-*, X-Forwarded-*, X-Received*, X-Authenticated*, X-Envelope-*, X-Delivered-To, X-Apparently-To, X-Failed-Recipients, X-Loop, X-Priority, X-MSMail-*, X-MS-*, X-Microsoft-*, X-Forefront-*, X-Exchange-*, X-Google-*, X-GM-*, X-Apple-*, X-Yahoo-*, X-YMail-*, X-AOL-*, X-Spam*, X-Virus-*, X-Proofpoint-*, X-Barracuda-*, X-Rspamd-*, X-Mimecast-*, X-IronPort-*, X-Sophos-*, X-Report-Abuse*, X-CSA-*. Names compare case-insensitively (`_` and `.` count as `-` for families); a repeated or any other name is a `validation_error`. Values: 1 to 200 printable ASCII characters. Custom headers are sent as spelled, are not DKIM-signed, and can be searched with `header` on `GET \/sent-emails`.","example":{"X-Campaign-Id":"spring-sale","Auto-Submitted":"auto-generated"}}}},"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"},"stopped_by":{"type":["string","null"],"enum":["sender","recipient","system",null],"description":"Who ended it: sender (canceled by its creator's side), recipient (stopped with repeat-stop) or system (the platform ended it: max_sends reached, until passed, or the creator lost the authority it was created under). Null while active or paused."},"thread_id":{"type":["string","null"],"format":"uuid","description":"The thread the repeat's sends live in; null until its first send exists."},"last_error_code":{"type":["string","null"],"description":"Why the last attempt did not send, or null. A code starting retry: is a transient failure the platform is retrying."},"held_reason":{"type":["string","null"],"enum":["sender_access_lost","send_refused","credential_reuse_hold",null],"description":"Why an active repeat's last tick was refused rather than sent: sender_access_lost (whoever created it can no longer send from its From; it resumes on its own at the next interval once that access returns) or send_refused (see last_error_code). Or, on a paused repeat, credential_reuse_hold: it was paused because the OAuth grant that created it was revoked after its refresh token was reused; resuming it (its creator, or an owner or admin, as a person) makes it the resumer's. Null when nothing is holding it."}},"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","stopped_by","thread_id","last_error_code","held_reason"]},"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\"`). 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","null"],"description":"Effective idempotency key used for this send. Null when a replayed send was stored without one."},"request_id":{"type":["string","null"],"description":"Server-issued request identifier for support and tracing. Null when a replayed send was stored without one."},"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"},"smtp_enhanced_status_code":{"type":["string","null"],"description":"Enhanced SMTP status code (for example `2.0.0`) from the first downstream delivery outcome when wait is true, or null when the server did not report one."},"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` collapsed onto an earlier reply to the same inbound email made in the last 5 minutes (at any time for a retry from the same Function answering the same inbound email) with the same credential, the same `from`, the same set of recipients (`to`, `cc`, `bcc`) and the same `in_reply_to`, even though its body differs. Any other difference is sent. Pass an `Idempotency-Key` to send a second reply. Absent or null on a request that made a send of its own."},"deduplicated":{"type":"boolean","const":true,"description":"Present and `true` when nothing was sent for this request because it collapsed onto an earlier send (`original_sent_email_id`). Absent on a request that made a send of its own."},"original_sent_email_id":{"type":"string","format":"uuid","description":"With `deduplicated: true`: the earlier send this request collapsed onto. Same value as `id`."}},"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","primitive_managed_domain":"#\/components\/schemas\/SendPermissionPrimitiveManagedDomain","opted_in_domains":"#\/components\/schemas\/SendPermissionOptedInDomains","org_member_email":"#\/components\/schemas\/SendPermissionOrgMemberEmail","org_member_emails":"#\/components\/schemas\/SendPermissionOrgMemberEmails"}},"oneOf":[{"$ref":"#\/components\/schemas\/SendPermissionAnyRecipient"},{"$ref":"#\/components\/schemas\/SendPermissionManagedZone"},{"$ref":"#\/components\/schemas\/SendPermissionYourDomain"},{"$ref":"#\/components\/schemas\/SendPermissionAddress"},{"$ref":"#\/components\/schemas\/SendPermissionPrimitiveManagedDomain"},{"$ref":"#\/components\/schemas\/SendPermissionOptedInDomains"},{"$ref":"#\/components\/schemas\/SendPermissionOrgMemberEmail"},{"$ref":"#\/components\/schemas\/SendPermissionOrgMemberEmails"}]},"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"},"replies_only":{"type":"boolean","description":"Present and true when this credential can only reply to mail it received (it holds mail:reply but not mail:send). The rules in `data` then apply to its replies; it cannot start a new message."}},"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":{"description":"The webhook body, exactly as serialized. May be null: this field carries message content, which is withheld when the content is not available to your credential.","type":["string","null"]},"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},"description":"Event types this endpoint receives. Omitted: every event type except the opt-in sent_email.* events, which an endpoint receives only when it lists them."},"max_size_bytes":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"Skip messages larger than this many bytes."},"exclude_attachments":{"type":"boolean","description":"When true, skip messages that have attachments."},"attachment_limit_mb":{"type":"number","exclusiveMinimum":0,"description":"Skip messages whose attachments total more than this many megabytes."},"sender_whitelist":{"type":"array","items":{"anyOf":[{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","pattern":"^@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$"}]},"description":"Deliver only messages whose authenticated From identity matches an entry: an exact address, or @domain, which also matches its subdomains."},"sender_blacklist":{"type":"array","items":{"anyOf":[{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","pattern":"^@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$"}]},"description":"Skip messages whose envelope sender or From address matches an entry: an exact address, or @domain, which also matches its subdomains."}}},"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":"A pull destination's stable name, or an http webhook's label (null when it has none)."},"owned_by_key":{"description":"Whether an API key owns this webhook instead of the organization.","type":"boolean"},"owner_key_id":{"description":"The API key that owns this webhook. In endpoint responses it is set only for a caller who may read API key metadata, and is null otherwise and when the organization owns the webhook. After a rotation, the key that carries on for the key first named while it can act. When none can, the key delivery stops on: the key first named while it is suspended and not expired, otherwise the newest key of its rotation that is not revoked, otherwise the newest one (only the key first named when nothing may carry on for it).","type":["string","null"],"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)$"},"owner_key_status":{"description":"Whether the owning key can act now. In endpoint responses it is set only for a caller who may read API key metadata. It is the state delivery acts on. Suspended also covers an expired key. A suspended or revoked owner is sent nothing. Anyone who may read the endpoint learns that from delivery.reasons, which never names the key.","type":["string","null"],"enum":["active","suspended","revoked"]},"disabled_reason":{"description":"Why the platform turned this endpoint off, when it names a reason. owner_changed: it was given to a new owner while its URL stayed, and the URL was chosen under the previous owner; turning it on or giving it a new URL starts it again. owner_credential_revoked: the third-party app or agent delegate grant that owned it was revoked; an owner or admin who turns it back on takes it over. Null while it is on and when it was turned off by hand. An access suspension does not set it (a suspension is decided through its own record). Other values may be added.","type":["string","null"]},"delivery":{"description":"For a webhook (kind http) in GET \/endpoints and GET \/endpoints\/{id}: whether it is being sent received mail, and why not. Shown to anyone who may read the endpoint; it names a state, never a key. Null when it could not be checked just now. Absent from other kinds of endpoint and from create and update answers.","type":["object","null"],"properties":{"delivering":{"type":"boolean","description":"Whether the webhook is being sent received mail at all now: it is on, it is not held, and its owner can receive mail. Which messages reach it is still decided per message, by routing, rule grants and its own rules."},"reasons":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":{}},"description":"Every reason it is not, in the order delivery checks them; empty when it is. Codes, of which more may be added: disabled: the endpoint is off (disabled_reason on the endpoint says why). held: the endpoint, or the route the email came through, is held pending an owner or admin decision after its creator's access was reduced. acting_owner_revoked, acting_owner_suspended (which covers an expired key) and acting_owner_left: its owner cannot act now. acting_owner_permission: its owner does not read mail. acting_owner_depth: its owner sees headers only. acting_owner_scope: its owner does not read this email's mailbox, or, with no email to judge, reads no received mail at all now. These three name what the owner's access holds, so a caller who may not read API key metadata sees acting_owner_access in their place."},"held_for_owner":{"description":"Rule-granted mail held for this webhook while its owner cannot act; null when none is. Only for a caller with access to every mailbox (held mail can be an earlier owner's), and absent when it could not be read just now.","type":["object","null"],"properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Rule-granted emails waiting for this webhook because its owner could not act at their last check."},"oldest_queued_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))$","description":"When the oldest of them was queued."},"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))$","description":"When the oldest is given up on if its owner still cannot act. Each is checked again periodically, so the count can trail a change of the owner's state."}},"required":["count","oldest_queued_at","expires_at"],"additionalProperties":{}}},"required":["delivering","reasons"],"additionalProperties":{}},"signed_with":{"description":"Which secret signs deliveries to this webhook: its own (read it from GET \/endpoints\/{id}\/signing-secret), or the organization's webhook secret. Absent for an endpoint that is not a webhook.","type":"string","enum":["own_secret","organization_secret"]},"signing_secret":{"description":"Only in the response that creates a webhook made with a signing secret of its own (one made as a person's own, or by an API key limited to some mailboxes, a third-party app or an agent delegate for itself): that secret, shown this once. Read it again from GET \/endpoints\/{id}\/signing-secret.","type":"string"}},"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},"description":"Event types this endpoint receives. Omitted: every event type except the opt-in sent_email.* events, which an endpoint receives only when it lists them."},"max_size_bytes":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"Skip messages larger than this many bytes."},"exclude_attachments":{"type":"boolean","description":"When true, skip messages that have attachments."},"attachment_limit_mb":{"type":"number","exclusiveMinimum":0,"description":"Skip messages whose attachments total more than this many megabytes."},"sender_whitelist":{"type":"array","items":{"anyOf":[{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","pattern":"^@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$"}]},"description":"Deliver only messages whose authenticated From identity matches an entry: an exact address, or @domain, which also matches its subdomains."},"sender_blacklist":{"type":"array","items":{"anyOf":[{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","pattern":"^@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$"}]},"description":"Skip messages whose envelope sender or From address matches an entry: an exact address, or @domain, which also matches its subdomains."}}},"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","minLength":1,"description":"For kind=pull, the stable name within the credential scope, required (a letter or digit, then letters, digits, - or _, 64 at most). Only connected-agent credentials can create pull destinations, and each receives only its assigned address. For kind=http, an optional label up to 200 characters, shown with the webhook in lists. Not for kind=function."},"takeover":{"type":"boolean","description":"When the domain (or, with no domain_id, the organization's fallback slot) already has an active endpoint: false (the default) answers 409 conflict with details.holder naming it; true retires that endpoint in the same request, after the checks deleting it would need. Route targets and pull destinations never hold the slot. A plan limit (one fallback webhook on the free plan, Power for per-domain webhooks) is checked first, and takeover lifts only the fallback limit the holder itself would count against."},"owner_key_id":{"description":"Make this webhook owned by an API key of the organization instead of by the organization. It receives the mail recipient routes send to it and interaction events for addresses the key reads, each checked against the key: only mail the key can read is sent, and nothing while the key is revoked or suspended. It is never a domain or organization default: it is created as a route target, and is_route_target false or takeover is refused. Not a connected agent key or a Function key.","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)$"}}},"UpdateEndpointInput":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","minLength":1,"description":"New URL, for a webhook (kind=http) only. The endpoint keeps its id, routes, stats, signing secret and owed deliveries, and its consecutive failure count starts again; a webhook turned off because its owner changed is turned back on (the change is refused when it could not be turned on, for example at the plan's webhook limit), unless the same request sets enabled to false. A URL another endpoint on the same domain already uses (enabled or not; endpoints with no domain count as one domain) is refused. A caller limited to its own endpoints (an API key or a person limited to some mailboxes) cannot change the URL of an API key's webhook that signs with the organization's secret: give it a signing secret of its own first. Owners, admins and other callers with the whole organization's mail can change it."},"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},"description":"Event types this endpoint receives. Omitted: every event type except the opt-in sent_email.* events, which an endpoint receives only when it lists them."},"max_size_bytes":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"Skip messages larger than this many bytes."},"exclude_attachments":{"type":"boolean","description":"When true, skip messages that have attachments."},"attachment_limit_mb":{"type":"number","exclusiveMinimum":0,"description":"Skip messages whose attachments total more than this many megabytes."},"sender_whitelist":{"type":"array","items":{"anyOf":[{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","pattern":"^@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$"}]},"description":"Deliver only messages whose authenticated From identity matches an entry: an exact address, or @domain, which also matches its subdomains."},"sender_blacklist":{"type":"array","items":{"anyOf":[{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","pattern":"^@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$"}]},"description":"Skip messages whose envelope sender or From address matches an entry: an exact address, or @domain, which also matches its subdomains."}}},"takeover":{"type":"boolean","description":"For a move to another domain_id whose slot already has an active endpoint: false (the default) answers 409 conflict with details.holder naming it; true retires that endpoint in the same request, after the checks deleting it would need."},"owner_key_id":{"description":"Give this webhook to an API key of the organization. Only a route target can be given to a key; a default endpoint stays the organization's, and takeover is refused with it. null gives a key-owned webhook back to the organization (a caller with access to every mailbox only); it stays a route target. Giving a webhook the organization does not own to a different key, or a key's webhook back to the organization, turns it off (enabled false, disabled_reason owner_changed) unless the same request sets enabled: its URL was chosen under its previous owner. A key that carries on for the key that owns it through rotation (its successor, at any depth up to 16 rotations) is not a different key: the webhook stays as it was, with the same signing secret. That does not hold for a webhook whose owning key was made from a key a transfer moved or rotated away, or from one that has since been deleted: no successor carries on for it, so giving the webhook to one is a change of owner. Turning it on, or a new URL, starts it again. A webhook that signs with its own secret keeps signing with one: the same one when it stays with the same key, or was the organization's (giving away one the organization owns needs webhook_secret:read, since the key and its holder can then read its secret), and a new one for any other owner's. The response's warnings say when an update turned the webhook off or on.","type":["string","null"],"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)$"},"name":{"description":"A webhook's label, up to 200 characters, shown with it in lists; null removes it. Only for http webhooks: a pull destination keeps the name it was created with.","type":["string","null"],"minLength":1}},"minProperties":1},"TestResult":{"type":"object","properties":{"status":{"type":"integer","description":"HTTP status code returned by the endpoint"},"body":{"description":"The receiver response text, only with organization-wide mail.raw:read. Null without that access because a receiver can echo a raw download link.","type":["string","null"]},"signature":{"type":"string","description":"The signature header value sent, answered to a caller who may see it: for a webhook signed with the organization's secret, a caller who may read that secret; for one signed with its own, the person who owns it, calling as themselves."},"reachable":{"type":"boolean","description":"Whether the endpoint answered the test with a 2xx status: the request reached it and its handler accepted it. It says nothing about whether real mail is sent to it: see delivery."},"delivery":{"description":"Whether real email.received events would be sent to this endpoint, judged by the endpoint's own conditions as delivery judges them. Null when that could not be checked just now.","type":["object","null"],"properties":{"event_type":{"type":"string","description":"The event type checked."},"would_deliver":{"type":"boolean","description":"Whether this endpoint's own conditions let the event through: it is on, it is not held, its owner can receive it, and it subscribes to the event type. Which endpoint routing or a rule grant sends a message to is decided separately, per message: POST \/routes\/simulate answers that, and a rule grant can deliver an email outside its owner's mailboxes. A Function endpoint is also judged at delivery on its Function's run state and key."},"reasons":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":{}},"description":"Every reason it would not be sent the event, in the order delivery checks them; empty when it would. Codes, of which more may be added: disabled: the endpoint is off (disabled_reason on the endpoint says why). held: the endpoint, or the route the email came through, is held pending an owner or admin decision after its creator's access was reduced. acting_owner_revoked, acting_owner_suspended (which covers an expired key) and acting_owner_left: its owner cannot act now. acting_owner_permission: its owner does not read mail. acting_owner_depth: its owner sees headers only. acting_owner_scope: its owner does not read this email's mailbox, or, with no email to judge, reads no received mail at all now. These three name what the owner's access holds, so a caller who may not read API key metadata sees acting_owner_access in their place. event_type_not_subscribed: rules.event_types leaves the event out."},"events_paused_until":{"description":"While repeated failures have paused this webhook (its circuit breaker is open): when webhook events other than mail (interaction, sent mail and credential events) are next attempted. Received mail is attempted regardless, so this does not change would_deliver. Null when nothing is paused, and for a Function endpoint.","type":["string","null"],"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))$"}},"required":["event_type","would_deliver","reasons","events_paused_until"],"additionalProperties":{}}},"required":["status","body","reachable","delivery"]},"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 this email would be sent to this endpoint by the endpoint's own conditions: it is on, neither it nor the email's route is held, its owner may receive this email, it subscribes to the event, and its rules match. Which endpoint routing sends the email to is decided separately: POST \/routes\/simulate answers that. A rule grant can still deliver an email whose mailbox the owner does not read, and a Function endpoint is also judged at delivery on its Function's run state and key."},"rule":{"description":"The first check that stops this email, in the order delivery makes them: disabled, held or an acting_owner_* code (see delivery.reasons), then event_types when the endpoint does not subscribe, then the name of the rule that does not match. Null when it would deliver.","type":["string","null"]},"reason":{"description":"Why, in words. Null when it would deliver.","type":["string","null"]},"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"]},"delivery":{"description":"The endpoint's own conditions for this email, as delivery judges them, the owner judged on this email's mailbox and time. Null for a pull destination, which nothing pushes to.","type":["object","null"],"properties":{"event_type":{"type":"string","description":"The event type checked."},"would_deliver":{"type":"boolean","description":"Whether this endpoint's own conditions let the event through: it is on, it is not held, its owner can receive it, and it subscribes to the event type. Which endpoint routing or a rule grant sends a message to is decided separately, per message: POST \/routes\/simulate answers that, and a rule grant can deliver an email outside its owner's mailboxes. A Function endpoint is also judged at delivery on its Function's run state and key."},"reasons":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":{}},"description":"Every reason it would not be sent the event, in the order delivery checks them; empty when it would. Codes, of which more may be added: disabled: the endpoint is off (disabled_reason on the endpoint says why). held: the endpoint, or the route the email came through, is held pending an owner or admin decision after its creator's access was reduced. acting_owner_revoked, acting_owner_suspended (which covers an expired key) and acting_owner_left: its owner cannot act now. acting_owner_permission: its owner does not read mail. acting_owner_depth: its owner sees headers only. acting_owner_scope: its owner does not read this email's mailbox, or, with no email to judge, reads no received mail at all now. These three name what the owner's access holds, so a caller who may not read API key metadata sees acting_owner_access in their place. event_type_not_subscribed: rules.event_types leaves the event out."},"events_paused_until":{"description":"While repeated failures have paused this webhook (its circuit breaker is open): when webhook events other than mail (interaction, sent mail and credential events) are next attempted. Received mail is attempted regardless, so this does not change would_deliver. Null when nothing is paused, and for a Function endpoint.","type":["string","null"],"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))$"}},"required":["event_type","would_deliver","reasons","events_paused_until"],"additionalProperties":{}}},"required":["would_deliver","rule","reason","rules_valid","subscribed_to_event","event_type","evaluated","delivery"]},"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},"expected_priority":{"type":"integer","minimum":0,"maximum":1000000,"description":"The priority the caller read for this route. When given, the whole reorder is refused with 409 conflict (details.route_ids naming the routes) unless the route still has it, so two reorders planned from the same list cannot both land. An entry whose priority equals its expected_priority only checks and writes nothing."}},"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","null"],"format":"uuid","description":"The received email the delivery is about. Null for deliveries that are not about a received email, such as outbound sent_email.* events."},"org_id":{"type":"string","format":"uuid"},"endpoint_id":{"type":"string","format":"uuid"},"endpoint_url":{"type":"string","description":"Where it was sent: a webhook's scheme and host, followed by \"\/... (path and query hidden)\" when its URL has more (GET \/endpoints\/{id} has the whole URL), function:\/\/<id> for a Function, or pull: for a pull subscription."},"status":{"type":"string","enum":["pending","delivered","header_confirmed","failed","skipped_by_rules","held","discarded"],"description":"Delivery status. skipped_by_rules: the endpoint's rules filtered the event out. held: kept back, unsent, until an owner or admin decides on the route that matched it. discarded: a held delivery that decision dropped."},"attempt_count":{"type":"integer"},"duration_ms":{"type":["integer","null"]},"last_error":{"type":["string","null"],"description":"Why the last attempt failed, or null. A webhook's answer is quoted after its status (HTTP 404: ...) only for a caller who can read the webhook (GET \/endpoints\/{id}; a deleted webhook only for a caller who reads every webhook); anyone else gets the status and a note. Text the platform writes (a redirect not followed, a network error) is shown to everyone."},"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"]},"event_type":{"type":["string","null"],"description":"The event type delivered, or null for a delivery recorded before event types existed."},"sent_email_id":{"type":["string","null"],"format":"uuid","description":"The sent email an outbound sent_email.* delivery is about, otherwise null."},"last_error_code":{"type":["string","null"],"description":"Machine-readable code for the last failure, or null. A code the webhook chose in its answer is shown only to a caller who can read the webhook; anyone else gets handler_4xx or handler_5xx."},"response_status":{"type":["integer","null"],"description":"The HTTP status the endpoint answered this attempt with: a webhook's own answer, or for a Function what its runtime answered, which is the platform's own error when the Function could not be run. Null when no answer came back (a network error, a timeout, an attempt refused or not sent) and on attempts recorded before this field."},"response_body":{"type":["string","null"],"description":"The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent. Null for a redirect (3xx), whose body repeats its Location; otherwise, in answers recorded since November 2026, the webhook URL's query string, its parameters, values long enough to be a credential and any userinfo are hidden wherever it repeats them, and the rest is as the endpoint wrote it. Null for a caller who cannot read the webhook (GET \/endpoints\/{id}; a deleted webhook only for a caller who reads every webhook), since a receiver's answer can restate its URL's credential."},"latest_attempt":{"type":"boolean","description":"Whether this is the latest attempt of its delivery: the one recorded last, where a record of no attempt (a rule skip or a held send, attempt_count 0) is latest only when no attempt was made. A retry is recorded as a new attempt with its own id and attempt_count (not always higher than the one before), so an earlier attempt never changes: follow the latest one, or read attempts on any of them."},"retry_scheduled":{"type":["boolean","null"],"description":"Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. A rule-granted delivery is retried on a schedule of its own, to the same endpoint, deciding it afresh: on its latest attempt true while another attempt is to come (the delivery's next_attempt_at says when); null while an attempt is under way (read it again shortly), while it waits on an owner who cannot receive mail or on a held endpoint (checked again hourly, and sent once that clears), when it is past its 7-day limit, or when that could not be read just now; and false otherwise, including once the endpoint is off or already has the email. Null on every attempt of a delivery retried or settled on a schedule of its own that this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, and a sent email's or other event's delivery. On the latest attempt that failed, is pending or was skipped in a way still owed, of an email that was not rejected and still has its content, to an endpoint that is still on, takes the event, has an owner the pass would deliver to (a revoked or suspended key, or an owner that may not receive the message, reads false) and has not received the email by another delivery: null while a delivery pass for the email is running (whether another follows is decided when it ends), true when the email has another automatic pass to come (a scheduled retry, the reclaim of a pass that stopped, or the recovery of a first pass that did not finish), and false otherwise. False on every other attempt, including every attempt a later one followed: no automatic retry of the email's delivery will try it again, which does not mean nothing else ever will."},"next_attempt_at":{"type":["string","null"],"format":"date-time","description":"When the next automatic attempt of a rule-granted delivery is due: set on its latest attempt while retry_scheduled is true. The attempt runs at the first retry pass at or after this time (one runs every few minutes), so a time already past means it is due now. Null otherwise, and on every delivery that is not rule-granted, whose retries are passes for the email with no time reported (whether one is coming is retry_scheduled)."}},"required":["id","email_id","org_id","endpoint_id","endpoint_url","status","attempt_count","created_at","updated_at","event_type","sent_email_id","last_error_code","response_status","response_body","latest_attempt","retry_scheduled","next_attempt_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"},"last_invoked_at":{"type":["string","null"],"format":"date-time","description":"When the function last received a delivery, or null if it never has."},"invocations_24h":{"type":"integer","minimum":0,"description":"Deliveries to the function in the last 24 hours."},"invocations_total":{"type":"integer","minimum":0,"description":"Deliveries to the function over its lifetime."},"tags":{"type":"array","items":{"type":"string"},"description":"The function's tags."},"has_code":{"type":"boolean","description":"Whether the function has stored source code."}},"required":["id","name","deploy_status","created_at","updated_at","last_invoked_at","invocations_24h","invocations_total","tags","has_code"]},"FunctionDetail":{"type":"object","description":"Full function record returned by getFunction.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":["string","null"],"description":"The stored handler source, the same value as code_source. Null when the function has no stored code, or when the caller may not read its source."},"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"},"last_invoked_at":{"type":["string","null"],"format":"date-time","description":"When the function last received a delivery, or null if it never has."},"invocations_24h":{"type":"integer","minimum":0,"description":"Deliveries to the function in the last 24 hours."},"invocations_total":{"type":"integer","minimum":0,"description":"Deliveries to the function over its lifetime."},"tags":{"type":"array","items":{"type":"string"},"description":"The function's tags."},"has_code":{"type":"boolean","description":"Whether the function has stored source code."},"code_source":{"type":["string","null"],"description":"The stored handler source. Null when the function has no stored code, or when the caller may not read its source."},"current_deployment_id":{"type":["string","null"],"format":"uuid","description":"The deployment serving traffic, or null before the first deploy."},"test_readiness":{"type":"object","description":"Whether a test run (testFunction) can be sent now. When ready is false, reason says why: no_endpoint (the function is not routed), no_inbound_domain (no domain can receive the test) or not_permitted (the caller may not run it). When ready is true, endpoint and domain name where the test lands, unless that domain is outside the caller's scope.","properties":{"ready":{"type":"boolean"},"reason":{"type":"string","enum":["no_endpoint","no_inbound_domain","not_permitted"]},"endpoint":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"domain_id":{"type":["string","null"],"format":"uuid"}},"required":["id","domain_id"]},"domain":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"domain":{"type":"string"}},"required":["id","domain"]}},"required":["ready"]},"source_kind":{"type":"string","enum":["inline","github"],"description":"inline: the code is deployed through this API. github: the function is bound to a GitHub repository, and its code changes arrive from there."}},"required":["id","name","code","deploy_status","created_at","updated_at","last_invoked_at","invocations_24h","invocations_total","tags","has_code","code_source","current_deployment_id","test_readiness"]},"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 Functions runtime before deploying. Include a\npackage.json (its `dependencies` are installed). Provide\neither `code` or `files`, not both.\n"},"entryPoint":{"type":"string","minLength":1,"description":"Entry file for a managed build: a key of files. Only valid with files; without it the builder looks for a conventional entry such as src\/index.ts."},"runtime_grant":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"object","properties":{"preset":{"type":"string","minLength":1,"maxLength":64},"addresses":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":320}},"permissions":{"maxItems":200,"type":"array","items":{"type":"string","maxLength":128}},"mail_scope":{"type":"string","const":"org"}},"required":["preset"],"additionalProperties":false}],"description":"The key the new function runs with: a preset name, or a preset with the addresses or permissions it narrows to. Omitted, it runs with the caller's own access. A grant wider than the caller's is refused. A custom grant must list addresses or send mail_scope \"org\"; one that does neither is refused (400 validation_error, details.reason mail_scope_required). listFunctionRuntimeKeyPresets lists the presets."}},"required":["name"]},"CreateFunctionResult":{"type":"object","description":"Returned by POST \/functions. deploy_status is pending when the request carried no code.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"deploy_status":{"$ref":"#\/components\/schemas\/FunctionDeployStatus"},"deployment_id":{"type":"string","format":"uuid","description":"The deployment this request created, when it sent files for a managed build."},"build":{"$ref":"#\/components\/schemas\/FunctionBuildSummary"}},"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"},"entryPoint":{"type":"string","minLength":1,"description":"Entry file for a managed build: a key of files. Only valid with files; without it the builder looks for a conventional entry such as src\/index.ts."}},"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","null"],"format":"uuid","description":"The function's endpoint. Null when the function is routed to a domain outside the caller's scope (domain_hidden)."},"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"},"domain_hidden":{"type":"boolean","enum":[true],"description":"Present, and true, when the function is routed to a domain outside the caller's scope: domain is null, but the binding is not the fallback, and the endpoint's id, rules and delivery counters are left out."}},"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"]},"default_destination":{"$ref":"#\/components\/schemas\/FunctionRoutingDestination","description":"Where this domain's mail that no route matches goes, or 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"]}},"org_default_destination":{"$ref":"#\/components\/schemas\/FunctionRoutingDestination","description":"Where mail on a verified domain with no route or default of its own goes, or null."}},"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","null"],"format":"uri","description":"The delivery's destination. Null for a delivery to another endpoint the same message went to."},"status":{"type":"string","enum":["pending","delivered","header_confirmed","failed","skipped_by_rules","held","discarded"]},"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","null"],"format":"uri","description":"The URL the function requested. Null for a caller whose credential does not reach mail content."},"host":{"type":"string"},"path":{"type":["string","null"],"description":"The request path. Null for a caller whose credential does not reach mail content."},"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","null"],"description":"The reply's subject. Null when a refused send's content was discarded."},"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"}},"not_delivered":{"type":"object","description":"Present when the function's key or state refused the test message: the refusal code and its explanation. Never anything the function wrote.","properties":{"code":{"type":"string"},"message":{"type":["string","null"]}},"required":["code","message"],"additionalProperties":false}},"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","null"],"description":"The textual message body. Null for a caller whose credential does not reach mail content, since a function handling mail can log text from it."},"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. A Function scope is available only to that Function's own credential, to an org-wide API key that can manage the Function, or to a person that can manage the Function; other credentials get `403 forbidden`.","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":[]},"schedule":{"$ref":"#\/components\/schemas\/TemplateSchedule"},"setup":{"$ref":"#\/components\/schemas\/TemplateSetup"},"postInstall":{"type":"string"},"recommendedGrant":{"description":"The runtime key the template suggests for the function it installs: a preset name, or an object with preset and optional addresses, permissions and mail_scope. The installer still chooses within their own access, and a value the server cannot use is ignored."}},"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-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-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-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":{"type":["boolean","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},"AgentWorkRecord":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","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}$"},"agentId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"workTitle":{"type":["string","null"]},"workDetail":{"type":["string","null"]},"status":{"type":"string","enum":["active","completed","cancelled"]},"proposedDeadlineAt":{"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"}]},"deadline":{"anyOf":[{"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}$"},"dueAt":{"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)))$"},"agreedAt":{"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)))$"},"agreedByUserId":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"workVersion":{"type":"integer","minimum":1,"maximum":2147483646}},"required":["id","dueAt","agreedAt","agreedByUserId","workVersion"],"additionalProperties":false},{"type":"null"}]},"createdBy":{"type":"object","properties":{"kind":{"type":"string","enum":["human","agent"]},"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":["kind","id"],"additionalProperties":false},"updatedBy":{"type":"object","properties":{"kind":{"type":"string","enum":["human","agent"]},"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":["kind","id"],"additionalProperties":false},"createdAt":{"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)))$"},"updatedAt":{"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":["id","agentId","version","workTitle","workDetail","status","proposedDeadlineAt","deadline","createdBy","updatedBy","createdAt","updatedAt"],"additionalProperties":false},"AgentWorkPage":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"records":{"type":"array","items":{"$ref":"#\/components\/schemas\/AgentWorkRecord"}},"nextCursor":{"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"}]}},"required":["records","nextCursor"],"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"]}}]},"ArchiveMember":{"type":"object","required":["path","size_bytes","sha256"],"properties":{"path":{"type":"string","description":"Member path inside the archive, relative, with \"\/\" separators and no leading \".\/\"."},"size_bytes":{"type":"integer","minimum":0,"description":"Decoded size in bytes."},"sha256":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 of the decoded bytes."}}},"ArchiveMembers":{"type":"object","required":["part_index","filename","format","members"],"properties":{"part_index":{"type":"integer","minimum":0,"description":"The attachment the listing is of."},"filename":{"type":["string","null"],"description":"That attachment's filename."},"format":{"type":"string","enum":["zip","tar","tar.gz"]},"members":{"type":"array","items":{"$ref":"#\/components\/schemas\/ArchiveMember"},"description":"Regular files in archive order."}}},"EmailListMeta":{"allOf":[{"$ref":"#\/components\/schemas\/PaginationMeta"},{"type":"object","properties":{"recipient_outside_access":{"type":"boolean","description":"Present, as true, when your credential is limited to some mailboxes, recipient names one that is neither one of its addresses nor on one of its domains (or the credential reads no received mail at all), and it reads through no email rule: the page is then empty however much mail that mailbox holds. Absent otherwise, and for an address on one of your domains, which may still list nothing."},"recipient_rules_only":{"type":"boolean","description":"Present, as true, when your credential is limited to some mailboxes, recipient names one that is neither one of its addresses nor on one of its domains, and the credential reads some mail through email rules: the page holds only the mail those rules give it there. Absent otherwise."}}}]},"IncludedMessageHeaders":{"type":"object","description":"Stored addressing and threading headers. A member is null when absent or withheld.","properties":{"from":{"type":["string","null"]},"to":{"type":["array","null"],"items":{"type":"string"}},"cc":{"type":["array","null"],"items":{"type":"string"}},"bcc":{"type":["array","null"],"items":{"type":"string"}},"reply_to":{"type":["array","null"],"items":{"type":"string"}},"in_reply_to":{"type":["array","null"],"items":{"type":"string"}},"references":{"type":["array","null"],"items":{"type":"string"}},"automation":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Received mail only."}},"required":["from","to","cc","bcc","reply_to","in_reply_to","references","automation"]},"ListInteractionsResult":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"wire_id":{"type":"string"},"role":{"type":"string"},"protocol":{"type":"string"},"protocol_version":{"type":"integer"},"state":{"type":"string"},"outcome":{"type":["string","null"]},"failure_reason":{"type":["string","null"]},"awaiting":{"type":["string","null"]},"counterparty_address":{"type":"string"},"counterparty_domain":{"type":"string"},"our_address":{"type":"string"},"step_count":{"type":"integer"},"expires_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"completed_at":{"type":["string","null"]},"latest_step_at":{"type":"string"}},"required":["id","wire_id","role","protocol","protocol_version","state","outcome","failure_reason","awaiting","counterparty_address","counterparty_domain","our_address","step_count","expires_at","created_at","updated_at","completed_at","latest_step_at"],"additionalProperties":false}},"next_cursor":{"type":["string","null"]}},"required":["items","next_cursor"],"additionalProperties":false},"GetInteractionResult":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"wire_id":{"type":"string"},"role":{"type":"string"},"protocol":{"type":"string"},"protocol_version":{"type":"integer"},"state":{"type":"string"},"outcome":{"type":["string","null"]},"failure_reason":{"type":["string","null"]},"awaiting":{"type":["string","null"]},"counterparty_address":{"type":"string"},"counterparty_domain":{"type":"string"},"our_address":{"type":"string"},"step_count":{"type":"integer"},"expires_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"completed_at":{"type":["string","null"]},"failure_detail":{"type":["string","null"]},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"wire_step_id":{"type":"string"},"step":{"type":"string"},"direction":{"type":"string"},"accepted":{"type":"boolean"},"reject_reason":{"type":["string","null"]},"created_at":{"type":"string"},"email_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"sent_email_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]}},"required":["id","wire_step_id","step","direction","accepted","reject_reason","created_at","email_id","sent_email_id"],"additionalProperties":false}}},"required":["id","wire_id","role","protocol","protocol_version","state","outcome","failure_reason","awaiting","counterparty_address","counterparty_domain","our_address","step_count","expires_at","created_at","updated_at","completed_at","failure_detail","steps"],"additionalProperties":false},"StartInteractionRequest":{"type":"object","properties":{"protocol":{"type":"string","minLength":1},"protocol_version":{"default":1,"type":"integer","exclusiveMinimum":0},"step":{"type":"string","minLength":1},"from":{"type":"string","minLength":3,"maxLength":998},"to":{"type":"string","minLength":3,"maxLength":320},"payload":{"description":"The step's protocol payload, at most 16384 bytes as JSON. Its shape is the protocol's."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"subject":{"type":"string","minLength":1,"maxLength":998},"body_text":{"type":"string","maxLength":262144}},"required":["protocol","step","from","to"],"additionalProperties":false},"StartInteractionResult":{"type":"object","properties":{"interaction_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"wire_id":{"type":"string"},"step_id":{"type":"string"},"send_id":{"type":["string","null"]},"events":{"type":"array","items":{"type":"string"}}},"required":["interaction_id","wire_id","step_id","send_id","events"],"additionalProperties":{}},"SendInteractionStepRequest":{"type":"object","properties":{"step":{"type":"string","minLength":1},"payload":{"description":"The step's protocol payload, at most 16384 bytes as JSON. Its shape is the protocol's."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"subject":{"type":"string","minLength":1,"maxLength":998},"body_text":{"type":"string","maxLength":262144}},"required":["step"],"additionalProperties":false},"SendInteractionStepResult":{"type":"object","properties":{"interaction_id":{"type":"string","format":"uuid"},"wire_id":{"type":"string"},"step_id":{"type":"string"},"send_id":{"type":["string","null"]},"state":{"type":"string"},"failure_reason":{"type":["string","null"]},"events":{"type":"array","items":{"type":"string"}}},"required":["interaction_id","wire_id","step_id","state","events"],"additionalProperties":{}},"GetInboundHourlyStatsResult":{"type":"array","items":{"type":"object","properties":{"hour":{"type":"string","format":"date-time"},"accepted":{"type":"integer","minimum":0},"failing":{"type":"integer","minimum":0},"rejected":{"type":"integer","minimum":0}},"required":["hour","accepted","failing","rejected"],"additionalProperties":false}},"GetSentHourlyStatsResult":{"type":"array","items":{"type":"object","properties":{"hour":{"type":"string","format":"date-time"},"delivered":{"type":"integer","minimum":0},"bounced":{"type":"integer","minimum":0},"denied":{"type":"integer","minimum":0}},"required":["hour","delivered","bounced","denied"],"additionalProperties":false}},"GetWebhookDeliveryStatsResult":{"type":"object","properties":{"since":{"type":"string","format":"date-time"},"clean_success":{"type":"integer","minimum":0},"recovered":{"type":"integer","minimum":0},"failed":{"type":"integer","minimum":0},"regressed":{"type":"integer","minimum":0}},"required":["since","clean_success","recovered","failed","regressed"],"additionalProperties":false},"ListActivityObjectsResult":{"type":"object","properties":{"covers":{"type":"string","description":"you: what a person created through any credential of theirs, or with relation=asked what others asked of them (their dashboard session, their own CLI or app login, apps and agent delegates acting for them, their API keys while theirs, their personal agents). this_credential: what the calling key, app or agent created itself."},"items":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","description":"One of: function, endpoint, function_trigger, pull_subscription, recipient_route, filter_rule, wake_schedule, repeating_send, scheduled_send, api_key, agent_connection, email_rule, email_rule_request, email_rule_objection, email_rule_coverage."},"id":{"type":"string"},"name":{"description":"The name the object is known by: an endpoint's display name, a route's or filter's pattern, a Function's name, a send's From address, a wake schedule's command and the address it wakes, an API key's name, an agent's address, a rule's or rule request's name, and for an objection or a coverage notice the name its rule had then (for a stopped notice, when you were last told of it, or while it last covered you). Never a webhook URL and never mail content.","type":["string","null"]},"state":{"type":"string","description":"Where it stands now: enabled, disabled, deleted or replaced (endpoints and routes), enabled or disabled (filters, wake schedules), live, suspended or deleted (Functions), active, paused or ended (repeating sends), scheduled (scheduled sends), active, suspended, expired or revoked (API keys), pending, claimed, connected or revoked (agents), draft, observe or active (email rules), pending, accepted, declined or withdrawn (rule requests and objections), and for a coverage notice what it told: started (a rule was made or turned active over your mailboxes, or an active rule came to reach you; once per rule, not per mailbox), new_version, stopped (it no longer covers you, why in stop_reason), or baseline (it already covered you when notices began). pending: a rule started covering your mailbox and the notices have not recorded that start yet (a restart within an hour of the last start notice, or within 5 minutes of the notices finding the rule stopped, is recorded without a new one, unless you were told it stopped since); its id is then the rule's, and created_at when the rule last changed."},"created_at":{"type":"string","format":"date-time"},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"When it was deleted, replaced or revoked, when an API key expired, when a repeating send stopped, or when a rule request or objection was decided (accepted, declined or withdrawn); null otherwise, as on every coverage notice (rule_now marks one whose rule was deleted)."},"owned_now":{"type":"string","enum":["you","someone_else"],"description":"you: it is still yours: never transferred, or transferred back to you. someone_else: it was transferred and another person holds it now, or, with relation=asked or concerning, another member made it."},"created_through":{"type":"object","properties":{"kind":{"type":"string","description":"The kind of credential it was created through: org_key, function_key, connected_key, oauth, agent_delegate or member (the person themselves). With relation=asked, member: the member who asked or objected; with relation=concerning, member: the rule's author."},"id":{"description":"In a person's view, the person's own user id for member, and an API key's id where they may read their keys (for a person limited to their own keys, only a key they still hold). Null otherwise, and always null in one credential's view, where it is the calling credential itself. With relation=asked, the user id of the member who asked or objected; with relation=concerning, of the rule's author.","type":["string","null"]},"name":{"description":"In a person's view, what that credential of theirs is called: an app login by its app, an API key by its name, an agent's key by its address. Null otherwise, and for the person themselves. With relation=asked, the name of the member who asked or objected; with relation=concerning, of the rule's author.","type":["string","null"]}},"required":["kind","id","name"],"additionalProperties":false},"rule_id":{"description":"For email_rule_coverage, always: the rule it is about, since names need not be unique (for pending, the same as id). GET \/v1\/email-rules\/covering-me\/{rule_id} shows the rule while it covers you, or as it last decided on your mail; otherwise it answers not_found, with details.reason rule_deleted once a rule you were told of is deleted.","type":"string","format":"uuid"},"rule_now":{"description":"For email_rule_coverage: deleted once the rule it is about has been deleted. Left out while it exists, or when that could not be checked.","type":"string","enum":["deleted"]},"stop_reason":{"description":"For a stopped notice: deleted, turned_off (no longer active), or no_longer_covers (active, reaching none of your mailboxes now).","type":"string","enum":["deleted","turned_off","no_longer_covers"]}},"required":["kind","id","name","state","created_at","ended_at","owned_now","created_through"],"additionalProperties":false}},"withheld":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"reason":{"type":"string","enum":["permission_missing","mail_scope","not_for_this_credential"],"description":"permission_missing: the caller lacks the permission the kind's own list needs (named in permission). mail_scope: listed only to a caller that reads every mailbox. not_for_this_credential: this kind of credential never lists them (a person's own kinds asked for by a key, an app or an agent), or the kind's own list refuses it (email rules, rule requests and objections for a token narrowed below the person; objections anywhere but the dashboard and the Primitive CLI)."},"permission":{"type":["string","null"]}},"required":["kind","reason","permission"],"additionalProperties":false},"description":"Kinds left out because the caller could not list them through their own routes. Nothing is said about how many there are."},"next_cursor":{"description":"Pass as cursor to read the next page; null on the last page. A page can hold fewer items than limit, or none, while next_cursor is set.","type":["string","null"]},"as_of":{"description":"With relation=concerning only: about when the notices last looked at your organization; null if never or unread. Coverage since may have no notice yet. They look only while one of its rules could cover someone, or a stopped notice is still to be told, so this stops moving while neither.","anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"pending_complete":{"description":"With relation=concerning, on the first page only: whether the check for rules that started covering you without their start notice (state pending, listed ahead of the notices and beyond limit) was finished. false: some may be missing. Absent where there is nothing to check.","type":"boolean"}},"required":["covers","items","withheld","next_cursor"],"additionalProperties":false},"GetEmailRuleVocabularyResult":{"type":"object","properties":{"version":{"type":"string"},"tree_version":{"type":"number","const":1},"columns":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["bool","text","text_list","number","timestamp","choice","multi"]},"source":{"type":"string","enum":["email","fact"],"description":"email: from the message itself. fact: from its classification."},"description":{"type":"string"},"operators":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["is_true","is_false","eq","in","contains","contains_any","contains_all","starts_with","ends_with","is_null","not_null","is_empty","not_empty","gt","gte","lt","lte","between","before","after"]},"value":{"type":"string","enum":["none","text","text_list","number","number_range","date_time"]}},"required":["op","value"],"additionalProperties":false},"description":"Each operator allowed, and its value: none (no value), text, text_list (a list of texts), number, number_range ([low, high]) or date_time (ISO 8601 with an offset from -12:00 to +14:00)."},"options":{"description":"The only values it takes, on a column that has them.","type":"array","items":{"type":"string"}},"min_confidence":{"type":"boolean","description":"Whether a condition may set min_confidence (0 to 1), though not one under an odd number of nots. On a multi column it tests each value the condition names by that value's own confidence: contains its value, contains_any at least one value the email has, contains_all every value."}},"required":["name","type","source","description","operators","min_confidence"],"additionalProperties":false}},"limits":{"type":"object","properties":{"depth":{"type":"integer","minimum":0},"conditions":{"type":"integer","minimum":0},"list_values":{"type":"integer","minimum":0},"text_length":{"type":"integer","minimum":0},"questions":{"type":"integer","minimum":0},"question_min_length":{"type":"integer","minimum":0},"question_max_length":{"type":"integer","minimum":0},"tree_bytes":{"type":"integer","minimum":0}},"required":["depth","conditions","list_values","text_length","questions","question_min_length","question_max_length","tree_bytes"],"additionalProperties":false,"description":"The most nesting levels (a condition is one), conditions, list values, characters in a text value and questions; a trimmed question's length bounds; and the most bytes the tree takes as stored JSON."}},"required":["version","tree_version","columns","limits"],"additionalProperties":false},"EmailRuleTree":{"type":"object","properties":{"version":{"type":"number","const":1},"vocabulary":{"type":"string","description":"Vocabulary version, such as v1."},"when":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"A condition {column, op, value?, min_confidence?} (see GET \/email-rules\/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."},"questions":{"type":"array","items":{"type":"string"},"description":"Yes\/no questions asked once the conditions match, over the email's text and attachment names, never attachment contents. A grant rule needs every one yes; a do_not_share rule matches on any yes."}},"required":["version","vocabulary","when","questions"],"additionalProperties":false},"EmailRuleBreadth":{"type":"object","properties":{"checked":{"type":"integer","minimum":0,"description":"Classified emails the conditions were decided for: of the newest 200 from the last 30 days in the scope counted (evaluation_scope as sent; by default your own mailboxes, for a grant rule those you may share), the ones you can still read."},"matched":{"type":"integer","minimum":0,"description":"Of those, how many they hold for (for a do-not-share rule, one they cannot decide counts)."}},"required":["checked","matched"],"additionalProperties":false},"EmailRuleScopeUsed":{"type":"object","properties":{"evaluation_scope":{"type":"string","enum":["author","own_mailboxes","selected"],"description":"author, own_mailboxes or selected, as a rule's evaluation_scope."},"scope_selection":{"anyOf":[{"type":"object","properties":{"addresses":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":320}},"domains":{"maxItems":50,"type":"array","items":{"type":"string","maxLength":253}}},"required":["addresses","domains"],"additionalProperties":false},{"type":"null"}],"description":"What a selected scope names; null for any other."}},"required":["evaluation_scope","scope_selection"],"additionalProperties":false},"EmailRuleScopeWarning":{"type":"object","properties":{"name":{"type":"string","maxLength":320},"kind":{"type":"string","enum":["address","domain"]},"reason":{"type":"string","enum":["unknown_domain","inactive_domain","unknown_address"],"description":"unknown_domain: none of the organization's domains. inactive_domain: one of its domains, not active now, so no new mail arrives. unknown_address: no inbox known as someone's, and no mail has reached it yet."},"message":{"type":"string","maxLength":1000}},"required":["name","kind","reason","message"],"additionalProperties":false},"EmailRuleScopeSelection":{"type":"object","properties":{"addresses":{"maxItems":100,"type":"array","items":{"type":"string","minLength":3,"maxLength":320,"pattern":"^[^\\s@]+@[^\\s@]+$"}},"domains":{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":253,"pattern":"^[^\\s@]+\\.[^\\s@]+$"}}},"additionalProperties":false,"description":"For a selected rule: inboxes, and domains (each address at them the author reads); each one you can read."},"CompileEmailRuleRequest":{"type":"object","properties":{"english":{"type":"string","minLength":1,"maxLength":2000,"description":"What the rule should match, in plain English."},"kind":{"description":"What the rule will be: grant (the default) or do_not_share. A do-not-share rule is compiled for recall: conditions only as narrow as the description says, and the judgement of what it describes put into questions, since an email it misses can be shared.","type":"string","enum":["grant","do_not_share"]},"include_sql":{"description":"Also return the SQL the rule renders to. Off by default.","type":"boolean"},"evaluation_scope":{"description":"The scope breadth is counted over, as a test of the tree runs it: send the one you will save the rule with. Nothing is saved. own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those mail:share_own lets you share). author: all you can read, inboxes added later included. selected: what scope_selection names.","type":"string","enum":["author","own_mailboxes","selected"]},"scope_selection":{"$ref":"#\/components\/schemas\/EmailRuleScopeSelection"}},"required":["english"],"additionalProperties":false},"CompileEmailRuleResult":{"type":"object","properties":{"tree":{"$ref":"#\/components\/schemas\/EmailRuleTree"},"explanation":{"type":"string","description":"What the rule matches, in plain English: only which emails, not what it grants or shares, which follows from its state, its scope and the keys, webhooks and people it reaches. The compiler leaves out a sentence that plainly says what the rule hands over."},"assumptions":{"type":"array","items":{"type":"string"},"description":"Readings the compiler chose where the description was open to more than one. Whenever the rule holds a condition, question or confidence bar that no words of the description call for, this list also has a line starting \"Added without the description asking for it:\" naming it (several share a line when there are many)."},"warnings":{"type":"array","items":{"type":"string"},"description":"Parts of the description the column contract cannot express, how the rule handles them, and the compiler's own notes on the rule (the Email Rules docs list them). When the rule has no questions and its conditions hold for nearly all of your recent mail (see breadth), or it is a grant rule whose conditions alone hold for over 20% of at least 20 emails (see conditions_breadth), the first warning says so."},"compiled_by":{"type":"string","description":"An opaque name for the version of the rule compiler that produced the rule, such as rule-compiler-v1. Send it back as compiled_by when you save the rule."},"kind":{"description":"The kind it was compiled for. Send it with the tree to a test.","type":"string","enum":["grant","do_not_share"]},"breadth":{"description":"How many recent emails its conditions hold for, warning or not, counted over evaluation_scope as sent (by default your own mailboxes, whatever scope you save it with). Null when not counted in full: a rule with questions, headers-only access, no test of it possible as you, a count that failed or ran out of time, or, with no scope sent, a rule that holds only for mail to To or Cc addresses outside your own mailboxes (a warning says so). Writes ignore it, so a compile answer (without include_sql) can be sent back unchanged.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"conditions_breadth":{"description":"For a grant rule with questions, breadth counted on its conditions alone: the emails its questions are asked about. Null for any other rule, or when not counted in full (as breadth).","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"breadth_scope":{"description":"The scope breadth and conditions_breadth are counted over: evaluation_scope as sent, by default own_mailboxes. Not the saved rule's scope, which a write sets with evaluation_scope; writes ignore this, as they ignore breadth.","$ref":"#\/components\/schemas\/EmailRuleScopeUsed"},"scope_warnings":{"description":"Names in the scope_selection sent that match nothing this organization receives mail at (a typo, or an inbox not yet in use), as a test or write would warn of them. Present only when there are some; writes ignore it.","maxItems":150,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleScopeWarning"}},"sql":{"description":"Present only when include_sql was true.","type":"object","properties":{"expression":{"type":"string","description":"A parameterized boolean expression over one row per email."},"params":{"type":"array","items":{},"description":"The values bound to $1, $2 and so on."}},"required":["expression","params"],"additionalProperties":false}},"required":["tree","explanation","assumptions","warnings","compiled_by"],"additionalProperties":false},"EmailRuleTested":{"type":"object","properties":{"id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"version":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}]},"vocabulary":{"type":"string"},"kind":{"description":"The kind it was tested as.","type":"string","enum":["grant","do_not_share"]},"note":{"description":"Present when a tree was sent without kind, so it was tested as grant.","type":"string"}},"required":["id","version","vocabulary"],"additionalProperties":false},"EmailRuleSampleJudgment":{"type":"object","properties":{"index":{"type":"integer","minimum":0},"sample_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"label":{"type":["string","null"]},"expected":{"type":"string","enum":["match","no_match"]},"outcome":{"type":"string","enum":["matched","near_miss","no_match","not_decided"]},"status":{"type":"string","enum":["passed","failed","undecided"]},"question_answers":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"answer":{"type":["boolean","null"]},"probability":{"type":["number","null"]},"error_class":{"type":"string"}},"required":["question","answer","probability"],"additionalProperties":false}}},"required":["index","sample_id","label","expected","outcome","status","question_answers"],"additionalProperties":false},"EmailRuleTreeInput":{"type":"object","properties":{"version":{"type":"number","const":1},"vocabulary":{"type":"string","description":"Vocabulary version, such as v1."},"when":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"A condition {column, op, value?, min_confidence?} (see GET \/email-rules\/vocabulary) or {all: [...]}, {any: [...]}, {not: ...}."},"questions":{"description":"Yes\/no questions asked once the conditions match, over the email's text and attachment names, never attachment contents. A grant rule needs every one yes; a do_not_share rule matches on any yes. Left out, none.","type":"array","items":{"type":"string"}}},"required":["version","vocabulary","when"],"additionalProperties":false},"EmailRuleExpectationSampleInput":{"type":"object","properties":{"expected":{"type":"string","enum":["match","no_match"]},"label":{"description":"What the email stands for, such as a personal medical bill.","anyOf":[{"type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\u0000]*$"},{"type":"null"}]},"email":{"$ref":"#\/components\/schemas\/EmailRuleSavedSampleEmail"},"id":{"type":"string","format":"uuid"},"created_at":{"type":"string"}},"required":["expected","email"],"additionalProperties":false},"EmailRuleSavedSampleEmail":{"type":"object","properties":{"from":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string","maxLength":200,"pattern":"^[^\\u0000]*$"}},"required":["address"],"additionalProperties":false},"to":{"minItems":1,"maxItems":10,"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"cc":{"maxItems":10,"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"subject":{"type":"string","maxLength":998,"pattern":"^[^\\u0000]*$"},"text":{"type":"string","minLength":1,"maxLength":4000,"pattern":"^[^\\u0000]*$"},"attachment_types":{"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[^\\u0000]*$"}},"attachment_names":{"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[^\\u0000-\\u001f\\u007f]*$"}},"automated":{"type":"boolean"},"from_mailing_list":{"type":"boolean"},"spam_score":{"type":"number","minimum":-1000,"maximum":1000}},"required":["from","to","text"],"additionalProperties":false,"description":"A made-up email, as POST \/email-rules\/test-sample takes it."},"TestEmailRuleRequest":{"type":"object","properties":{"tree":{"description":"An unsaved rule tree, as POST \/email-rules\/compile returned it or written by hand. Give one of tree, rule_id and request_id.","$ref":"#\/components\/schemas\/EmailRuleTreeInput"},"evaluation_scope":{"type":"string","enum":["author","own_mailboxes","selected"],"description":"own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those mail:share_own lets you share). author: all you can read, inboxes added later included. selected: what scope_selection names."},"scope_selection":{"$ref":"#\/components\/schemas\/EmailRuleScopeSelection"},"rule_id":{"description":"A saved rule you can see. Its current version is tested, with its author's access; one you see only because it covers your mailboxes, with yours, over only those (dashboard or Primitive CLI only).","type":"string","format":"uuid"},"request_id":{"description":"A pending rule request you can see: its rule as asked, tested as you over your own mailboxes (what accepting would share, for the member asked). Give one of tree, rule_id and request_id.","type":"string","format":"uuid"},"kind":{"description":"For an unsaved tree: do_not_share tests it as a rule that blocks sharing, so an email it cannot decide counts as matched. A saved rule or a request uses its own.","type":"string","enum":["grant","do_not_share"]},"email_ids":{"description":"Test exactly these emails, and any expected ones. Without it, the newest emails the rule can read are tested, at most 200 and none older than 30 days.","minItems":1,"maxItems":200,"type":"array","items":{"type":"string","format":"uuid"}},"classify_missing":{"description":"Classify emails in the set that have not been classified yet and arrived more than 2 hours ago, and save their classification. Defaults to true. Newer ones are left to the normal classification and reported pending. The rule is never evaluated on them for real.","type":"boolean"},"expect":{"type":"object","properties":{"match":{"maxItems":100,"type":"array","items":{"type":"string","format":"uuid"}},"no_match":{"maxItems":100,"type":"array","items":{"type":"string","format":"uuid"}},"samples":{"maxItems":20,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleExpectationSampleInput"}}},"additionalProperties":false,"description":"Emails the rule must match and must not (at most 100), always tested whatever their age, and made-up emails (samples, at most 20). Given, it replaces a saved rule's own."}},"additionalProperties":false},"TestEmailRuleResult":{"type":"object","properties":{"rule":{"$ref":"#\/components\/schemas\/EmailRuleTested"},"window":{"type":"object","properties":{"mode":{"type":"string","enum":["recent","ids"]},"since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"The oldest arrival the recent window reaches; null when email_ids were given."},"limit":{"type":"integer","minimum":0,"description":"The most emails the recent window holds: 200, less one place per expected email."},"scope":{"description":"The scope the test ran with: a saved rule's as stored, over its author's mail (GET \/email-rules\/{id}\/scope: what it reaches now), or a tree's as sent, over yours (by default own_mailboxes). A rule covering you, tested as you: own_mailboxes, your mailboxes it covers.","$ref":"#\/components\/schemas\/EmailRuleScopeUsed"},"oldest_at":{"description":"When the oldest email tested arrived, expected ones included; null when none was. The window is the newest 200 the scope reaches, so a narrower scope reaches further back: compare counts only over the same scope and span.","anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"newest_at":{"description":"When the newest email tested arrived; null when none was.","anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"required":["mode","since","limit"],"additionalProperties":false},"counts":{"type":"object","properties":{"emails":{"type":"integer","minimum":0},"matched":{"type":"integer","minimum":0},"near_miss":{"type":"integer","minimum":0},"no_match":{"type":"integer","minimum":0},"unclassified":{"type":"integer","minimum":0},"skipped":{"type":"integer","minimum":0},"not_found":{"type":"integer","minimum":0}},"required":["emails","matched","near_miss","no_match","unclassified","not_found"],"additionalProperties":false},"classification":{"type":"object","properties":{"classified_now":{"type":"integer","minimum":0,"description":"Emails this test classified and saved."},"still_unclassified":{"type":"integer","minimum":0,"description":"Emails left without a classification (unclassified); a skipped email is counted under skipped instead."}},"required":["classified_now","still_unclassified"],"additionalProperties":false},"questions":{"type":"object","properties":{"asked":{"type":"integer","minimum":0,"description":"Calls made to answer the rule's questions, one per matching email whose answers were not reused."},"capped":{"type":"boolean","description":"Whether the per-test limit on those calls was reached."},"stored":{"description":"Matching emails whose question answers were reused (kept for the saved rule version, or recorded with its decision) instead of asked.","type":"integer","minimum":0}},"required":["asked","capped"],"additionalProperties":false},"results":{"type":"array","items":{"type":"object","properties":{"email_id":{"type":"string","format":"uuid"},"outcome":{"type":"string","enum":["matched","near_miss","no_match","unclassified","skipped","not_found"],"description":"near_miss: Not matched, but nearly: its conditions held and its questions did not match it, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). skipped: an email you can see that cannot be tested; skipped_reason says why. not_found: no such email, or not one the rule can read (or not settled, or awaiting sender authentication)."},"received_at":{"type":"string","format":"date-time"},"from_address":{"type":["string","null"]},"recipient":{"description":"The mailbox this copy of the email was received in, as GET \/v1\/emails\/{id} shows it. Given with received_at, from_address and subject, so absent for not_found.","type":"string"},"subject":{"type":["string","null"]},"attachment_names":{"description":"On matched and near_miss: the file names the email lists, in order, at most 10 (the names its questions are given; never what is in the files). Parts with no name are left out; absent when there are none.","type":"array","items":{"type":"string"}},"parts":{"description":"How each top-level part of the rule decided. Present for matched, near_miss and no_match.","type":"array","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0},"matched":{"type":"boolean"},"columns":{"type":"array","items":{"type":"string"}}},"required":["index","matched","columns"],"additionalProperties":false}},"missing_facts":{"description":"Facts the rule reads that this email's classification left out, so the rule did not match.","type":"array","items":{"type":"string"}},"values":{"description":"By column the rule names: the value as its conditions compared it (a list cut at 50), and a fact's confidence. A fact left out reads null.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}},{"type":"null"}]},"confidence":{"type":["number","null"]},"value_confidences":{"description":"A multi fact's chosen values, each with its own confidence: what a min_confidence on the column tests. Given in rule test results.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}}},"required":["value"],"additionalProperties":false}},"question_answers":{"description":"The answers to the rule's own questions, asked only when its conditions matched.","type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"answer":{"type":["boolean","null"]},"probability":{"type":["number","null"]},"error_class":{"type":"string"}},"required":["question","answer","probability"],"additionalProperties":false}},"question_answers_source":{"description":"Where question_answers came from, when there are any. stored: a saved rule version's answers about this email, kept once given (by evaluation or an earlier test) or recorded with its decision, reused because answers are sampled and asking again can differ. asked_now: asked by this test (always, for an unsaved tree). not_asked: no call could be made; error_class says why.","type":"string","enum":["stored","asked_now","not_asked"]},"skipped_reason":{"description":"Why a skipped email cannot be tested. unparsed: its content failed to parse, so it can never be classified. rejected: it was rejected and never delivered. content_discarded: its content has been discarded, or is being. Nothing is classified or asked about it and no stored answers are used, so it never counts as a pass. The recent window holds only mail that can be tested, apart from unparsed mail; an email you name is reported as skipped, whichever of these it is.","type":"string","enum":["unparsed","rejected","content_discarded"]},"unclassified_reason":{"description":"Why an unclassified email has no classification. pending: not reached before the test stopped, or not classified here (it arrived within the last 2 hours and normal classification has not finished with it, or classify_missing was false); run the test again later.","type":"string","enum":["pending","classifier_unavailable","classification_failed","not_classifiable"]}},"required":["email_id","outcome"],"additionalProperties":false},"description":"Newest first; ids not found (email_ids, then expected ones) follow in the order given."},"summary":{"type":"object","properties":{"decided":{"type":"integer","minimum":0,"description":"Emails decided on their conditions: matched, near_miss and no_match, less any missing a fact the rule reads."},"conditions_held":{"description":"Of decided, how many its conditions alone held for. Compile's breadth and conditions_breadth count this over recent mail in the scope sent to it, by default your own; here it is counted over the emails this test decided.","type":"integer","minimum":0},"conditions":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0},"text":{"type":"string"},"held":{"type":"integer","minimum":0},"failed":{"type":"integer","minimum":0},"only_failure":{"type":"integer","minimum":0}},"required":["index","text","held","failed","only_failure"],"additionalProperties":false},"description":"Each top-level condition (its parts index) in plain English, and how many decided emails it held on, failed on, and (except in an any rule) was the only failure on."},"questions":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0},"yes":{"type":"integer","minimum":0},"no":{"type":"integer","minimum":0},"unanswered":{"type":"integer","minimum":0}},"required":["index","yes","no","unanswered"],"additionalProperties":false},"description":"Each question's answers on the emails whose conditions held."},"warnings":{"type":"array","items":{"type":"string"},"description":"What to look at first, such as what stopped every email when nothing matched. On a test of the recent window, a grant rule whose conditions held for more than 20% of at least 20 emails checked (at least 90% if it has no questions), or for every one of fewer, gets a warning saying so, before those about what it decided."}},"required":["decided","conditions","questions","warnings"],"additionalProperties":false},"scope_warnings":{"description":"Names in scope_selection that match nothing this organization receives mail at (a typo, or an inbox not yet in use). Present only when there are some.","maxItems":150,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleScopeWarning"}},"expectations":{"description":"Present when expect was given, or for a saved rule with saved expectations or made-up emails.","type":"object","properties":{"passed":{"type":"boolean","description":"Every expectation passed. One undecided is no pass: unclassified or a question unanswered (test again later), skipped (it cannot be tested), or not found (no such email the rule can read)."},"counts":{"type":"object","properties":{"passed":{"type":"integer","minimum":0},"failed":{"type":"integer","minimum":0},"undecided":{"type":"integer","minimum":0}},"required":["passed","failed","undecided"],"additionalProperties":false},"results":{"type":"array","items":{"type":"object","properties":{"email_id":{"type":"string","format":"uuid"},"expected":{"type":"string","enum":["match","no_match"]},"outcome":{"type":"string","enum":["matched","near_miss","no_match","unclassified","skipped","not_found"]},"status":{"type":"string","enum":["passed","failed","undecided"]}},"required":["email_id","expected","outcome","status"],"additionalProperties":false},"description":"match first, then no_match, each in the order given (saved expectations by email id)."},"samples":{"description":"Made-up emails judged (expect.samples, or a saved rule's), in order; counts include them.","type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleSampleJudgment"}}},"required":["passed","counts","results"],"additionalProperties":false}},"required":["rule","window","counts","classification","questions","results"],"additionalProperties":false},"EmailRuleLookAlike":{"type":"object","properties":{"kind":{"type":"string","enum":["medical","payroll","personal_purchase","bank_tax","other"]},"expected":{"type":"string","enum":["match","no_match"],"description":"match: a near case the rule should match; no_match: one it should not."},"why":{"type":"string","description":"Why the rule should, or should not, match it."},"email":{"type":"object","properties":{"from":{"type":"object","properties":{"address":{"type":"string"},"name":{"type":"string"}},"required":["address"],"additionalProperties":false},"to":{"type":"array","items":{"type":"string"}},"cc":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"text":{"type":"string"},"attachment_types":{"type":"array","items":{"type":"string"}},"attachment_names":{"type":"array","items":{"type":"string"}},"automated":{"type":"boolean"},"from_mailing_list":{"type":"boolean"}},"required":["from","to","cc","subject","text","attachment_types","attachment_names","automated","from_mailing_list"],"additionalProperties":false,"description":"A made-up email, as test-sample takes it."},"outcome":{"type":"string","enum":["matched","near_miss","no_match","not_decided"]},"status":{"type":"string","enum":["passed","failed","undecided"],"description":"failed: the rule decided it against expected (shared one it should not, or missed one it should match). undecided: the rule reads a spam score, which it cannot carry, whatever it decided; or not matched on something it could not decide (not classified, a fact or answer missing, or not reached in time)."},"question_answers":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"answer":{"type":["boolean","null"]},"probability":{"type":["number","null"]},"error_class":{"type":"string"}},"required":["question","answer","probability"],"additionalProperties":false}}},"required":["kind","expected","why","email","outcome","status","question_answers"],"additionalProperties":false},"GenerateLookAlikesRequest":{"type":"object","properties":{"tree":{"description":"An unsaved rule tree, as POST \/email-rules\/compile returned it or written by hand. Give one of tree, rule_id and request_id.","$ref":"#\/components\/schemas\/EmailRuleTreeInput"},"english":{"description":"What the tree is meant to match; without it, the tree is described in words.","type":"string","minLength":1,"maxLength":2000},"evaluation_scope":{"type":"string","enum":["author","own_mailboxes","selected"],"description":"own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those mail:share_own lets you share). author: all you can read, inboxes added later included. selected: what scope_selection names."},"scope_selection":{"$ref":"#\/components\/schemas\/EmailRuleScopeSelection"},"rule_id":{"description":"A saved rule you can see, or one that covers your mailboxes (as you, sent to one of them; dashboard or Primitive CLI only): its current version and words.","type":"string","format":"uuid"},"request_id":{"description":"A rule request you can see: its rule as asked.","type":"string","format":"uuid"},"kind":{"description":"For an unsaved tree: do_not_share tests it as a rule that blocks sharing, so an email it cannot decide counts as matched. A saved rule or a request uses its own.","type":"string","enum":["grant","do_not_share"]},"count":{"description":"How many, 1 to 8; 6 by default.","type":"integer","minimum":1,"maximum":8},"kinds":{"description":"Which kinds, taken in turn, all expected no_match; other is a business near miss. Left out, a default mix.","minItems":1,"maxItems":5,"type":"array","items":{"type":"string","enum":["medical","payroll","personal_purchase","bank_tax","other"]}},"to":{"description":"The mailbox they arrive in; by default the first address (alphabetically) a selected scope names that a made-up email can be sent to, preferring one the author still reads; else the rule author's (a request's target's) sign-in address when you may see it, else yours.","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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"additionalProperties":false},"GenerateLookAlikesResult":{"type":"object","properties":{"rule":{"$ref":"#\/components\/schemas\/EmailRuleTested"},"look_alikes":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleLookAlike"}},"counts":{"type":"object","properties":{"passed":{"type":"integer","minimum":0},"failed":{"type":"integer","minimum":0},"undecided":{"type":"integer","minimum":0},"dropped":{"type":"integer","minimum":0,"description":"Asked for but not returned: not written, outside the sample bounds, or of a kind and expected not asked for."}},"required":["passed","failed","undecided","dropped"],"additionalProperties":false},"note":{"type":"string","description":"That the emails are made up and were never sent or stored."}},"required":["rule","look_alikes","counts","note"],"additionalProperties":false},"TestEmailRuleOnSampleRequest":{"type":"object","properties":{"tree":{"description":"An unsaved rule tree, as POST \/email-rules\/compile returned it or written by hand. Give one of tree, rule_id and request_id.","$ref":"#\/components\/schemas\/EmailRuleTreeInput"},"evaluation_scope":{"type":"string","enum":["author","own_mailboxes","selected"],"description":"own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those mail:share_own lets you share). author: all you can read, inboxes added later included. selected: what scope_selection names."},"scope_selection":{"$ref":"#\/components\/schemas\/EmailRuleScopeSelection"},"rule_id":{"description":"A saved rule you can see, or one that covers your mailboxes (as you; dashboard or Primitive CLI only). Its current version is tested.","type":"string","format":"uuid"},"request_id":{"description":"A pending rule request you can see: its rule as asked, tested as you over your own mailboxes (what accepting would share, for the member asked). Give one of tree, rule_id and request_id.","type":"string","format":"uuid"},"kind":{"description":"For an unsaved tree: do_not_share tests it as a rule that blocks sharing, so an email it cannot decide counts as matched. A saved rule or a request uses its own.","type":"string","enum":["grant","do_not_share"]},"email":{"type":"object","properties":{"from":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string","maxLength":200}},"required":["address"],"additionalProperties":false},"to":{"minItems":1,"maxItems":50,"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"description":"The recipients; the first is read as the mailbox it arrived in."},"cc":{"maxItems":50,"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"subject":{"type":"string","maxLength":998},"text":{"type":"string","minLength":1,"maxLength":50000,"description":"The body, as plain text."},"attachment_types":{"description":"Content types of attachments the email carries, such as application\/pdf. No attachment content.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":255}},"attachment_names":{"description":"File names of those attachments, which the classifier and the questions read. With attachment_types, the same number, paired in order.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[^\\u0000-\\u001f\\u007f]*$"}},"automated":{"description":"Whether the email was sent automatically, as its headers would show. Defaults to false.","type":"boolean"},"from_mailing_list":{"description":"Whether the email came from a mailing list, as its headers would show. Defaults to false.","type":"boolean"},"spam_score":{"description":"The spam score the inbound filter would give it. Without one, a rule that reads spam_score decides on no score and the answer lists it in unset_columns.","type":"number","minimum":-1000,"maximum":1000}},"required":["from","to","text"],"additionalProperties":false,"description":"A made-up received email. Nothing about it is stored."},"with_protection":{"description":"With true, a grant rule that matches is also judged against the active do-not-share rules over the first recipient's mailbox, which you must read in full and the rule must run over; see protection. Limited to 20 an hour per member.","type":"boolean"}},"required":["email"],"additionalProperties":false},"TestEmailRuleOnSampleResult":{"type":"object","properties":{"rule":{"$ref":"#\/components\/schemas\/EmailRuleTested"},"received_at":{"type":"string","format":"date-time","description":"When the made-up email counts as received (now): a rule on received_at is decided on it."},"outcome":{"type":"string","enum":["matched","near_miss","no_match"],"description":"near_miss: Not matched, but nearly: its conditions held and its questions did not match it, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false)."},"parts":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0},"matched":{"type":"boolean"},"columns":{"type":"array","items":{"type":"string"}}},"required":["index","matched","columns"],"additionalProperties":false},"description":"How each top-level part of the rule decided."},"missing_facts":{"description":"Facts the rule reads that this email's classification left out, so the rule did not match.","type":"array","items":{"type":"string"}},"values":{"description":"By column the rule names: the value as its conditions compared it (a list cut at 50), and a fact's confidence. A fact left out reads null.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}},{"type":"null"}]},"confidence":{"type":["number","null"]},"value_confidences":{"description":"A multi fact's chosen values, each with its own confidence: what a min_confidence on the column tests. Given in rule test results.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}}},"required":["value"],"additionalProperties":false}},"unset_columns":{"description":"Email columns the rule reads that the made-up email left unset (spam_score without one), decided as no value.","type":"array","items":{"type":"string"}},"question_answers":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"answer":{"type":["boolean","null"]},"probability":{"type":["number","null"]},"error_class":{"type":"string"}},"required":["question","answer","probability"],"additionalProperties":false}},"classification":{"type":"object","properties":{"status":{"type":"string","enum":["ok","classifier_unavailable","classification_failed"]},"error_class":{"type":"string"}},"required":["status"],"additionalProperties":false,"description":"Whether the email was classified; without a classification, a rule that reads a fact does not match."},"facts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"value":{},"confidence":{"type":"number"}},"required":["value","confidence"],"additionalProperties":false},"description":"What the classifier read from the email, by fact: its value and confidence."},"protection":{"description":"With with_protection: whether a match would be shared. Nothing is stored.","type":"object","properties":{"mailbox":{"type":"string","description":"The mailbox judged: the first recipient."},"outcome":{"type":"string","enum":["shared","withheld","not_matched"],"description":"not_matched: the rule did not match, so nothing would be shared and no do-not-share rule was judged."},"withheld_by":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"reason":{"type":"string","enum":["matched","undecided"]}},"required":["id","name","reason"],"additionalProperties":false},"description":"Do-not-share rules you can see that would keep it from being shared. undecided: left undecided as real mail would leave it, or not reached by this test (past 50 rules, 10 rules' questions, or the time limit); it is held back either way."},"withheld_by_hidden":{"type":"integer","minimum":0,"description":"Rules you cannot see that would."}},"required":["mailbox","outcome","withheld_by","withheld_by_hidden"],"additionalProperties":false}},"required":["rule","received_at","outcome","parts","question_answers","classification","facts"],"additionalProperties":false},"EmailRuleWritten":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"state":{"type":"string","enum":["draft","observe","active"]},"kind":{"description":"grant: what it matches can be shared. do_not_share: it blocks sharing what it matches.","type":"string","enum":["grant","do_not_share"]},"protection_note":{"description":"On a do_not_share rule only: it protects nothing yet (not active), a classifier fact narrows it, or its several questions are alternatives.","type":"string"},"evaluation_scope":{"description":"Whose mail it runs over, as stored (GET \/email-rules\/{id}\/scope: what that is now).","type":"string","enum":["author","own_mailboxes","selected"]},"scope_selection":{"description":"What a selected rule names; null for any other scope.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleScopeNames"},{"type":"null"}]},"current_version":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}]},"revision":{"description":"Moves on by one with every update. Send it as if_revision.","type":"integer","exclusiveMinimum":0},"author_user_id":{"type":"string","format":"uuid"},"author":{"description":"Who wrote it: author_user_id with their name and sign-in email.","$ref":"#\/components\/schemas\/EmailRuleAuthor"},"is_author":{"type":"boolean","description":"Whether you are its author, in your current membership. Only its author edits it."},"pending_objections":{"description":"To its author, on the dashboard or the Primitive CLI: objections to it waiting for their answer (GET \/email-rule-objections). A write's answer leaves it out when it could not be read in time: absent is not 0.","type":"integer","minimum":0},"objection_notice":{"description":"With pending_objections above 0: that members objected (each message says what they ask: to turn it off, narrow it or explain it), and how to answer.","type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"current":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleVersion"},{"type":"null"}],"description":"Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET \/email-rules lists rules without it."},"scope_warnings":{"description":"Names in scope_selection that match nothing this organization receives mail at (a typo, or an inbox not yet in use). Present only when there are some.","maxItems":150,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleScopeWarning"}},"do_not_share_note":{"description":"On a grant rule's create answer: that active do-not-share rules reaching an email run first: one that matches it keeps it from being shared through this rule, and one that cannot decide it holds it back while it is decided again for up to a day; it is shared through this rule only if none of them then matches it.","type":"string"},"expectations":{"description":"Its saved expectations, each list by email id, as stored: on every create, and on an update that set or removed them.","type":"object","properties":{"match":{"type":"array","items":{"type":"string","format":"uuid"}},"no_match":{"type":"array","items":{"type":"string","format":"uuid"}}},"required":["match","no_match"],"additionalProperties":false},"expectation_samples":{"description":"Its saved made-up emails, when this write set or added some.","type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleExpectationSample"}},"expectations_check":{"description":"After a write that saved a version or changed its expectations or made-up emails, if it has some: the emails by id judged on that version from stored classifications without asking its questions, and any made-up emails listed as undecided. Undecided: one that needs answers not yet kept for that version, is unclassified, skipped or not reached, or that its author does not read. Absent when it could not be judged (the rule changed again, its author cannot be resolved, your access shows headers only, it could not finish in time, or the check failed), and after a write that changed only made-up emails on a rule with emails by id. Test by rule_id for the rest. With if_expectations_pass, the judgment made before the save, which passed.","type":"object","properties":{"version":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}]},"passed":{"description":"False when any failed, true when every one passed, null when none failed but some are undecided (not a failure).","type":["boolean","null"]},"decided":{"description":"Whether every expectation was judged (undecided and undecided_samples are empty). Absent from a server that predates it.","type":"boolean"},"failed":{"type":"array","items":{"type":"string","format":"uuid"}},"undecided":{"type":"array","items":{"type":"string","format":"uuid"}},"failed_samples":{"description":"Made-up emails that failed, by index in the set judged (saved oldest first, then added; or as sent).","type":"array","items":{"type":"integer","minimum":0}},"undecided_samples":{"description":"Made-up emails not decided, by index. Only a write that saves a version or activates the rule, sent with if_expectations_pass or judged by default, judges them.","type":"array","items":{"type":"integer","minimum":0}},"failed_sample_details":{"description":"failed_samples with each one's label, from a write that judged them before saving (if_expectations_pass, or by default).","type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleSampleDetail"}},"undecided_sample_details":{"description":"undecided_samples with each one's label, as failed_sample_details.","type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleSampleDetail"}},"note":{"description":"On an update that saved a version that did not pass, or a do_not_share rule created active: that it was saved anyway, is current, and whether it is live.","type":"string"}},"required":["version","passed","failed","undecided"],"additionalProperties":false},"restored_from":{"description":"When this write saved an earlier version again (restore_version): which, and that the scope is not part of a version.","type":"object","properties":{"version":{"type":"integer","exclusiveMinimum":0},"note":{"type":"string"}},"required":["version","note"],"additionalProperties":false},"affects":{"description":"After an update that saved a version, or turned a grant rule active: API keys listing the rule and the webhooks they own, which read and receive what a grant rule grants from now on. shares_ended is always 0: a new version keeps the rule's share (share_notice says so). Saving a version does not by itself change the rule's state. Absent after any other write, and if not read.","type":"object","properties":{"keys":{"type":"integer","minimum":0},"endpoints":{"type":"integer","minimum":0},"shares_ended":{"type":"integer","minimum":0}},"required":["keys","endpoints","shares_ended"],"additionalProperties":false},"notice":{"description":"After a write that saved a version of an active rule keys, webhooks or a share read without checking it first: what it changes, and how to have the next one checked.","type":"string"},"share_notice":{"description":"After a write that saved a version of a rule with a live share: that the share goes on under it, given even when affects is not, with whom it is and the version they accepted when those can be read.","type":"string"}},"required":["id","name","state","current_version","author_user_id","is_author","created_at","updated_at","current"],"additionalProperties":false},"EmailRuleScopeNames":{"type":"object","properties":{"addresses":{"type":"array","items":{"type":"string"}},"domains":{"type":"array","items":{"type":"string"}}},"required":["addresses","domains"],"additionalProperties":false},"EmailRuleAuthor":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"name":{"description":"Their name; null unless you can list members or this is you, and when they have none set.","type":["string","null"]},"email":{"description":"Sign-in email; null unless you can list member emails or this is you.","type":["string","null"]}},"required":["user_id","name","email"],"additionalProperties":false},"EmailRuleVersion":{"type":"object","properties":{"version":{"type":"integer","exclusiveMinimum":0},"english":{"type":"string"},"tree":{"$ref":"#\/components\/schemas\/EmailRuleTree"},"explanation":{"type":"string"},"assumptions":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}},"compiled_by":{"type":"string","description":"Who produced the tree, as stated when saved (not verified), such as rule-compiler-v1 or member."},"vocabulary":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"sql":{"description":"Present only when include_sql was true.","type":"object","properties":{"expression":{"type":"string","description":"A parameterized boolean expression over one row per email."},"params":{"type":"array","items":{},"description":"The values bound to $1, $2 and so on."}},"required":["expression","params"],"additionalProperties":false}},"required":["version","english","tree","explanation","assumptions","warnings","compiled_by","vocabulary","created_at"],"additionalProperties":false},"EmailRuleExpectationSample":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"expected":{"type":"string","enum":["match","no_match"]},"label":{"type":["string","null"]},"email":{"$ref":"#\/components\/schemas\/EmailRuleSavedSampleEmail"},"created_at":{"type":"string","format":"date-time"}},"required":["id","expected","label","email","created_at"],"additionalProperties":false},"EmailRuleSampleDetail":{"type":"object","properties":{"index":{"type":"integer","minimum":0},"label":{"type":["string","null"]}},"required":["index","label"],"additionalProperties":false},"CreateEmailRuleRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"A short name for the rule."},"kind":{"description":"grant (the default): what the rule matches can be shared. do_not_share: the rule runs before every other rule, and while it is active no rule shares an email it matches; an email it cannot decide counts as matched. Set when the rule is created; it never changes.","type":"string","enum":["grant","do_not_share"]},"state":{"description":"observe (the default) records what the rule would match and grants or blocks nothing; draft is not evaluated; active only for a do_not_share rule.","type":"string","enum":["draft","observe","active"]},"evaluation_scope":{"type":"string","enum":["author","own_mailboxes","selected"],"description":"own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those mail:share_own lets you share). author: all you can read, inboxes added later included. selected: what scope_selection names."},"scope_selection":{"$ref":"#\/components\/schemas\/EmailRuleScopeSelection"},"expectations":{"type":"object","properties":{"match":{"maxItems":100,"type":"array","items":{"type":"string","format":"uuid"}},"no_match":{"maxItems":100,"type":"array","items":{"type":"string","format":"uuid"}}},"additionalProperties":false,"description":"Emails it must match and must not (at most 100): a test by rule_id judges them unless expect is given, and each version it saves is checked against them."},"expectation_samples":{"description":"Made-up emails it must match or not (at most 20).","minItems":1,"maxItems":20,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleExpectationSampleInput"}},"english":{"type":"string","minLength":1,"maxLength":2000,"description":"The plain-English description the rule was compiled from."},"tree":{"description":"The rule tree. Checked against the column contract on save.","$ref":"#\/components\/schemas\/EmailRuleTreeInput"},"explanation":{"description":"What the rule matches, in plain English. Left out, the server writes one from the tree.","type":"string","minLength":1,"maxLength":1000},"assumptions":{"description":"The compile response's assumptions. Stored as sent.","maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}},"warnings":{"description":"The compile response's warnings. Stored as sent, then any server note they lack on an address or domain the description names that the rule does not test, or on a grant rule that tests only who an email was sent to (on a rule request, every compiler note on the rule).","maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}},"compiled_by":{"description":"Who produced the tree (not verified): the compile response's compiled_by, or member (the default) for a tree you wrote or edited.","type":"string","minLength":1,"maxLength":64},"breadth":{"description":"Ignored (the compile answer's breadth).","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"conditions_breadth":{"description":"Ignored.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"breadth_scope":{"description":"Ignored (the compile answer's breadth_scope): set a rule's scope with evaluation_scope.","$ref":"#\/components\/schemas\/EmailRuleScopeUsed"},"scope_warnings":{"description":"Ignored (the compile answer's scope_warnings).","maxItems":150,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleScopeWarning"}}},"required":["name","english","tree"],"additionalProperties":false},"CreateEmailRuleResult":{"$ref":"#\/components\/schemas\/EmailRuleWritten"},"EmailRule":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"state":{"type":"string","enum":["draft","observe","active"]},"kind":{"description":"grant: what it matches can be shared. do_not_share: it blocks sharing what it matches.","type":"string","enum":["grant","do_not_share"]},"protection_note":{"description":"On a do_not_share rule only: it protects nothing yet (not active), a classifier fact narrows it, or its several questions are alternatives.","type":"string"},"evaluation_scope":{"description":"Whose mail it runs over, as stored (GET \/email-rules\/{id}\/scope: what that is now).","type":"string","enum":["author","own_mailboxes","selected"]},"scope_selection":{"description":"What a selected rule names; null for any other scope.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleScopeNames"},{"type":"null"}]},"current_version":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}]},"revision":{"description":"Moves on by one with every update. Send it as if_revision.","type":"integer","exclusiveMinimum":0},"author_user_id":{"type":"string","format":"uuid"},"author":{"description":"Who wrote it: author_user_id with their name and sign-in email.","$ref":"#\/components\/schemas\/EmailRuleAuthor"},"is_author":{"type":"boolean","description":"Whether you are its author, in your current membership. Only its author edits it."},"pending_objections":{"description":"To its author, on the dashboard or the Primitive CLI: objections to it waiting for their answer (GET \/email-rule-objections). A write's answer leaves it out when it could not be read in time: absent is not 0.","type":"integer","minimum":0},"objection_notice":{"description":"With pending_objections above 0: that members objected (each message says what they ask: to turn it off, narrow it or explain it), and how to answer.","type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","state","current_version","author_user_id","is_author","created_at","updated_at"],"additionalProperties":false},"ListEmailRulesResult":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRule"}},"CursorPageMeta":{"type":"object","description":"Paging for a list read a page at a time.","required":["limit","cursor"],"properties":{"limit":{"type":"integer","description":"The page size this answer used."},"cursor":{"type":["string","null"],"description":"Pass as cursor to read the next page; null on the last page."}}},"EmailRuleRead":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"state":{"type":"string","enum":["draft","observe","active"]},"kind":{"description":"grant: what it matches can be shared. do_not_share: it blocks sharing what it matches.","type":"string","enum":["grant","do_not_share"]},"protection_note":{"description":"On a do_not_share rule only: it protects nothing yet (not active), a classifier fact narrows it, or its several questions are alternatives.","type":"string"},"evaluation_scope":{"description":"Whose mail it runs over, as stored (GET \/email-rules\/{id}\/scope: what that is now).","type":"string","enum":["author","own_mailboxes","selected"]},"scope_selection":{"description":"What a selected rule names; null for any other scope.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleScopeNames"},{"type":"null"}]},"current_version":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}]},"revision":{"description":"Moves on by one with every update. Send it as if_revision.","type":"integer","exclusiveMinimum":0},"author_user_id":{"type":"string","format":"uuid"},"author":{"description":"Who wrote it: author_user_id with their name and sign-in email.","$ref":"#\/components\/schemas\/EmailRuleAuthor"},"is_author":{"type":"boolean","description":"Whether you are its author, in your current membership. Only its author edits it."},"pending_objections":{"description":"To its author, on the dashboard or the Primitive CLI: objections to it waiting for their answer (GET \/email-rule-objections). A write's answer leaves it out when it could not be read in time: absent is not 0.","type":"integer","minimum":0},"objection_notice":{"description":"With pending_objections above 0: that members objected (each message says what they ask: to turn it off, narrow it or explain it), and how to answer.","type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"current":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleVersion"},{"type":"null"}],"description":"Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET \/email-rules lists rules without it."},"expectations":{"description":"Its saved expectations, each list by email id.","type":"object","properties":{"match":{"type":"array","items":{"type":"string","format":"uuid"}},"no_match":{"type":"array","items":{"type":"string","format":"uuid"}}},"required":["match","no_match"],"additionalProperties":false},"expectation_samples":{"description":"Its saved made-up emails, oldest first.","type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleExpectationSample"}}},"required":["id","name","state","current_version","author_user_id","is_author","created_at","updated_at","current"],"additionalProperties":false},"GetEmailRuleResult":{"$ref":"#\/components\/schemas\/EmailRuleRead"},"UpdateEmailRuleRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"A short name for the rule."},"state":{"description":"draft disables the rule; observe records decisions; active also grants what it matches, or for a do_not_share rule keeps it from being shared. Keys read and receive what a rule granted only while it is active: leaving active pauses that, and returning to active restores reads and the delivery of queued mail not yet skipped.","type":"string","enum":["draft","observe","active"]},"english":{"type":"string","minLength":1,"maxLength":2000,"description":"The plain-English description the rule was compiled from."},"tree":{"description":"The rule tree. Checked against the column contract on save.","$ref":"#\/components\/schemas\/EmailRuleTreeInput"},"explanation":{"description":"What the rule matches, in plain English. Left out, the server writes one from the tree.","type":"string","minLength":1,"maxLength":1000},"assumptions":{"description":"The compile response's assumptions. Stored as sent.","maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}},"warnings":{"description":"The compile response's warnings. Stored as sent, then any server note they lack on an address or domain the description names that the rule does not test, or on a grant rule that tests only who an email was sent to (on a rule request, every compiler note on the rule).","maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}},"compiled_by":{"description":"Who produced the tree (not verified): the compile response's compiled_by, or member (the default) for a tree you wrote or edited.","type":"string","minLength":1,"maxLength":64},"breadth":{"description":"Ignored (the compile answer's breadth).","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"conditions_breadth":{"description":"Ignored.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"breadth_scope":{"description":"Ignored (the compile answer's breadth_scope): set a rule's scope with evaluation_scope.","$ref":"#\/components\/schemas\/EmailRuleScopeUsed"},"scope_warnings":{"description":"Ignored (the compile answer's scope_warnings).","maxItems":150,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleScopeWarning"}},"kind":{"description":"Must be the rule's own (400 kind_mismatch).","type":"string","enum":["grant","do_not_share"]},"evaluation_scope":{"type":"string","enum":["author","own_mailboxes","selected"],"description":"own_mailboxes: mail to your own addresses and agents (for a rule that shares, those mail:share_own lets you share). author: all you can read, inboxes added later included. selected: what scope_selection names. Left out, the scope stays. A change saves a new version with the same conditions, and is refused while the rule is shared."},"scope_selection":{"$ref":"#\/components\/schemas\/EmailRuleScopeSelection"},"if_version":{"description":"The current_version you read: if it has moved, nothing is saved (409 email_rule_conflict, version_moved). It guards versions only; if_revision guards every update.","type":"integer","minimum":1},"expectations":{"description":"Replaces its saved expectations (author only); null removes them.","anyOf":[{"type":"object","properties":{"match":{"maxItems":100,"type":"array","items":{"type":"string","format":"uuid"}},"no_match":{"maxItems":100,"type":"array","items":{"type":"string","format":"uuid"}}},"additionalProperties":false,"description":"Emails it must match and must not (at most 100): a test by rule_id judges them unless expect is given, and each version it saves is checked against them."},{"type":"null"}]},"expectation_samples":{"description":"Replaces its saved made-up emails (author only); null or [] removes them.","anyOf":[{"maxItems":20,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleExpectationSampleInput"}},{"type":"null"}]},"add_expectation_samples":{"description":"Adds made-up emails to those saved (author only), up to 20 in all.","minItems":1,"maxItems":20,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleExpectationSampleInput"}},"if_revision":{"description":"The revision you read: if the rule was updated since, nothing is saved (409 email_rule_conflict, revision_moved).","type":"integer","minimum":1,"maximum":2147483647},"if_expectations_pass":{"description":"true: judge the rule as this write leaves it (new tree, restored version, new scope, or the version it activates) against the expectations it leaves, as POST \/email-rules\/test would, before saving; it can take as long as a test. Failed: 409 expectations_failed; on emails the rule cannot read: 409 expectations_unreadable; undecided: 409 expectations_undecided (each with details.expectations_check); none to judge: 400 no_expectations. Refused writes save nothing; a pass saves unless the rule moved meanwhile (409 email_rule_conflict). Only writes that save a version or activate the rule are judged. Omitted: judged as with true when the write saves a version or changes the scope of an active rule that API keys, webhooks or a share read and that has expectations. false: never judged first.","type":"boolean"},"restore_version":{"description":"Save version n again as the next version (author only): its english, tree, explanation, assumptions, warnings and compiled_by. A version carries no scope: the rule keeps its own unless evaluation_scope is sent. 404 version_not_found; 400 already_current or version_not_renderable. Not with a version's own fields.","type":"integer","minimum":1,"maximum":2147483647}},"additionalProperties":false},"UpdateEmailRuleResult":{"$ref":"#\/components\/schemas\/EmailRuleWritten"},"DeleteEmailRuleResult":{"type":"object","properties":{"deleted":{"type":"boolean"},"ended_shares":{"description":"The shares of this rule that the delete ended (end_reason rule_deleted). Each keeps its record.","type":"array","items":{"type":"string","format":"uuid"}},"keys":{"description":"The API keys, not revoked, that still list the rule and that you may read (GET \/v1\/keys). They now read nothing through it; edit or revoke them. Left out, with other_keys, when the keys could not be read.","type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"key_prefix":{"type":"string"}},"required":["id","name","key_prefix"],"additionalProperties":false}},"other_keys":{"description":"How many more keys, not revoked, list the rule that you may not read. They are not named.","type":"integer"},"share_notice":{"description":"When the delete ended a share: that it did, with whom it was with and the version they accepted when those can be read.","type":"string"}},"required":["deleted"],"additionalProperties":false},"EmailRuleWithheldBy":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"reason":{"type":"string","enum":["matched","undecided","outside_scope_stale"],"description":"undecided: it had not decided the email, which blocks too. outside_scope_stale: cleared as outside its scope, and its author now reads the email."}},"required":["id","name","reason"],"additionalProperties":false},"ListEmailRuleDecisionsResult":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email_id":{"type":"string","format":"uuid"},"rule_version":{"type":"integer","exclusiveMinimum":0},"classification_id":{"type":"string","format":"uuid"},"matched":{"type":"boolean"},"near_miss":{"type":"boolean","description":"Not matched, but nearly: its conditions held and its questions did not match it, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). Decisions recorded under the earlier definition counted an exclusion too."},"parts":{"type":"array","items":{},"description":"How each top-level part of the rule decided."},"question_answers":{"type":"array","items":{},"description":"The answers to the rule's own questions, when they were asked."},"detail_withheld":{"description":"Present, as true, on a decision about an email whose content cannot be read (withheld, failed to parse, discarded, or still awaiting sender authentication): matched still says what the rule decided, but how it got there was read from that content, so parts and question_answers are empty and near_miss is false. Discarding an email's content normally removes its decisions. Absent otherwise.","type":"boolean","const":true},"withheld_by":{"description":"Set when do-not-share rules kept this match from being shared, so no key or webhook got it through the rule: those you can see, and why.","type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleWithheldBy"}},"withheld_by_hidden":{"type":"integer","minimum":0,"description":"With withheld_by: how many more kept it back that you cannot see, or that were deleted since. Those rules are not shown; an admin can see them."},"created_at":{"type":"string","format":"date-time"},"from_address":{"description":"The decided email's sender address, as GET \/v1\/emails\/{id} shows it.","type":["string","null"]},"subject":{"description":"The decided email's subject, as GET \/v1\/emails\/{id} shows it.","type":["string","null"]},"received_at":{"description":"When the decided email arrived. Null, with the other two, when you are not shown the email (it awaits sender authentication).","anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"required":["id","email_id","rule_version","classification_id","matched","near_miss","parts","question_answers","created_at"],"additionalProperties":false}},"EmailRuleCoveringAuthor":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"name":{"description":"Their name; null when they have none set.","type":["string","null"]},"email":{"description":"Sign-in email; null unless you can list member emails or this is you.","type":["string","null"]}},"required":["user_id","name","email"],"additionalProperties":false},"EmailRuleReceivers":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","properties":{"owner":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"member"},"user_id":{"type":"string","description":"The member's user id.","format":"uuid"},"name":{"description":"Their name; null when they have none set.","type":["string","null"]}},"required":["kind","user_id","name"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"organization","description":"Held by no member: an organization key, or one whose holder has left."}},"required":["kind"],"additionalProperties":false}]},"count":{"type":"integer","exclusiveMinimum":0},"reads_in_full":{"description":"On a rule over your mail: how many of these keys also list one of your own mailboxes, or its domain, so they read your mail there beyond what this rule matches. It counts what they can read, not whether any has read an email.","type":"integer","minimum":0},"newest_at":{"description":"When the newest of these was made, to the minute: a key, a webhook or the share, or the rule (the share, for what reaches it only through the share) if that was later. Not when a key was given access to this rule, or anything turned back on; a rotated key shows as newly made, and one key on two rules can show the same minute under both.","type":"string","format":"date-time"}},"required":["owner","count"],"additionalProperties":false},"description":"API keys that work now (not revoked, expired or suspended) and hold mail:read, whose access lists the rule or its live share. Owner: who holds each, or the organization."},"webhooks":{"type":"array","items":{"type":"object","properties":{"owner":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"member"},"user_id":{"type":"string","description":"The member's user id.","format":"uuid"},"name":{"description":"Their name; null when they have none set.","type":["string","null"]}},"required":["kind","user_id","name"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"organization","description":"Held by no member: an organization key, or one whose holder has left."}},"required":["kind"],"additionalProperties":false}]},"count":{"type":"integer","exclusiveMinimum":0},"hosts":{"description":"Distinct sites they deliver to (the domain a host is registered under, an app's own name on a known app platform, or a function URL service in its region), sorted, at most 10; \"unknown host\" (last) for any that cannot be named.","type":"array","items":{"type":"string"}},"hosts_not_listed":{"description":"Hosts past those listed.","type":"integer","minimum":0},"newest_at":{"description":"When the newest of these was made, to the minute: a key, a webhook or the share, or the rule (the share, for what reaches it only through the share) if that was later. Not when a key was given access to this rule, or anything turned back on; a rotated key shows as newly made, and one key on two rules can show the same minute under both.","type":"string","format":"date-time"}},"required":["owner","count"],"additionalProperties":false},"description":"Webhooks taking received mail, bounces or reports that are sent what it matches: those of the key delivery acts as, when it lists the rule or its live share (owner: its holder, or the organization), and those of the member it is shared with (owner: that member)."},"shares":{"type":"array","items":{"type":"object","properties":{"beneficiary":{"type":"object","properties":{"kind":{"type":"string","const":"member"},"user_id":{"type":"string","description":"The member's user id.","format":"uuid"},"name":{"description":"Their name; null when they have none set.","type":["string","null"]}},"required":["kind","user_id","name"],"additionalProperties":false},"count":{"type":"integer","exclusiveMinimum":0},"newest_at":{"description":"When the newest of these was made, to the minute: a key, a webhook or the share, or the rule (the share, for what reaches it only through the share) if that was later. Not when a key was given access to this rule, or anything turned back on; a rotated key shows as newly made, and one key on two rules can show the same minute under both.","type":"string","format":"date-time"}},"required":["beneficiary","count"],"additionalProperties":false},"description":"The rule's live share (at most one), by its member, if they can read it and it is among their newest 20 shares."}},"required":["keys","webhooks","shares"],"additionalProperties":false,"description":"Who receives what the rule shares, by person and count. As receivers, empty unless it is an active grant rule."},"ListEmailRuleResultsResult":{"type":"object","properties":{"email":{"description":"The email these results are about, so a saved answer says which one it was.","type":"object","properties":{"id":{"type":"string","format":"uuid"},"from_address":{"description":"The sender address, as GET \/v1\/emails\/{id} shows it.","type":["string","null"]},"recipient":{"type":"string","description":"The mailbox this copy of the email was received in, as GET \/v1\/emails\/{id} shows it."},"subject":{"type":["string","null"]},"received_at":{"type":"string","format":"date-time"}},"required":["id","from_address","recipient","subject","received_at"],"additionalProperties":false},"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"state":{"type":"string","enum":["draft","observe","active"]},"kind":{"description":"grant: the rule shares what it matches. do_not_share: the rule blocks sharing of what it matches. While it is active, no rule shares an email it has matched from then on; anything shared before, including while it was in observe, stays shared.","type":"string","enum":["grant","do_not_share"]},"decision":{"anyOf":[{"type":"object","properties":{"rule_version":{"type":"integer","exclusiveMinimum":0,"description":"The version of the rule that decided."},"matched":{"type":"boolean"},"near_miss":{"description":"Not matched, but nearly: its conditions held and its questions did not match it, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). False when detail_withheld is true. Decisions recorded under the earlier definition counted an exclusion too.","type":"boolean"},"parts":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0},"matched":{"type":"boolean"},"columns":{"type":"array","items":{"type":"string"}}},"required":["index","matched","columns"],"additionalProperties":false},"description":"How each top-level part of the rule decided. Empty when a fact the rule reads was missing, so it was not evaluated, or when detail_withheld is true."},"question_answers":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"answer":{"type":["boolean","null"]},"probability":{"type":["number","null"]},"error_class":{"type":"string"}},"required":["question","answer","probability"],"additionalProperties":false},"description":"The answers to the rule's own questions, asked only when its conditions matched."},"tree":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleTree"},{"type":"null"}],"description":"The conditions and questions of the version that decided, so the result reads in that version's words."},"detail_withheld":{"description":"Present, as true, on a result for an email whose content cannot be read (withheld, failed to parse or discarded): matched still says what the rule decided, but how it got there was read from that content, so parts and question_answers are empty and near_miss is false. Absent otherwise.","type":"boolean","const":true},"withheld_by":{"description":"Set when do-not-share rules kept this match from being shared, so no key or webhook got it through the rule: those you can see, and why.","type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleWithheldBy"}},"withheld_by_hidden":{"type":"integer","minimum":0,"description":"With withheld_by: how many more kept it back that you cannot see, or that were deleted since. Those rules are not shown; an admin can see them."},"created_at":{"type":"string","format":"date-time"}},"required":["rule_version","matched","parts","question_answers","tree","created_at"],"additionalProperties":false},{"type":"null"}],"description":"What the rule most recently decided about this email; null when it has not run on it."},"not_run_reason":{"anyOf":[{"type":"string","enum":["draft","created_after_email","outside_scope"]},{"type":"null"}],"description":"What is known about a rule with no result on this email. draft: the rule is a draft now. created_after_email: the rule was created after the email arrived. outside_scope: the mailbox this email is in is outside what its scope names (its author's own mailboxes, its selection, or all its author reads now), so it does not decide on it. Null when it has a result, or when nothing is known (it may not have decided yet)."},"author_left":{"description":"On a do-not-share rule whose author is no longer a member of the organization: it blocks nothing now, whatever it decided. Absent otherwise.","type":"boolean","const":true},"read_only":{"description":"Another member's rule that decided on your own mail; dashboard and CLI only. name is null unless known to cover you.","type":"boolean","const":true},"author":{"description":"With read_only: who wrote it.","$ref":"#\/components\/schemas\/EmailRuleCoveringAuthor"},"receivers":{"description":"With read_only: who receives what it shares; null unless it covers you now.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleReceivers"},{"type":"null"}]}},"required":["id","name","state","decision","not_run_reason"],"additionalProperties":false},"description":"Rules you can see, and read_only ones: matched first, then not matched, then not run; newest first within each."},"hidden":{"type":"integer","minimum":0,"description":"Rules that decided on this email that you cannot see."},"not_listed":{"description":"Rules you can see past the 50 listed.","type":"integer","minimum":0},"classification":{"description":"What the classifier read from this email (one classification per vocabulary); null when it has none (not classified, or never will be), or while the email's content is withheld, discarded or failed to parse.","anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"vocabulary":{"type":"string"},"classified_at":{"type":"string","format":"date-time"},"facts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}},{"type":"null"}]},"confidence":{"type":["number","null"]},"value_confidences":{"description":"A multi fact's chosen values, each with its own confidence: what a min_confidence on the column tests. Given in rule test results.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}}},"required":["value"],"additionalProperties":false},"description":"Every fact column, as rule conditions read it: the value (a multi's options lowercased; null if the classifier left it out or wrote another shape) and its confidence. It can differ from GET \/emails\/{id} understanding, which shows the facts as written."}},"required":["id","vocabulary","classified_at","facts"],"additionalProperties":false},{"type":"null"}]}},"required":["rules","hidden"],"additionalProperties":false},"ListEmailRuleVersionsResult":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleVersion"}},"GetEmailRuleScopeResult":{"type":"object","properties":{"evaluation_scope":{"type":"string","enum":["author","own_mailboxes","selected"],"description":"own_mailboxes: mail to its author's own addresses and agents (for a rule that shares, those mail:share_own lets them share). author: all its author can read, inboxes added later included. selected: what scope_selection names."},"scope_selection":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleScopeNames"},{"type":"null"}]},"locked":{"type":"boolean","description":"Shared, so its scope cannot change."},"author_reach":{"anyOf":[{"type":"string","enum":["organization","limited","own"]},{"type":"null"}],"description":"How much its author can read now; null when it cannot run as them."},"no_longer_readable":{"description":"What scope_selection names that its author no longer reads in full.","$ref":"#\/components\/schemas\/EmailRuleScopeNames"},"still_read":{"description":"Inboxes its author still reads at the domains in no_longer_readable; the rule still covers them.","type":"array","items":{"type":"string"}},"stopped_reason":{"description":"Why it cannot run now (a reason as on POST \/email-rules\/test), or null.","type":["string","null"]},"scope_warnings":{"description":"Names in scope_selection that match nothing this organization receives mail at (a typo, or an inbox not yet in use). Present only when there are some.","maxItems":150,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleScopeWarning"}},"reads_nothing":{"description":"It can decide no email: it cannot run as its author, or everything its scope names is at none of the organization's domains or no longer read. An inbox not in use yet, or a domain not active now, does not count.","type":"boolean"}},"required":["evaluation_scope","scope_selection","locked","author_reach","no_longer_readable","stopped_reason"],"additionalProperties":false},"ListEmailRuleScopeOptionsResult":{"type":"object","properties":{"reach":{"type":"string","enum":["organization","limited","own"],"description":"organization: all of the organization's mail. own: only your own. limited: some inboxes or domains."},"own_mail":{"type":"boolean","description":"Whether a rule over only your own mail would run for you."},"own_mail_to_block":{"description":"Whether a do_not_share rule over only your own mail would run for you: it covers every own mailbox you read, including ones you could not share.","type":"boolean"},"addresses":{"type":"array","items":{"type":"string"},"description":"Inboxes you can read, matching q. For someone who reads the whole organization, the inboxes known as someone's: member addresses, members' verified work emails at the organization's domains, agents' addresses and inboxes with shared access. Otherwise the inboxes your access lists (search with q for those past the first page). See may_name_unlisted for what else you may name."},"domains":{"type":"array","items":{"type":"string"},"description":"Domains you can read, matching q."},"more":{"type":"boolean","description":"More match than are listed."},"may_name_unlisted":{"description":"Whether you may also name an inbox this list never shows, however it is searched (more only says whether a search would show further ones): true when you read the whole organization (any address, such as an inbox at its domains nobody has claimed yet) or read a domain (any address there your access does not leave out). Name it in scope_selection.addresses as you would a listed one. Absent from a server that predates it.","type":"boolean"}},"required":["reach","own_mail","addresses","domains","more"],"additionalProperties":false},"EmailRuleRequestProposer":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"email":{"description":"Sign-in email; null unless you can list member emails, this is you, or they asked you.","type":["string","null"]},"active":{"type":"boolean","description":"Still a member under the membership recorded (a request or share with someone who left no longer works)."}},"required":["user_id","name","email","active"],"additionalProperties":false},"EmailRuleRequestTarget":{"type":"object","properties":{"user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Null unless you can list members or this is you."},"name":{"description":"Null unless you can list members or this is you.","type":["string","null"]},"email":{"description":"Sign-in email; null unless you can list member emails or this is you.","type":["string","null"]},"active":{"type":"boolean","description":"Still a member under the membership recorded (a request or share with someone who left no longer works)."}},"required":["user_id","name","email","active"],"additionalProperties":false},"CreateEmailRuleRequestRequest":{"type":"object","properties":{"target_user_id":{"description":"The member asked for the rule. Give target_user_id or target_email, not both.","type":"string","format":"uuid"},"target_email":{"description":"The member asked for the rule, by their member mailbox address, or by their verified work email if you can list members. Give target_user_id or target_email, not both. Looking members up by address is limited to 30 an hour, and unless you can list members the answer does not say who it was.","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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string","minLength":1,"maxLength":200,"description":"A short name for the rule."},"message":{"description":"A short note to the member asked about why you are asking, up to 500 characters; null or absent for none. They see it with the request; it cannot be changed later.","anyOf":[{"type":"string","minLength":1,"maxLength":500},{"type":"null"}]},"english":{"type":"string","minLength":1,"maxLength":2000,"description":"The plain-English description the rule was compiled from."},"tree":{"description":"The rule tree. Checked against the column contract on save.","$ref":"#\/components\/schemas\/EmailRuleTreeInput"},"explanation":{"description":"What the rule matches, in plain English. Left out, the server writes one from the tree.","type":"string","minLength":1,"maxLength":1000},"assumptions":{"description":"The compile response's assumptions. Stored as sent.","maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}},"warnings":{"description":"The compile response's warnings. Stored as sent, then any server note they lack on an address or domain the description names that the rule does not test, or on a grant rule that tests only who an email was sent to (on a rule request, every compiler note on the rule).","maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}},"compiled_by":{"description":"Who produced the tree (not verified): the compile response's compiled_by, or member (the default) for a tree you wrote or edited.","type":"string","minLength":1,"maxLength":64},"breadth":{"description":"Ignored (the compile answer's breadth).","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"conditions_breadth":{"description":"Ignored.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"breadth_scope":{"description":"Ignored (the compile answer's breadth_scope): set a rule's scope with evaluation_scope.","$ref":"#\/components\/schemas\/EmailRuleScopeUsed"},"scope_warnings":{"description":"Ignored (the compile answer's scope_warnings).","maxItems":150,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleScopeWarning"}},"kind":{"description":"Only grant.","type":"string","const":"grant"}},"required":["name","english","tree"],"additionalProperties":false},"CreateEmailRuleRequestResult":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","accepted","declined","withdrawn"]},"name":{"type":"string"},"english":{"type":"string"},"tree":{"$ref":"#\/components\/schemas\/EmailRuleTree"},"explanation":{"type":"string"},"assumptions":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"},"description":"As the member who asked sent them, normally from their compile, then every compiler note on the rule's tree and words they lack. The compiler's note that the rule matches nearly everything, in its own wording, counted that member's recent mail: anyone else reads it naming them."},"server_explanation":{"description":"What the rule as asked actually does: Primitive's own reading of the request's tree, never the words of the member who asked or of a model. Null when the tree no longer reads as one.","type":["string","null"]},"server_notes":{"description":"Primitive's own notes on the rule as asked, worked out from its tree and description each time the request is read, and to the member asked while pending, how to test it on look-alikes and that counts the asker quotes came from their own mail. Unlike warnings, never written by the member who asked, though a note can quote their words or tree values.","type":"array","items":{"type":"string"}},"compiled_by":{"type":"string"},"vocabulary":{"type":"string"},"proposed_via":{"type":"string","enum":["session","oauth"],"description":"session: the proposer's own session; oauth: an OAuth or CLI token acting for them."},"created_at":{"type":"string","format":"date-time"},"decided_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"decided_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"proposer":{"$ref":"#\/components\/schemas\/EmailRuleRequestProposer"},"target":{"$ref":"#\/components\/schemas\/EmailRuleRequestTarget"},"target_address_typed":{"description":"The address the proposer typed to name the target; null for anyone else, and when named by user id.","type":["string","null"]},"is_proposer":{"type":"boolean"},"is_target":{"type":"boolean"},"share_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"The share accepting created, while it exists."},"message":{"description":"What the member asking wrote about why; null when they wrote nothing.","type":["string","null"]},"decline_reason":{"description":"Why the member asked declined it; null unless they gave a reason when declining.","type":["string","null"]},"edited_on_accept":{"description":"Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.","type":["boolean","null"]},"notice":{"description":"For the member asked, while pending: who asked (as proposer names them), what the rule actually does (server_explanation), Primitive's warnings, what accepting would share, then where to see what becomes of one email (rule-results and rule_deliveries). For the member who asked, once accepted with changes: that the request is not what is shared. Else null.","type":["string","null"]},"target_can_accept":{"description":"Whether the member asked can accept it now under their role. Null unless you can list members and their roles (members:read), or when it could not be read.","type":["boolean","null"]},"target_warning":{"description":"Why the member asked cannot accept it yet, naming the permission their role lacks. The request is still sent. Null when they can, or when target_can_accept is null.","type":["string","null"]},"existing":{"description":"Present, as true, when the create carried an Idempotency-Key and you already had a pending request to this member asking exactly this (name, english, tree, explanation and message, naming the member the same way): that request is returned (200), and no second one is made or sent.","type":"boolean","const":true}},"required":["id","status","name","english","tree","explanation","assumptions","warnings","compiled_by","vocabulary","proposed_via","created_at","decided_at","decided_by_user_id","proposer","target","target_address_typed","is_proposer","is_target","share_id"],"additionalProperties":false},"CheckEmailRuleRequestTargetRequest":{"type":"object","properties":{"target_user_id":{"description":"The member to ask. Give target_user_id or target_email, not both.","type":"string","format":"uuid"},"target_email":{"description":"The member to ask, by their member mailbox address or verified work email. Give target_user_id or target_email, not both. Counts toward the same 30 lookups by address an hour as asking.","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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"additionalProperties":false},"CheckEmailRuleRequestTargetResult":{"type":"object","properties":{"target":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"email":{"type":["string","null"]}},"required":["user_id","name","email"],"additionalProperties":false},"can_accept":{"type":"boolean","description":"Whether they can accept a rule request now under their role."},"missing_permission":{"description":"The permission their role lacks for accepting, when can_accept is false.","type":["string","null"]},"warning":{"description":"The same in words, with who can fix it.","type":["string","null"]}},"required":["target","can_accept","missing_permission","warning"],"additionalProperties":false},"EmailRuleRequest":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","accepted","declined","withdrawn"]},"name":{"type":"string"},"english":{"type":"string"},"tree":{"$ref":"#\/components\/schemas\/EmailRuleTree"},"explanation":{"type":"string"},"assumptions":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"},"description":"As the member who asked sent them, normally from their compile, then every compiler note on the rule's tree and words they lack. The compiler's note that the rule matches nearly everything, in its own wording, counted that member's recent mail: anyone else reads it naming them."},"server_explanation":{"description":"What the rule as asked actually does: Primitive's own reading of the request's tree, never the words of the member who asked or of a model. Null when the tree no longer reads as one.","type":["string","null"]},"server_notes":{"description":"Primitive's own notes on the rule as asked, worked out from its tree and description each time the request is read, and to the member asked while pending, how to test it on look-alikes and that counts the asker quotes came from their own mail. Unlike warnings, never written by the member who asked, though a note can quote their words or tree values.","type":"array","items":{"type":"string"}},"compiled_by":{"type":"string"},"vocabulary":{"type":"string"},"proposed_via":{"type":"string","enum":["session","oauth"],"description":"session: the proposer's own session; oauth: an OAuth or CLI token acting for them."},"created_at":{"type":"string","format":"date-time"},"decided_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"decided_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"proposer":{"$ref":"#\/components\/schemas\/EmailRuleRequestProposer"},"target":{"$ref":"#\/components\/schemas\/EmailRuleRequestTarget"},"target_address_typed":{"description":"The address the proposer typed to name the target; null for anyone else, and when named by user id.","type":["string","null"]},"is_proposer":{"type":"boolean"},"is_target":{"type":"boolean"},"share_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"The share accepting created, while it exists."},"message":{"description":"What the member asking wrote about why; null when they wrote nothing.","type":["string","null"]},"decline_reason":{"description":"Why the member asked declined it; null unless they gave a reason when declining.","type":["string","null"]},"edited_on_accept":{"description":"Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.","type":["boolean","null"]},"notice":{"description":"For the member asked, while pending: who asked (as proposer names them), what the rule actually does (server_explanation), Primitive's warnings, what accepting would share, then where to see what becomes of one email (rule-results and rule_deliveries). For the member who asked, once accepted with changes: that the request is not what is shared. Else null.","type":["string","null"]}},"required":["id","status","name","english","tree","explanation","assumptions","warnings","compiled_by","vocabulary","proposed_via","created_at","decided_at","decided_by_user_id","proposer","target","target_address_typed","is_proposer","is_target","share_id"],"additionalProperties":false},"ListEmailRuleRequestsResult":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleRequest"}},"GetEmailRuleRequestResult":{"$ref":"#\/components\/schemas\/EmailRuleRequest"},"DeclineEmailRuleRequestRequest":{"type":"object","properties":{"reason":{"description":"Why you are declining, up to 500 characters; null or absent for none. The member who asked sees it with the request.","anyOf":[{"type":"string","minLength":1,"maxLength":500},{"type":"null"}]}},"additionalProperties":false},"DeclineEmailRuleRequestResult":{"$ref":"#\/components\/schemas\/EmailRuleRequest"},"WithdrawEmailRuleRequestResult":{"$ref":"#\/components\/schemas\/EmailRuleRequest"},"DeleteEmailRuleRequestResult":{"type":"object","properties":{"deleted":{"type":"boolean"}},"required":["deleted"],"additionalProperties":false},"EmailRuleWithVersion":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"state":{"type":"string","enum":["draft","observe","active"]},"kind":{"description":"grant: what it matches can be shared. do_not_share: it blocks sharing what it matches.","type":"string","enum":["grant","do_not_share"]},"protection_note":{"description":"On a do_not_share rule only: it protects nothing yet (not active), a classifier fact narrows it, or its several questions are alternatives.","type":"string"},"evaluation_scope":{"description":"Whose mail it runs over, as stored (GET \/email-rules\/{id}\/scope: what that is now).","type":"string","enum":["author","own_mailboxes","selected"]},"scope_selection":{"description":"What a selected rule names; null for any other scope.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleScopeNames"},{"type":"null"}]},"current_version":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}]},"revision":{"description":"Moves on by one with every update. Send it as if_revision.","type":"integer","exclusiveMinimum":0},"author_user_id":{"type":"string","format":"uuid"},"author":{"description":"Who wrote it: author_user_id with their name and sign-in email.","$ref":"#\/components\/schemas\/EmailRuleAuthor"},"is_author":{"type":"boolean","description":"Whether you are its author, in your current membership. Only its author edits it."},"pending_objections":{"description":"To its author, on the dashboard or the Primitive CLI: objections to it waiting for their answer (GET \/email-rule-objections). A write's answer leaves it out when it could not be read in time: absent is not 0.","type":"integer","minimum":0},"objection_notice":{"description":"With pending_objections above 0: that members objected (each message says what they ask: to turn it off, narrow it or explain it), and how to answer.","type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"current":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleVersion"},{"type":"null"}],"description":"Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET \/email-rules lists rules without it."}},"required":["id","name","state","current_version","author_user_id","is_author","created_at","updated_at","current"],"additionalProperties":false},"EmailRuleShare":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"request_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"end_reason":{"anyOf":[{"type":"string","enum":["stopped_by_granter","removed_by_beneficiary","stopped_by_admin","rule_changed","rule_deleted"]},{"type":"null"}],"description":"Why it ended, once it has: stopped_by_granter, removed_by_beneficiary, stopped_by_admin, rule_changed (a new version of the rule was saved, on a share that ended before new versions kept shares) or rule_deleted (the rule was deleted; the share keeps its record, and the rule is no longer shown). Null while it has not."},"accepted_rule_version":{"type":"integer","description":"The version of the rule that was accepted. A later version keeps the share: what each version matches is shared from when it is saved."},"edited_on_accept":{"type":"boolean","description":"Whether the member who accepted changed the rule's English, tree or explanation before accepting (a new name does not count)."},"live":{"type":"boolean","description":"Not ended, and both people are still members under the memberships that were recorded."},"delivery_paused":{"description":"True when the rule is active and the share live but new email is not being shared now (what was shared stays visible); false when new email is shared. Null when it does not apply (share not live, rule not active, or you are not on the share) or could not be read.","type":["boolean","null"]},"is_granter":{"type":"boolean","description":"Whether you are the member sharing their mail."},"is_beneficiary":{"type":"boolean","description":"Whether you are the member it is shared with."},"granter":{"$ref":"#\/components\/schemas\/EmailRuleShareGranter"},"beneficiary":{"$ref":"#\/components\/schemas\/EmailRuleShareBeneficiary"},"rule":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"state":{"type":"string","enum":["draft","observe","active"]},"current_version":{"anyOf":[{"type":"integer"},{"type":"null"}]},"english":{"type":["string","null"]},"explanation":{"type":["string","null"]},"updated_at":{"type":"string","format":"date-time"}},"required":["name","state","current_version","english","explanation","updated_at"],"additionalProperties":false},{"type":"null"}],"description":"The rule's name, state and description; null once the share is not live, except for the member sharing their mail."},"rule_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"The rule itself. Only for the member sharing their mail; null for anyone else."}},"required":["id","request_id","created_at","ended_at","end_reason","accepted_rule_version","edited_on_accept","live","delivery_paused","is_granter","is_beneficiary","granter","beneficiary","rule","rule_id"],"additionalProperties":false},"EmailRuleShareGranter":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"email":{"description":"Sign-in email; null unless you can list member emails, this is you, or this is a live share with you.","type":["string","null"]},"active":{"type":"boolean","description":"Still a member under the membership recorded (a request or share with someone who left no longer works)."}},"required":["user_id","name","email","active"],"additionalProperties":false},"EmailRuleShareBeneficiary":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"email":{"description":"Sign-in email; null unless you can list member emails, this is you, or you are the granter (live or not).","type":["string","null"]},"active":{"type":"boolean","description":"Still a member under the membership recorded (a request or share with someone who left no longer works)."}},"required":["user_id","name","email","active"],"additionalProperties":false},"AcceptEmailRuleRequestRequest":{"type":"object","properties":{"name":{"description":"A name for the rule. Defaults to the name it was asked for under.","type":"string","minLength":1,"maxLength":200},"english":{"type":"string","minLength":1,"maxLength":2000,"description":"The plain-English description the rule was compiled from."},"tree":{"description":"The rule tree. Checked against the column contract on save.","$ref":"#\/components\/schemas\/EmailRuleTreeInput"},"explanation":{"description":"What the rule matches, in plain English. Left out, the server writes one from the tree.","type":"string","minLength":1,"maxLength":1000},"assumptions":{"description":"The compile response's assumptions. Stored as sent.","maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}},"warnings":{"description":"The compile response's warnings. Stored as sent, then any server note they lack on an address or domain the description names that the rule does not test, or on a grant rule that tests only who an email was sent to (on a rule request, every compiler note on the rule).","maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}},"compiled_by":{"description":"Who produced the tree (not verified): the compile response's compiled_by, or member (the default) for a tree you wrote or edited.","type":"string","minLength":1,"maxLength":64},"breadth":{"description":"Ignored (the compile answer's breadth).","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"conditions_breadth":{"description":"Ignored.","anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleBreadth"},{"type":"null"}]},"breadth_scope":{"description":"Ignored (the compile answer's breadth_scope): set a rule's scope with evaluation_scope.","$ref":"#\/components\/schemas\/EmailRuleScopeUsed"},"scope_warnings":{"description":"Ignored (the compile answer's scope_warnings).","maxItems":150,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleScopeWarning"}},"kind":{"description":"Only grant.","type":"string","const":"grant"},"expectation_samples":{"description":"Made-up emails the rule must match or not, such as look-alikes it must never match, kept on the rule this accept writes.","minItems":1,"maxItems":20,"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleExpectationSampleInput"}}},"additionalProperties":false},"AcceptEmailRuleRequestResult":{"type":"object","properties":{"notice":{"description":"What is now shared, in words. When you changed the rule and it can now match mail the request left out by who it was sent to (such as mail where an address is only copied), that comes first; when you changed its english, tree or explanation (edited_on_accept; a new name alone is no change), that rule.current is what you agreed to and request what was asked.","type":"string"},"rule":{"$ref":"#\/components\/schemas\/EmailRuleWithVersion"},"share":{"$ref":"#\/components\/schemas\/EmailRuleShare"},"request":{"$ref":"#\/components\/schemas\/EmailRuleRequest"}},"required":["rule","share","request"],"additionalProperties":false},"ListEmailRuleSharesResult":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleShare"}},"ListGivenEmailRuleSharesResult":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleShare"}},"GetEmailRuleShareResult":{"$ref":"#\/components\/schemas\/EmailRuleShare"},"ListEmailRuleShareEmailsResult":{"type":"array","items":{"type":"object","properties":{"imported":{"default":null,"anyOf":[{"oneOf":[{"type":"object","properties":{"from":{"type":"string","const":"google_workspace"},"method":{"type":"string","const":"primitive_direct"},"verification":{"type":"string","const":"provider_fetched"},"at":{"type":"string","format":"date-time"}},"required":["from","method","verification","at"],"additionalProperties":false},{"type":"object","properties":{"from":{"type":"string","const":"unknown"},"method":{"type":"string","const":"api_upload"},"verification":{"type":"string","const":"unverified"},"at":{"type":"string","format":"date-time"}},"required":["from","method","verification","at"],"additionalProperties":false}]},{"type":"null"}]},"delivery_report":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["permanent","transient","undetermined"]},"sent_email_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"failed_recipients":{"type":"array","items":{"type":"string"}},"status_code":{"type":["string","null"]},"diagnostic":{"type":["string","null"]}},"required":["type","sent_email_id","failed_recipients","status_code","diagnostic"],"additionalProperties":false},{"type":"null"}]},"presence_control":{"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"}]},"sender_member":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"user_id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}},"required":["address","user_id","name"],"additionalProperties":false},{"type":"null"}]},"repeat":{"anyOf":[{"type":"object","properties":{"repeat_id":{"type":"string","format":"uuid"},"sequence":{"type":"integer","minimum":1}},"required":["repeat_id","sequence"],"additionalProperties":false},{"type":"null"}]},"id":{"type":"string","format":"uuid"},"thread_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"message_id":{"type":["string","null"]},"domain_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"org_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"sender":{"type":"string"},"recipient":{"type":"string"},"subject":{"type":["string","null"]},"domain":{"type":"string"},"spam_score":{"type":["number","null"]},"created_at":{"type":"string"},"received_at":{"type":"string"},"raw_size_bytes":{"type":["number","null"]},"webhook_status":{"type":["string","null"]},"webhook_attempt_count":{"type":"number"},"from_header":{"type":["string","null"]},"from_name":{"type":["string","null"]},"from_email":{"type":"string"},"to_email":{"type":"string"},"from_known_address":{"type":"boolean"},"attachment_count":{"type":"number"},"automation_headers":{"anyOf":[{"type":"object","properties":{"list_unsubscribe":{"type":"string"},"list_id":{"type":"string"},"precedence":{"type":"string"},"auto_submitted":{"type":"string"}},"additionalProperties":{}},{"type":"null"}]},"automated":{"type":"boolean"},"automated_reasons":{"type":"array","items":{"type":"string"}},"from_verified":{"type":"boolean"},"from_header_email":{"type":["string","null"]},"relay":{"anyOf":[{"type":"object","properties":{"hostname":{"type":"string"},"via":{"type":"string","const":"mail_relay"},"delivery":{"type":"array","items":{"type":"object","properties":{"recipient":{"type":"string"},"status":{"type":"string","enum":["delivered","deferred","bounced"]},"smtp_code":{"anyOf":[{"type":"integer"},{"type":"null"}]},"enhanced_status_code":{"type":["string","null"]},"smtp_response":{"type":"string"},"at":{"type":"string"}},"required":["recipient","status","smtp_code","enhanced_status_code","smtp_response","at"],"additionalProperties":false}}},"required":["hostname","via"],"additionalProperties":false},{"type":"null"}]},"reply_count":{"type":"integer","minimum":0},"last_replied_at":{"type":["string","null"]},"awaiting":{"type":"string","enum":["you","them"]},"fyi":{"type":"boolean"},"muted":{"type":"boolean"},"interaction_hint":{"type":"string","enum":["status","card","none","pending"]},"interaction_kind":{"type":["string","null"]},"interaction_candidate":{"type":"boolean"},"snippet":{"type":["string","null"]},"parse_status":{"anyOf":[{"type":"string","enum":["pending","processing","complete","failed"]},{"type":"null"}]},"in_reply_to":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"references":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"score":{"type":"number"},"highlights":{"anyOf":[{"type":"object","properties":{"subject":{"type":"array","items":{"type":"string"}},"body":{"type":"array","items":{"type":"string"}}},"required":["subject","body"],"additionalProperties":false},{"type":"null"}]},"shared_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"For the person sharing, when the share handed this email over. For the person it was shared with, when the share's rule first matched it, which is when it was shared or shortly before. Null if that could not be read."}},"required":["imported","id","message_id","domain_id","org_id","status","sender","recipient","subject","domain","spam_score","created_at","received_at","raw_size_bytes","automated","automated_reasons","from_verified","from_header_email","relay","reply_count","last_replied_at","awaiting","interaction_hint","interaction_kind","interaction_candidate","shared_at"],"additionalProperties":{}}},"EndEmailRuleShareResult":{"type":"object","properties":{"ended":{"type":"boolean"},"rule_id":{"type":"string"},"note":{"type":"string"}},"required":["ended"],"additionalProperties":{}},"EmailRuleCoveringMe":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"state":{"type":"string","enum":["draft","observe","active"],"description":"draft: it decides nothing. observe: it decides and records, and shares nothing. active: it decides and, for a grant rule, shares what it matches."},"kind":{"type":"string","enum":["grant","do_not_share"],"description":"grant: what it matches can be shared. do_not_share: it blocks sharing what it matches."},"do_not_share_note":{"description":"On a grant rule: that the active do-not-share rules over an email run first, keeping from it what they match and holding back what they cannot decide, which is decided again for up to a day and shared only if none of them then matches it.","type":"string"},"author":{"description":"Who wrote it.","$ref":"#\/components\/schemas\/EmailRuleCoveringAuthor"},"is_author":{"type":"boolean","description":"Whether you wrote it."},"covers":{"$ref":"#\/components\/schemas\/EmailRuleCoverage"},"current_version":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}]},"receivers":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleReceivers"},{"type":"null"}],"description":"Null when it could not be read in time, or for a rule shown in draft or observe that turned active or was deleted after the page was read; try again."},"receivers_once_active":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleReceivers"},{"type":"null"}],"description":"For a grant rule in draft or observe: who would receive what it matches once it is turned active. None of them receives anything yet. Null for any other rule, and as receivers is null."},"created_at":{"type":"string","format":"date-time"},"english":{"description":"What its author asked for, in their words, at its current version. Null when it has no version, was deleted after the page was read, or the words could not be read in time; GET \/v1\/email-rules\/covering-me\/{id} shows them with its tree.","type":["string","null"]},"explanation":{"description":"What its current version matches, as compiled. Null as english is.","type":["string","null"]}},"required":["id","name","state","kind","author","is_author","covers","current_version","receivers","receivers_once_active","created_at","english","explanation"],"additionalProperties":false},"EmailRuleCoverage":{"type":"object","properties":{"addresses":{"type":"array","items":{"type":"string"},"description":"Your own mailboxes (your member mailbox and your personal agents' addresses) it reaches now, lowercase."},"via":{"type":"string","enum":["author_reach","selected","own_mailboxes"],"description":"author_reach: it runs over everything its author can read, which includes these. selected: it names these mailboxes or their domain. own_mailboxes: it is your own rule over your own mail."}},"required":["addresses","via"],"additionalProperties":false},"ListEmailRulesCoveringMeResult":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleCoveringMe"}},"EmailRuleCoveringMeRead":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"description":"Null when covers_now is false: its name now may describe something else.","type":["string","null"]},"state":{"type":"string","enum":["draft","observe","active"],"description":"draft: it decides nothing. observe: it decides and records, and shares nothing. active: it decides and, for a grant rule, shares what it matches."},"kind":{"type":"string","enum":["grant","do_not_share"],"description":"grant: what it matches can be shared. do_not_share: it blocks sharing what it matches."},"do_not_share_note":{"description":"On a grant rule: that the active do-not-share rules over an email run first, keeping from it what they match and holding back what they cannot decide, which is decided again for up to a day and shared only if none of them then matches it.","type":"string"},"author":{"description":"Who wrote it.","$ref":"#\/components\/schemas\/EmailRuleCoveringAuthor"},"is_author":{"type":"boolean","description":"Whether you wrote it."},"covers":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleCoverage"},{"type":"null"}],"description":"Null when it no longer covers your mailboxes and you see it because it decided on your mail."},"current_version":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}],"description":"Null when covers_now is false."},"receivers":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleReceivers"},{"type":"null"}],"description":"Null when covers_now is false (who receives it now is not about your mail), when it could not be read in time, or for a rule shown in draft or observe that turned active or was deleted after it was read; try again."},"receivers_once_active":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleReceivers"},{"type":"null"}],"description":"As on the list; also null when covers_now is false."},"created_at":{"type":"string","format":"date-time"},"covers_now":{"type":"boolean","description":"Whether it covers one of your own mailboxes now. False for a rule you see only because it decided on mail at your own mailbox before."},"current":{"anyOf":[{"$ref":"#\/components\/schemas\/EmailRuleCoveringMeVersion"},{"type":"null"}],"description":"Its current version: what it asks of each email. When covers_now is false, the newest version that decided on your mail instead."},"note":{"description":"Present when covers_now is false: what is withheld, and why.","type":"string"}},"required":["id","name","state","kind","author","is_author","covers","current_version","receivers","receivers_once_active","created_at","covers_now","current"],"additionalProperties":false},"EmailRuleCoveringMeVersion":{"type":"object","properties":{"version":{"type":"integer","exclusiveMinimum":0},"english":{"type":"string","description":"What its author asked for, in their words."},"tree":{"$ref":"#\/components\/schemas\/EmailRuleTree"},"explanation":{"type":"string","description":"What the rule matches, as compiled."},"assumptions":{"type":"array","items":{"type":"string"}},"compiled_by":{"type":"string"},"vocabulary":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"required":["version","english","tree","explanation","assumptions","compiled_by","vocabulary","created_at"],"additionalProperties":false},"GetEmailRuleCoveringMeResult":{"$ref":"#\/components\/schemas\/EmailRuleCoveringMeRead"},"EmailRuleDecisionOnMyMail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email_id":{"type":"string","format":"uuid"},"rule_version":{"type":"integer","exclusiveMinimum":0},"classification_id":{"type":"string","format":"uuid"},"matched":{"type":"boolean"},"near_miss":{"type":"boolean","description":"Not matched, but nearly. False when detail_withheld is true."},"parts":{"type":"array","items":{},"description":"How each top-level part of the rule decided."},"question_answers":{"type":"array","items":{},"description":"The answers to the rule's own questions, when they were asked."},"detail_withheld":{"description":"Present, as true, when the email's content cannot be read: matched still says what the rule decided, and parts and question_answers are empty.","type":"boolean","const":true},"created_at":{"type":"string","format":"date-time"},"from_address":{"description":"The email's sender address, as GET \/v1\/emails\/{id} shows it.","type":["string","null"]},"subject":{"type":["string","null"]},"received_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"When the email arrived. Null, with the other two, when you are not shown the email (it awaits sender authentication)."}},"required":["id","email_id","rule_version","classification_id","matched","near_miss","parts","question_answers","created_at","from_address","subject","received_at"],"additionalProperties":false},"ListEmailRuleDecisionsOnMyMailResult":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleDecisionOnMyMail"}},"ListEmailRuleVersionsCoveringMeResult":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleCoveringMeVersion"}},"EmailRuleObjection":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","accepted","declined","withdrawn"],"description":"pending until the rule's author accepts it (turning the rule off) or declines it, or the member who made it withdraws it; never changes after."},"rule":{"type":"object","properties":{"id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Null once the rule was deleted outright."},"name":{"type":"string","description":"Its name when the objection was made."}},"required":["id","name"],"additionalProperties":false},"objector":{"description":"Who objected.","$ref":"#\/components\/schemas\/EmailRuleObjectionPerson"},"author":{"description":"Who wrote the rule.","$ref":"#\/components\/schemas\/EmailRuleObjectionPerson"},"is_objector":{"type":"boolean","description":"Whether you made it, in your current membership."},"is_author":{"type":"boolean","description":"Whether it is to a rule you wrote, in your current membership."},"message":{"description":"Why they object and what they ask (to turn the rule off, narrow it, or explain who receives it); null when none was given, or once withdrawn.","type":["string","null"]},"decline_reason":{"description":"The author's answer when declining, which the objector sees; null when none was given.","type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"decided_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"decided_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]}},"required":["id","status","rule","objector","author","is_objector","is_author","message","decline_reason","created_at","decided_at","decided_by_user_id"],"additionalProperties":false},"EmailRuleObjectionPerson":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"email":{"description":"Sign-in email; null unless you can list member emails or this is you.","type":["string","null"]}},"required":["user_id","name","email"],"additionalProperties":false},"CreateEmailRuleObjectionRequest":{"type":"object","properties":{"rule_id":{"type":"string","description":"A rule that covers one of your own mailboxes now (GET \/email-rules\/covering-me), written by another member.","format":"uuid"},"message":{"description":"Why you object and what you ask, up to 500 characters: to turn the rule off, to narrow it, or to explain who receives what it matches. Null or absent for none. The rule's author and the organization's owners and admins see it while it stands; withdrawing clears it.","anyOf":[{"type":"string","minLength":1,"maxLength":500},{"type":"null"}]}},"required":["rule_id"],"additionalProperties":false},"CreateEmailRuleObjectionResult":{"$ref":"#\/components\/schemas\/EmailRuleObjection"},"ListEmailRuleObjectionsResult":{"type":"array","items":{"$ref":"#\/components\/schemas\/EmailRuleObjection"}},"GetEmailRuleObjectionResult":{"$ref":"#\/components\/schemas\/EmailRuleObjection"},"WithdrawEmailRuleObjectionResult":{"$ref":"#\/components\/schemas\/EmailRuleObjection"},"DeclineEmailRuleObjectionRequest":{"type":"object","properties":{"reason":{"description":"Why you are declining, up to 500 characters; null or absent for none. The member who objected sees it: it is how to answer one that asks you to narrow the rule or explain who receives it.","anyOf":[{"type":"string","minLength":1,"maxLength":500},{"type":"null"}]}},"additionalProperties":false},"DeclineEmailRuleObjectionResult":{"$ref":"#\/components\/schemas\/EmailRuleObjection"},"AcceptEmailRuleObjectionResult":{"$ref":"#\/components\/schemas\/EmailRuleObjection"},"GetDomainAgentAuthorizationResult":{"type":"object","properties":{"domain_id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"state":{"type":"string","enum":["published","published_unauthorized","not_set_up","inconclusive"]},"records":{"type":"array","items":{"type":"string"}}},"required":["domain_id","domain","state","records"],"additionalProperties":false},"UpdatedDomain":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"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\u2019s 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\u2019s 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."},"managed_zone":{"type":["string","null"],"description":"For a platform-managed domain, the zone Primitive operates on your behalf (for example `primitive.email`); you publish no DNS records for it. Null for a domain you own."}},"required":["id","org_id","domain","is_active","created_at","dns_health"],"description":"A verified domain as `PATCH \/domains\/{id}` returns it: the verified domain's fields, without `verified`."},"CheckVanityInboxNameResult":{"type":"object","properties":{"status":{"type":"string","enum":["available","taken","reserved","invalid"]},"name":{"type":["string","null"]},"message":{"type":["string","null"]}},"required":["status","name","message"],"additionalProperties":false},"ClaimVanityInboxNameRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64}},"required":["name"],"additionalProperties":false},"ClaimVanityInboxNameResult":{"type":"object","properties":{"subdomain":{"type":"string"},"domain":{"type":"string"},"outbound_ready":{"type":"boolean"}},"required":["subdomain","domain","outbound_ready"],"additionalProperties":false},"ImportGoogleMailMessageRequest":{"type":"object","properties":{"message_id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},"required":["message_id"],"additionalProperties":false},"ImportGoogleMailMessageResult":{"anyOf":[{"type":"object","properties":{"status":{"type":"string","enum":["imported","already_present"]},"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)$"}},"required":["status","email_id"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","enum":["suppressed","in_progress","indeterminate"]}},"required":["status"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"skipped"},"reason":{"type":"string","enum":["excluded_label","out_of_window","too_large","provider_missing"]}},"required":["status","reason"],"additionalProperties":false}]},"ReconcileGoogleMailImportRequest":{"type":"object","properties":{},"additionalProperties":false},"ReconcileGoogleMailImportResult":{"type":"object","properties":{"cleaned":{"type":"integer","minimum":0},"unresolved":{"type":"integer","minimum":0}},"required":["cleaned","unresolved"],"additionalProperties":false},"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":["imported","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":{"imported":{"type":["object","null"],"description":"Historical import; never sender or SMTP delivery proof. EML uploads are unverified even when downloaded from a provider. Null for live mail. Excluded from live tails and deltas.","additionalProperties":false,"required":["from","method","verification","at"],"properties":{"from":{"type":"string","enum":["google_workspace","unknown"],"description":"Original mail provider. Unknown when Primitive has not established the provider of an uploaded file."},"method":{"type":"string","enum":["primitive_direct","api_upload"],"description":"How Primitive acquired the historical message."},"verification":{"type":"string","enum":["provider_fetched","unverified"]},"at":{"type":"string","format":"date-time","description":"When Primitive acquired this historical message."}},"oneOf":[{"type":"null"},{"type":"object","required":["from","method","verification","at"],"properties":{"from":{"const":"google_workspace"},"method":{"const":"primitive_direct"},"verification":{"const":"provider_fetched"},"at":{"type":"string","format":"date-time"}}},{"type":"object","required":["from","method","verification","at"],"properties":{"from":{"const":"unknown"},"method":{"const":"api_upload"},"verification":{"const":"unverified"},"at":{"type":"string","format":"date-time"}}}]},"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","null"],"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 body is not shown (a blocked sender, or a parse that failed after the body was accepted). May be null: this field carries message content, which is withheld when the content is not available to your credential."},"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."},"message":{"oneOf":[{"$ref":"#\/components\/schemas\/ChangeMessage"},{"type":"null"}],"description":"Present only when the request sets `expand=message`. For `email.visible`, `email.updated`, `sent_email.created`, `sent_email.updated`: the message as it reads at the time of this response (not at the time of the change), exactly as the list endpoints return it, marked by `direction`. Null for every other kind, and when the message is no longer readable by this credential (deleted, or withdrawn from view after the change)."}}},"ChangeMessage":{"description":"A message inlined by `expand=message`. `direction` `inbound` is a GET \/emails row; `outbound` is a GET \/sent-emails row.","oneOf":[{"$ref":"#\/components\/schemas\/ChangeInboundMessage"},{"$ref":"#\/components\/schemas\/ChangeOutboundMessage"}],"discriminator":{"propertyName":"direction","mapping":{"inbound":"#\/components\/schemas\/ChangeInboundMessage","outbound":"#\/components\/schemas\/ChangeOutboundMessage"}}},"ChangeInboundMessage":{"description":"An inbound message inlined by `expand=message`, as GET \/emails returns it.","allOf":[{"$ref":"#\/components\/schemas\/EmailSummary"},{"type":"object","required":["direction"],"properties":{"direction":{"type":"string","enum":["inbound"]}}}]},"ChangeOutboundMessage":{"description":"A sent message inlined by `expand=message`, as GET \/sent-emails returns it.","allOf":[{"$ref":"#\/components\/schemas\/SentEmailSummary"},{"type":"object","required":["direction"],"properties":{"direction":{"type":"string","enum":["outbound"]}}}]},"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-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-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-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-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},"LookupFunctionResult":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"deploy_status":{"type":"string"},"deployed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"tags":{"type":"array","items":{"type":"string"}},"has_code":{"type":"boolean"}},"required":["id","name","deploy_status","deployed_at","created_at","updated_at","tags","has_code"],"additionalProperties":{}},{"type":"null"}]},"GetOrgFunctionUsageResult":{"type":"object","properties":{"monthly":{"type":"object","properties":{"invocations":{"type":"number","minimum":0},"runtime_ms":{"type":"number","minimum":0},"delivered":{"type":"number","minimum":0},"failed":{"type":"number","minimum":0}},"required":["invocations","runtime_ms","delivered","failed"],"additionalProperties":{}},"lifetime":{"type":"object","properties":{"invocations":{"type":"number","minimum":0},"runtime_ms":{"type":"number","minimum":0},"delivered":{"type":"number","minimum":0},"failed":{"type":"number","minimum":0}},"required":["invocations","runtime_ms","delivered","failed"],"additionalProperties":{}},"active_functions":{"type":"number","minimum":0}},"required":["monthly","lifetime","active_functions"],"additionalProperties":{}},"GetOrgFunctionInvocationSeriesResult":{"type":"object","properties":{"days":{"type":"integer","exclusiveMinimum":0},"buckets":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string"},"label":{"type":"string"}},"required":["day","label"],"additionalProperties":{}}},"functions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"total":{"type":"number","minimum":0}},"required":["id","name","total"],"additionalProperties":{}}},"points":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string"},"function_id":{"type":"string","format":"uuid"},"invocations":{"type":"number","minimum":0},"failed":{"type":"number","minimum":0}},"required":["day","function_id","invocations","failed"],"additionalProperties":{}}}},"required":["days","buckets","functions","points"],"additionalProperties":{}},"GetOrgFunctionFallbackResult":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["http","function"]},"functionId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"functionName":{"type":["string","null"]}},"required":["kind","functionId","functionName"],"additionalProperties":{}},{"type":"null"}]},"RedeployFunctionResult":{"type":"object","properties":{"redeployed":{"type":"boolean","const":true},"id":{"type":"string","format":"uuid"},"deployment_id":{"type":"string","format":"uuid"}},"required":["redeployed","id","deployment_id"],"additionalProperties":{}},"UpdateFunctionTagsRequest":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"],"additionalProperties":false},"UpdateFunctionTagsResult":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"],"additionalProperties":{}},"ListFunctionDeploymentsResult":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"},"deploy_error":{"type":["string","null"]},"started_at":{"type":"string","format":"date-time"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"duration_ms":{"type":["number","null"]},"is_current":{"type":"boolean"},"source_kind":{"type":"string","enum":["esm","managed_build"]},"entry_point":{"type":["string","null"]},"bundle_bytes":{"type":["number","null"]},"bundle_gzip_bytes":{"type":["number","null"]},"build_ms":{"type":["number","null"]},"bundle_sha256":{"type":["string","null"]},"source_sha256":{"type":["string","null"]},"builder_version":{"type":["string","null"]}},"required":["id","status","deploy_error","started_at","finished_at","duration_ms","is_current","source_kind","entry_point","bundle_bytes","bundle_gzip_bytes","build_ms","bundle_sha256","source_sha256","builder_version"],"additionalProperties":{}}}},"required":["items"],"additionalProperties":{}},"GetFunctionDeploymentResult":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"},"deploy_error":{"type":["string","null"]},"started_at":{"type":"string","format":"date-time"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"duration_ms":{"type":["number","null"]},"is_current":{"type":"boolean"},"source_kind":{"type":"string","enum":["esm","managed_build"]},"entry_point":{"type":["string","null"]},"bundle_bytes":{"type":["number","null"]},"bundle_gzip_bytes":{"type":["number","null"]},"build_ms":{"type":["number","null"]},"bundle_sha256":{"type":["string","null"]},"source_sha256":{"type":["string","null"]},"builder_version":{"type":["string","null"]}},"required":["id","status","deploy_error","started_at","finished_at","duration_ms","is_current","source_kind","entry_point","bundle_bytes","bundle_gzip_bytes","build_ms","bundle_sha256","source_sha256","builder_version"],"additionalProperties":{}},"GetFunctionSourceResult":{"type":"object","properties":{"deploymentId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"},"bytes":{"type":"integer","minimum":0},"sha256":{"type":"string"}},"required":["path","content","bytes","sha256"],"additionalProperties":{}}}},"required":["deploymentId","files"],"additionalProperties":{}},"GetFunctionUsageResult":{"type":"object","properties":{"monthly":{"type":"object","properties":{"invocations":{"type":"number","minimum":0},"runtime_ms":{"type":"number","minimum":0},"delivered":{"type":"number","minimum":0},"failed":{"type":"number","minimum":0}},"required":["invocations","runtime_ms","delivered","failed"],"additionalProperties":{}},"lifetime":{"type":"object","properties":{"invocations":{"type":"number","minimum":0},"runtime_ms":{"type":"number","minimum":0},"delivered":{"type":"number","minimum":0},"failed":{"type":"number","minimum":0}},"required":["invocations","runtime_ms","delivered","failed"],"additionalProperties":{}}},"required":["monthly","lifetime"],"additionalProperties":{}},"GetFunctionUsersResult":{"type":"object","properties":{"users":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"truncated":{"type":"boolean"},"cap":{"type":"integer","exclusiveMinimum":0}},"required":["users","truncated","cap"],"additionalProperties":{}},"ListFunctionActivityResult":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"function_id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["email.sent","email.replied"]},"occurred_at":{"type":"string","format":"date-time"},"status":{"type":"string"},"delivery_id":{"type":["string","null"]},"email_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"sent_email_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"related_email_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]}},"required":["id","function_id","type","occurred_at","status","delivery_id","email_id","sent_email_id","related_email_id"],"additionalProperties":{}}},"next_cursor":{"type":["string","null"]}},"required":["items","next_cursor"],"additionalProperties":{}},"ListFunctionInvocationsResult":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"function_id":{"type":"string","format":"uuid"},"outcome":{"type":["string","null"]},"cf_truncated":{"type":"boolean"},"trigger_kind":{"type":["string","null"]},"cpu_time_ms":{"type":["number","null"]},"wall_time_ms":{"type":["number","null"]},"started_at":{"type":"string","format":"date-time"},"request_url":{"type":["string","null"]},"request_method":{"type":["string","null"]},"response_status":{"anyOf":[{"type":"integer"},{"type":"null"}]},"exception_name":{"type":["string","null"]},"exception_message":{"type":["string","null"]},"exception_stack":{"type":["string","null"]},"exception_fingerprint":{"type":["string","null"]},"cf_script_version_id":{"type":["string","null"]},"dispatch_namespace":{"type":["string","null"]},"webhook_delivery_id":{"type":["string","null"]},"log_line_count":{"anyOf":[{"type":"integer"},{"type":"null"}]},"log_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time"}},"required":["id","function_id","outcome","cf_truncated","trigger_kind","cpu_time_ms","wall_time_ms","started_at","request_url","request_method","response_status","exception_name","exception_message","exception_fingerprint","cf_script_version_id","dispatch_namespace","webhook_delivery_id","log_line_count","log_bytes","created_at"],"additionalProperties":{}}},"next_cursor":{"type":["string","null"]}},"required":["items","next_cursor"],"additionalProperties":{}},"GetFunctionInvocationResult":{"type":"object","properties":{"invocation":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"function_id":{"type":"string","format":"uuid"},"outcome":{"type":["string","null"]},"cf_truncated":{"type":"boolean"},"trigger_kind":{"type":["string","null"]},"cpu_time_ms":{"type":["number","null"]},"wall_time_ms":{"type":["number","null"]},"started_at":{"type":"string","format":"date-time"},"request_url":{"type":["string","null"]},"request_method":{"type":["string","null"]},"response_status":{"anyOf":[{"type":"integer"},{"type":"null"}]},"exception_name":{"type":["string","null"]},"exception_message":{"type":["string","null"]},"exception_stack":{"type":["string","null"]},"exception_fingerprint":{"type":["string","null"]},"cf_script_version_id":{"type":["string","null"]},"dispatch_namespace":{"type":["string","null"]},"webhook_delivery_id":{"type":["string","null"]},"log_line_count":{"anyOf":[{"type":"integer"},{"type":"null"}]},"log_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time"}},"required":["id","function_id","outcome","cf_truncated","trigger_kind","cpu_time_ms","wall_time_ms","started_at","request_url","request_method","response_status","exception_name","exception_message","exception_fingerprint","cf_script_version_id","dispatch_namespace","webhook_delivery_id","log_line_count","log_bytes","created_at"],"additionalProperties":{}},"logs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"function_id":{"type":"string","format":"uuid"},"level":{"type":"string"},"message":{"type":["string","null"]},"ts":{"type":"string","format":"date-time"},"metadata":{"anyOf":[{},{"type":"null"}]}},"required":["id","function_id","level","message","ts","metadata"],"additionalProperties":{}}},"logs_truncated":{"type":"boolean"}},"required":["invocation","logs","logs_truncated"],"additionalProperties":{}},"ListFunctionRuntimeKeyPresetsResult":{"type":"object","properties":{"enabled":{"type":"boolean"},"presets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"available":{"type":"boolean"},"reason":{"type":["string","null"]},"differs_from_today":{"type":"boolean"}},"required":["id","label","description","available","reason","differs_from_today"],"additionalProperties":false}}},"required":["enabled","presets"],"additionalProperties":false},"RequestFunctionRuntimeKeyRunnerRequest":{"type":"object","properties":{"preset":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"RequestFunctionRuntimeKeyRunnerResult":{"type":"object","properties":{"enabled":{"type":"boolean"},"presets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"available":{"type":"boolean"},"reason":{"type":["string","null"]},"differs_from_today":{"type":"boolean"}},"required":["id","label","description","available","reason","differs_from_today"],"additionalProperties":false}}},"required":["enabled","presets"],"additionalProperties":false},"GetFunctionRuntimeKeyResult":{"type":"object","properties":{"mode":{"type":"string","enum":["org_shared","per_function"]},"preset":{"type":["string","null"]},"permissions":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mail_scope":{},"attached_secret_keys":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"key_prefix":{"type":["string","null"]},"expires_at":{"type":["string","null"]},"suspended_at":{"type":["string","null"]},"suspended_reason":{"type":["string","null"]},"function_bound_key":{"anyOf":[{"type":"string","enum":["live","revoked"]},{"type":"null"}]},"suspended_by":{"anyOf":[{"type":"string","enum":["transfer","access_decision","owner_or_admin","other"]},{"type":"null"}]},"stop_rank":{"anyOf":[{"type":"string","enum":["owner","admin"]},{"type":"null"}]},"key_suspension_reasons":{"type":"array","items":{"type":"string"}},"transfer_stop_placed":{"type":"boolean"}},"required":["mode","preset","permissions","mail_scope","attached_secret_keys","key_prefix","expires_at","suspended_at","suspended_reason"],"additionalProperties":false},"SetFunctionRuntimeKeyRequest":{"type":"object","properties":{"runtime_grant":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"object","properties":{"preset":{"type":"string","minLength":1,"maxLength":64},"addresses":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":320}},"permissions":{"maxItems":200,"type":"array","items":{"type":"string","maxLength":128}},"mail_scope":{"type":"string","const":"org"}},"required":["preset"],"additionalProperties":false}]},"attached_secret_keys":{"maxItems":100,"type":"array","items":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]{0,127}$"}}},"additionalProperties":false},"SetFunctionRuntimeKeyResult":{"type":"object","properties":{"mode":{"type":"string","enum":["org_shared","per_function"]},"preset":{"type":["string","null"]},"permissions":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mail_scope":{},"attached_secret_keys":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"key_prefix":{"type":["string","null"]},"expires_at":{"type":["string","null"]},"suspended_at":{"type":["string","null"]},"suspended_reason":{"type":["string","null"]},"function_bound_key":{"anyOf":[{"type":"string","enum":["live","revoked"]},{"type":"null"}]},"suspended_by":{"anyOf":[{"type":"string","enum":["transfer","access_decision","owner_or_admin","other"]},{"type":"null"}]},"stop_rank":{"anyOf":[{"type":"string","enum":["owner","admin"]},{"type":"null"}]},"key_suspension_reasons":{"type":"array","items":{"type":"string"}},"transfer_stop_placed":{"type":"boolean"}},"required":["mode","preset","permissions","mail_scope","attached_secret_keys","key_prefix","expires_at","suspended_at","suspended_reason"],"additionalProperties":false},"SuspendFunctionRuntimeKeyRequest":{"type":"object","properties":{"reason":{"type":"string","enum":["owner_action","emergency_revoke"]}},"additionalProperties":false},"SuspendFunctionRuntimeKeyResult":{"type":"object","properties":{"mode":{"type":"string","enum":["org_shared","per_function"]},"preset":{"type":["string","null"]},"permissions":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mail_scope":{},"attached_secret_keys":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"key_prefix":{"type":["string","null"]},"expires_at":{"type":["string","null"]},"suspended_at":{"type":["string","null"]},"suspended_reason":{"type":["string","null"]},"function_bound_key":{"anyOf":[{"type":"string","enum":["live","revoked"]},{"type":"null"}]},"suspended_by":{"anyOf":[{"type":"string","enum":["transfer","access_decision","owner_or_admin","other"]},{"type":"null"}]},"stop_rank":{"anyOf":[{"type":"string","enum":["owner","admin"]},{"type":"null"}]},"key_suspension_reasons":{"type":"array","items":{"type":"string"}},"transfer_stop_placed":{"type":"boolean"}},"required":["mode","preset","permissions","mail_scope","attached_secret_keys","key_prefix","expires_at","suspended_at","suspended_reason"],"additionalProperties":false},"ReaffirmFunctionRuntimeKeyRequest":{"type":"object","properties":{"suspended_at":{"type":"string","format":"date-time"},"suspended_reason":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}]},"suspended_by":{"anyOf":[{"type":"string","enum":["transfer","access_decision","owner_or_admin","other"]},{"type":"null"}]}},"additionalProperties":false},"ReaffirmFunctionRuntimeKeyResult":{"type":"object","properties":{"mode":{"type":"string","enum":["org_shared","per_function"]},"preset":{"type":["string","null"]},"permissions":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mail_scope":{},"attached_secret_keys":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"key_prefix":{"type":["string","null"]},"expires_at":{"type":["string","null"]},"suspended_at":{"type":["string","null"]},"suspended_reason":{"type":["string","null"]},"function_bound_key":{"anyOf":[{"type":"string","enum":["live","revoked"]},{"type":"null"}]},"suspended_by":{"anyOf":[{"type":"string","enum":["transfer","access_decision","owner_or_admin","other"]},{"type":"null"}]},"stop_rank":{"anyOf":[{"type":"string","enum":["owner","admin"]},{"type":"null"}]},"key_suspension_reasons":{"type":"array","items":{"type":"string"}},"transfer_stop_placed":{"type":"boolean"}},"required":["mode","preset","permissions","mail_scope","attached_secret_keys","key_prefix","expires_at","suspended_at","suspended_reason"],"additionalProperties":false},"UpdateFunctionFromTemplateResult":{"type":"object","properties":{"status":{"type":"string","enum":["updated","up_to_date"]},"function_id":{"type":"string","format":"uuid"},"content_sha":{"type":["string","null"]}},"required":["status","function_id","content_sha"],"additionalProperties":{}},"CheckSendabilityRequest":{"type":"object","properties":{"to":{"type":"string","minLength":3,"maxLength":320},"from":{"type":"string","minLength":3,"maxLength":998}},"required":["to"],"additionalProperties":false},"CheckSendabilityResult":{"type":"object","properties":{"allowed":{"type":"boolean"},"summary":{"type":"string"}},"required":["allowed","summary"],"additionalProperties":{}},"GetDeliveryResult":{"type":"object","properties":{"id":{"anyOf":[{"type":"string","maxLength":19,"pattern":"^\\d+$"},{"type":"string","format":"uuid"}]},"email_id":{"anyOf":[{"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)$"},{"type":"null"}]},"event_type":{"type":["string","null"]},"sent_email_id":{"anyOf":[{"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)$"},{"type":"null"}]},"org_id":{"type":"string"},"endpoint_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)$"},"endpoint_url":{"type":"string","description":"Where it was sent: a webhook's scheme and host, followed by \"\/... (path and query hidden)\" when its URL has more (GET \/endpoints\/{id} has the whole URL), function:\/\/<id> for a Function, or pull: for a pull subscription."},"status":{"type":"string"},"attempt_count":{"type":"integer"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}]},"last_error":{"description":"Why the last attempt failed, or null. A webhook's answer is quoted after its status (HTTP 404: ...) only for a caller who can read the webhook (GET \/endpoints\/{id}; a deleted webhook only for a caller who reads every webhook); anyone else gets the status and a note. Text the platform writes (a redirect not followed, a network error) is shown to everyone.","type":["string","null"]},"last_error_code":{"description":"Machine-readable code for the last failure, or null. A code the webhook chose in its answer is shown only to a caller who can read the webhook; anyone else gets handler_4xx or handler_5xx.","type":["string","null"]},"response_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"The HTTP status the endpoint answered this attempt with: a webhook's own answer, or for a Function what its runtime answered, which is the platform's own error when the Function could not be run. Null when no answer came back (a network error, a timeout, an attempt refused or not sent) and on attempts recorded before this field."},"response_body":{"description":"The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent. Null for a redirect (3xx), whose body repeats its Location; otherwise, in answers recorded since November 2026, the webhook URL's query string, its parameters, values long enough to be a credential and any userinfo are hidden wherever it repeats them, and the rest is as the endpoint wrote it. Null for a caller who cannot read the webhook (GET \/endpoints\/{id}; a deleted webhook only for a caller who reads every webhook), since a receiver's answer can restate its URL's credential.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"email":{"anyOf":[{"type":"object","properties":{"sender":{"type":"string"},"recipient":{"type":["string","null"]},"subject":{"type":["string","null"]}},"required":["sender","recipient","subject"],"additionalProperties":false},{"type":"null"}]},"latest_attempt":{"type":"boolean","description":"Whether this is the latest attempt of its delivery: the one recorded last, where a record of no attempt (a rule skip or a held send, attempt_count 0) is latest only when no attempt was made. A retry is recorded as a new attempt with its own id and attempt_count (not always higher than the one before), so an earlier attempt never changes: follow the latest one, or read attempts on any of them."},"retry_scheduled":{"description":"Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. A rule-granted delivery is retried on a schedule of its own, to the same endpoint, deciding it afresh: on its latest attempt true while another attempt is to come (the delivery's next_attempt_at says when); null while an attempt is under way (read it again shortly), while it waits on an owner who cannot receive mail or on a held endpoint (checked again hourly, and sent once that clears), when it is past its 7-day limit, or when that could not be read just now; and false otherwise, including once the endpoint is off or already has the email. Null on every attempt of a delivery retried or settled on a schedule of its own that this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, and a sent email's or other event's delivery. On the latest attempt that failed, is pending or was skipped in a way still owed, of an email that was not rejected and still has its content, to an endpoint that is still on, takes the event, has an owner the pass would deliver to (a revoked or suspended key, or an owner that may not receive the message, reads false) and has not received the email by another delivery: null while a delivery pass for the email is running (whether another follows is decided when it ends), true when the email has another automatic pass to come (a scheduled retry, the reclaim of a pass that stopped, or the recovery of a first pass that did not finish), and false otherwise. False on every other attempt, including every attempt a later one followed: no automatic retry of the email's delivery will try it again, which does not mean nothing else ever will.","type":["boolean","null"]},"next_attempt_at":{"description":"When the next automatic attempt of a rule-granted delivery is due: set on its latest attempt while retry_scheduled is true. The attempt runs at the first retry pass at or after this time (one runs every few minutes), so a time already past means it is due now. Null otherwise, and on every delivery that is not rule-granted, whose retries are passes for the email with no time reported (whether one is coming is retry_scheduled).","type":["string","null"]},"attempts":{"type":"array","items":{"type":"object","properties":{"id":{"anyOf":[{"type":"string","maxLength":19,"pattern":"^\\d+$"},{"type":"string","format":"uuid"}]},"attempt_count":{"type":"integer"},"status":{"type":"string"},"last_error_code":{"description":"Machine-readable code for the last failure, or null. A code the webhook chose in its answer is shown only to a caller who can read the webhook; anyone else gets handler_4xx or handler_5xx.","type":["string","null"]},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}]},"created_at":{"type":"string"},"response_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"The HTTP status the endpoint answered this attempt with: a webhook's own answer, or for a Function what its runtime answered, which is the platform's own error when the Function could not be run. Null when no answer came back (a network error, a timeout, an attempt refused or not sent) and on attempts recorded before this field."},"response_body":{"description":"The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent. Null for a redirect (3xx), whose body repeats its Location; otherwise, in answers recorded since November 2026, the webhook URL's query string, its parameters, values long enough to be a credential and any userinfo are hidden wherever it repeats them, and the rest is as the endpoint wrote it. Null for a caller who cannot read the webhook (GET \/endpoints\/{id}; a deleted webhook only for a caller who reads every webhook), since a receiver's answer can restate its URL's credential.","type":["string","null"]},"retry_scheduled":{"description":"Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. A rule-granted delivery is retried on a schedule of its own, to the same endpoint, deciding it afresh: on its latest attempt true while another attempt is to come (the delivery's next_attempt_at says when); null while an attempt is under way (read it again shortly), while it waits on an owner who cannot receive mail or on a held endpoint (checked again hourly, and sent once that clears), when it is past its 7-day limit, or when that could not be read just now; and false otherwise, including once the endpoint is off or already has the email. Null on every attempt of a delivery retried or settled on a schedule of its own that this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, and a sent email's or other event's delivery. On the latest attempt that failed, is pending or was skipped in a way still owed, of an email that was not rejected and still has its content, to an endpoint that is still on, takes the event, has an owner the pass would deliver to (a revoked or suspended key, or an owner that may not receive the message, reads false) and has not received the email by another delivery: null while a delivery pass for the email is running (whether another follows is decided when it ends), true when the email has another automatic pass to come (a scheduled retry, the reclaim of a pass that stopped, or the recovery of a first pass that did not finish), and false otherwise. False on every other attempt, including every attempt a later one followed: no automatic retry of the email's delivery will try it again, which does not mean nothing else ever will.","type":["boolean","null"]}},"required":["id","attempt_count","status","last_error_code","duration_ms","created_at","response_status","response_body","retry_scheduled"],"additionalProperties":false},"description":"Every attempt of this delivery in the order recorded, oldest first, this one included: the same endpoint, and the same event (or, for an attempt recorded without one, the same email). Each carries what the endpoint answered that attempt (response_status, response_body). Records of no attempt (attempt_count 0) are listed where they were recorded, so the last entry is not always the latest attempt (latest_attempt says which is). The newest 100 when there are more."}},"required":["id","email_id","event_type","sent_email_id","org_id","endpoint_id","endpoint_url","status","attempt_count","duration_ms","last_error","last_error_code","response_status","response_body","created_at","updated_at","email","latest_attempt","retry_scheduled","next_attempt_at","attempts"],"additionalProperties":false},"FunctionBuildSummary":{"type":"object","properties":{"entry_point":{"type":["string","null"]},"bundle_bytes":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}]},"bundle_gzip_bytes":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}]},"bundle_sha256":{"type":["string","null"]},"bundleSha256":{"type":["string","null"]},"source_sha256":{"type":["string","null"]},"sourceSha256":{"type":["string","null"]},"resolved_dependencies":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"warnings":{"type":"array","items":{"type":"string"}},"build_ms":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}]}},"additionalProperties":{},"description":"The managed build that produced the deployment, when the request sent files. The response also repeats each field in camelCase; read the snake_case names."},"UpdateFunctionResult":{"type":"object","description":"Returned by PUT \/functions\/{id}. A code upload answers with the function's name, timestamps and deploy result; a managed build (files) answers with deployment_id and build instead.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"deploy_status":{"$ref":"#\/components\/schemas\/FunctionDeployStatus"},"deploy_error":{"type":["string","null"],"description":"Error message from the most recent failed deploy, or null."},"deployed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"deployment_id":{"type":"string","format":"uuid","description":"The deployment this request created, when it sent files for a managed build."},"build":{"$ref":"#\/components\/schemas\/FunctionBuildSummary"}},"required":["id","deploy_status"]},"FunctionRoutingDestination":{"type":["object","null"],"description":"Where inbound mail that no recipient route matches is delivered: a function or a webhook, from the domain's own default (scope domain) or the organization's (scope org). Null when there is none. host is shown only to a caller that may read webhooks. owner_scoped is true when the organization does not own the destination, so it receives only the mail its owner can read.","properties":{"kind":{"type":"string","enum":["function","webhook"]},"function":{"type":"object","properties":{"id":{"type":["string","null"],"format":"uuid"},"name":{"type":["string","null"]}},"required":["id","name"]},"host":{"type":"string"},"scope":{"type":"string","enum":["domain","org"]},"owner_scoped":{"type":"boolean","enum":[true]}},"required":["kind","scope"]},"Projects_listOrganizationInvitations_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"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)$"},"email":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"status":{"type":"string","enum":["preparing","pending","accepted","expired","revoked","unknown","failed"]},"delivery":{"type":"string","enum":["accepted","unknown","rejected"]},"expires_at":{"type":["string","null"]},"revoked_reason":{"anyOf":[{"type":"string","enum":["inviter_departed","inviter_lost_permission","accepted_after_inviter_departed"]},{"type":"null"}]},"failure_reason":{"type":"string","enum":["member_limit","already_member","already_invited","invalid_address","not_allowed","refused"]},"role":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"builtin"},"key":{"type":"string","enum":["admin","member"]},"role_id":{"type":"null"}},"required":["kind","key","role_id"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"template"},"key":{"type":"string","minLength":1,"maxLength":100},"role_id":{"type":"null"}},"required":["kind","key","role_id"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"custom"},"key":{"type":"null"},"role_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)$"}},"required":["kind","key","role_id"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"none"},"key":{"type":"null"},"role_id":{"type":"null"}},"required":["kind","key","role_id"],"additionalProperties":false}]}},"required":["id","email","status","delivery","expires_at"],"additionalProperties":false}},"cursor":{"anyOf":[{"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)$"},{"type":"null"}]}},"required":["items","cursor"],"additionalProperties":false},"Projects_inviteOrganizationMember_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"request_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)$"},"email":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"role":{"description":"The role the person joins with: member (the default), none for no access, admin, a role template key or a custom role id. Anything above member, and member where every member reads all of the organization's mail, needs a signed-in dashboard session holding roles:assign. owner and restricted are refused.","type":"string","minLength":1,"maxLength":100}},"required":["request_id","email"],"additionalProperties":false},"Projects_inviteOrganizationMember_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"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)$"},"email":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"status":{"type":"string","enum":["preparing","pending","accepted","expired","revoked","unknown","failed"]},"delivery":{"type":"string","enum":["accepted","unknown","rejected"]},"expires_at":{"type":["string","null"]},"revoked_reason":{"anyOf":[{"type":"string","enum":["inviter_departed","inviter_lost_permission","accepted_after_inviter_departed"]},{"type":"null"}]},"failure_reason":{"type":"string","enum":["member_limit","already_member","already_invited","invalid_address","not_allowed","refused"]},"role":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"builtin"},"key":{"type":"string","enum":["admin","member"]},"role_id":{"type":"null"}},"required":["kind","key","role_id"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"template"},"key":{"type":"string","minLength":1,"maxLength":100},"role_id":{"type":"null"}},"required":["kind","key","role_id"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"custom"},"key":{"type":"null"},"role_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)$"}},"required":["kind","key","role_id"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"none"},"key":{"type":"null"},"role_id":{"type":"null"}},"required":["kind","key","role_id"],"additionalProperties":false}]}},"required":["id","email","status","delivery","expires_at"],"additionalProperties":false},"Projects_listOrganizationPeople_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"user_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)$"},"name":{"type":"string"},"work_email":{"anyOf":[{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"role":{"type":"string","enum":["owner","admin","member","restricted"]},"role_name":{"type":"string"},"role_id":{"anyOf":[{"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)$"},{"type":"null"}],"description":"The id of the person's own template or custom role, as GET \/v1\/roles lists it and PUT \/v1\/members\/{id}\/role accepts. Null when they have none: a built-in role, or (with role restricted) access from groups only."},"agents":{"type":"array","items":{"type":"object","properties":{"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)$"},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"}},"required":["id","address","name"],"additionalProperties":false}},"mail_access":{"description":"What their access lets them do with mail now. read false: they cannot read mail here; their role must give mail:read (in delegated mode they also need their own mailbox or an access entry). Only for a caller holding members:read; absent when it could not be read in time.","type":"object","properties":{"read":{"type":"boolean","description":"Holds mail:read, over every mailbox or only some."},"send":{"type":"boolean","description":"Holds mail:send, from every address or only some."}},"required":["read","send"],"additionalProperties":false}},"required":["user_id","name","work_email","role","role_name","role_id","agents"],"additionalProperties":false}},"cursor":{"anyOf":[{"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)$"},{"type":"null"}]},"can_invite":{"type":"boolean"}},"required":["items","cursor","can_invite"],"additionalProperties":false},"Projects_listProjects_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"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)$"},"name":{"type":"string"},"description":{"type":"string"},"repository":{"type":["string","null"]},"color":{"type":"string"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"members":{"type":"array","items":{"type":"object","properties":{"user_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)$"},"name":{"type":"string"},"work_email":{"anyOf":[{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"role":{"type":"string","enum":["owner","admin","member","restricted"]},"role_name":{"type":"string"},"role_id":{"anyOf":[{"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)$"},{"type":"null"}],"description":"The id of the person's own template or custom role, as GET \/v1\/roles lists it and PUT \/v1\/members\/{id}\/role accepts. Null when they have none: a built-in role, or (with role restricted) access from groups only."},"agents":{"type":"array","items":{"type":"object","properties":{"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)$"},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"}},"required":["id","address","name"],"additionalProperties":false}},"recipient":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"work_email"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"agent"},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["kind","address"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"private_agent"}},"required":["kind"],"additionalProperties":false}]},"destination":{"anyOf":[{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"recipient_available":{"type":"boolean"}},"required":["user_id","name","work_email","role","role_name","role_id","agents","recipient","destination","recipient_available"],"additionalProperties":false}},"agents":{"type":"array","items":{"type":"object","properties":{"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)$"},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"}},"required":["id","address","name"],"additionalProperties":false}},"access":{"type":"object","properties":{"can_manage":{"type":"boolean"},"can_invite":{"type":"boolean"}},"required":["can_manage","can_invite"],"additionalProperties":false}},"required":["id","name","description","repository","color","version","members","agents","access"],"additionalProperties":false}},"cursor":{"anyOf":[{"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)$"},{"type":"null"}]}},"required":["items","cursor"],"additionalProperties":false},"Projects_createProject_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"request_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)$"},"name":{"type":"string","minLength":1,"maxLength":60},"description":{"default":"","type":"string","maxLength":4096},"repository":{"default":null,"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}]},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"members":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"user_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)$"},"recipient":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"work_email"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"agent"},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["kind","address"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"private_agent"}},"required":["kind"],"additionalProperties":false}]}},"required":["user_id","recipient"],"additionalProperties":false}},"agent_addresses":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}},"required":["request_id","name","color","members","agent_addresses"],"additionalProperties":false},"Projects_createProject_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"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)$"},"name":{"type":"string"},"description":{"type":"string"},"repository":{"type":["string","null"]},"color":{"type":"string"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"members":{"type":"array","items":{"type":"object","properties":{"user_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)$"},"name":{"type":"string"},"work_email":{"anyOf":[{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"role":{"type":"string","enum":["owner","admin","member","restricted"]},"role_name":{"type":"string"},"role_id":{"anyOf":[{"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)$"},{"type":"null"}],"description":"The id of the person's own template or custom role, as GET \/v1\/roles lists it and PUT \/v1\/members\/{id}\/role accepts. Null when they have none: a built-in role, or (with role restricted) access from groups only."},"agents":{"type":"array","items":{"type":"object","properties":{"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)$"},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"}},"required":["id","address","name"],"additionalProperties":false}},"recipient":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"work_email"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"agent"},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["kind","address"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"private_agent"}},"required":["kind"],"additionalProperties":false}]},"destination":{"anyOf":[{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"recipient_available":{"type":"boolean"}},"required":["user_id","name","work_email","role","role_name","role_id","agents","recipient","destination","recipient_available"],"additionalProperties":false}},"agents":{"type":"array","items":{"type":"object","properties":{"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)$"},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"}},"required":["id","address","name"],"additionalProperties":false}},"access":{"type":"object","properties":{"can_manage":{"type":"boolean"},"can_invite":{"type":"boolean"}},"required":["can_manage","can_invite"],"additionalProperties":false}},"required":["id","name","description","repository","color","version","members","agents","access"],"additionalProperties":false},"Projects_updateProject_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"name":{"type":"string","minLength":1,"maxLength":60},"description":{"default":"","type":"string","maxLength":4096},"repository":{"default":null,"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}]},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"members":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"user_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)$"},"recipient":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"work_email"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"agent"},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["kind","address"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"private_agent"}},"required":["kind"],"additionalProperties":false}]}},"required":["user_id","recipient"],"additionalProperties":false}},"agent_addresses":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}},"required":["version","name","color","members","agent_addresses"],"additionalProperties":false},"Projects_deleteProject_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"],"additionalProperties":false},"Projects_listProjectInvitations_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"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)$"},"project_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)$"},"email":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"status":{"type":"string","enum":["preparing","pending","accepted","expired","revoked","unknown","failed"]},"delivery":{"type":"string","enum":["accepted","unknown","rejected"]},"expires_at":{"type":["string","null"]},"revoked_reason":{"anyOf":[{"type":"string","enum":["inviter_departed","inviter_lost_permission","accepted_after_inviter_departed"]},{"type":"null"}]},"failure_reason":{"type":"string","enum":["member_limit","already_member","already_invited","invalid_address","not_allowed","refused"]}},"required":["id","project_id","email","status","delivery","expires_at"],"additionalProperties":false}}},"required":["items"],"additionalProperties":false},"Projects_inviteProjectMember_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"request_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)$"},"email":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["request_id","email"],"additionalProperties":false},"Projects_inviteProjectMember_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"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)$"},"project_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)$"},"email":{"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"status":{"type":"string","enum":["preparing","pending","accepted","expired","revoked","unknown","failed"]},"delivery":{"type":"string","enum":["accepted","unknown","rejected"]},"expires_at":{"type":["string","null"]},"revoked_reason":{"anyOf":[{"type":"string","enum":["inviter_departed","inviter_lost_permission","accepted_after_inviter_departed"]},{"type":"null"}]},"failure_reason":{"type":"string","enum":["member_limit","already_member","already_invited","invalid_address","not_allowed","refused"]}},"required":["id","project_id","email","status","delivery","expires_at"],"additionalProperties":false},"ListMailboxAccessResult":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"mailbox_address":{"type":["string","null"]},"mailbox_domain":{"type":["string","null"]},"mailbox_kind":{"anyOf":[{"type":"string","enum":["personal","shared","agent","app"]},{"type":"null"}]},"principal_kind":{"type":"string","enum":["member","group","agent","app","address"]},"principal_user_id":{"type":["string","null"]},"principal_group_id":{"type":["string","null"]},"principal_agent_connection_id":{"type":["string","null"]},"principal_address":{"type":["string","null"]},"actions":{"type":"array","items":{"type":"string","enum":["read","draft","send_as","send_on_behalf"]}},"depth":{"type":"string","enum":["metadata","content","raw"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","mailbox_address","mailbox_domain","mailbox_kind","principal_kind","principal_user_id","principal_group_id","principal_agent_connection_id","principal_address","actions","depth","created_at","updated_at"],"additionalProperties":{}}},"note":{"description":"Only with no entries: says where each member's mail access comes from instead.","type":"string"}},"required":["entries"],"additionalProperties":{}},"SetMailboxAccessRequest":{"type":"object","properties":{"entries":{"maxItems":500,"type":"array","items":{"type":"object","properties":{"principal":{"type":"object","properties":{"kind":{"type":"string","enum":["member","group","agent","address"]},"id":{"type":"string","minLength":1,"maxLength":320}},"required":["kind","id"],"additionalProperties":false},"actions":{"minItems":1,"maxItems":4,"type":"array","items":{"type":"string","enum":["read","draft","send_as","send_on_behalf"]}},"depth":{"default":"content","description":"Deprecated and ignored: every scope that reaches mail reads the full email, and raw MIME follows mail.raw:read. New entries record content.","type":"string","enum":["metadata","content","raw"]}},"required":["principal","actions"],"additionalProperties":false}}},"required":["entries"],"additionalProperties":false},"SetMailboxAccessResult":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"mailbox_address":{"type":["string","null"]},"mailbox_domain":{"type":["string","null"]},"mailbox_kind":{"anyOf":[{"type":"string","enum":["personal","shared","agent","app"]},{"type":"null"}]},"principal_kind":{"type":"string","enum":["member","group","agent","app","address"]},"principal_user_id":{"type":["string","null"]},"principal_group_id":{"type":["string","null"]},"principal_agent_connection_id":{"type":["string","null"]},"principal_address":{"type":["string","null"]},"actions":{"type":"array","items":{"type":"string","enum":["read","draft","send_as","send_on_behalf"]}},"depth":{"type":"string","enum":["metadata","content","raw"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","mailbox_address","mailbox_domain","mailbox_kind","principal_kind","principal_user_id","principal_group_id","principal_agent_connection_id","principal_address","actions","depth","created_at","updated_at"],"additionalProperties":{}}}},"required":["entries"],"additionalProperties":{}},"ListGroupsResult":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"role_id":{"type":["string","null"]},"members":{"type":"array","items":{"type":"string"}}},"required":["id","name","description","role_id","members"],"additionalProperties":{}}}},"required":["groups"],"additionalProperties":{}},"CreateGroupRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"role":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}]},"members":{"maxItems":1000,"type":"array","items":{"type":"string","format":"uuid"}}},"required":["name"],"additionalProperties":false},"CreateGroupResult":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"role_id":{"type":["string","null"]},"members":{"type":"array","items":{"type":"string"}}},"required":["id","name","description","role_id","members"],"additionalProperties":{}},"UpdateGroupRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"role":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}]},"members":{"maxItems":1000,"type":"array","items":{"type":"string","format":"uuid"}}},"additionalProperties":false},"UpdateGroupResult":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"role_id":{"type":["string","null"]},"members":{"type":"array","items":{"type":"string"}}},"required":["id","name","description","role_id","members"],"additionalProperties":{}},"DeleteGroupResult":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string"}},"required":["deleted","id"],"additionalProperties":{}},"Credentials_listCredentials_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["org_key","function_key","connected_key","oauth","agent_delegate","cli_user_access"]},"id":{"type":"string"},"name":{"type":["string","null"]},"prefix":{"type":["string","null"]},"client":{"anyOf":[{"type":"object","properties":{"client_id":{"type":"string"},"name":{"type":["string","null"]}},"required":["client_id","name"],"additionalProperties":false},{"type":"null"}]},"grant":{"type":"object","properties":{"ref":{"type":["string","null"]},"type":{"type":"string","enum":["template","concrete","computed"]},"template":{"type":["string","null"]},"permissions":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mail_scope":{},"resources":{"anyOf":[{},{"type":"null"}]}},"required":["ref","type","template","permissions","mail_scope","resources"],"additionalProperties":{}},"creator":{"type":"object","properties":{"kind":{"type":["string","null"]},"id":{"type":["string","null"]},"email":{"type":["string","null"]},"name":{"type":["string","null"]},"status":{"type":"string","enum":["member","departed","unknown","agent_before_claim","system"]}},"required":["kind","id","email","status"],"additionalProperties":{}},"created_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_used_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_used_ip":{"type":["string","null"]},"expires_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"}]},"conditions":{"anyOf":[{"type":"object","properties":{"ip_allowlist":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"rate_limit_per_minute":{"type":["number","null"]},"suspended_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":["ip_allowlist","rate_limit_per_minute","suspended_at"],"additionalProperties":false},{"type":"null"}]},"flags":{"type":"array","items":{"type":"string"}},"actions":{"type":"array","items":{"type":"string","enum":["revoke","rotate","edit","transfer","suggest"]}},"cli":{"type":"object","properties":{"device_name":{"type":["string","null"]},"user_agent":{"type":["string","null"]}},"required":["device_name","user_agent"],"additionalProperties":false},"belongs_to":{"type":"object","properties":{"function":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"],"additionalProperties":false},{"type":"null"}]},"agent_address":{"type":["string","null"]},"person":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"name":{"type":["string","null"]}},"required":["id","email"],"additionalProperties":false},{"type":"null"}]}},"required":["function","agent_address","person"],"additionalProperties":false},"address_warnings":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"reason":{"type":"string"},"description":{"type":"string"}},"required":["address","reason","description"],"additionalProperties":{}},"description":"Notes on the key's grant, worked out from it on every read: whole_mailbox, an address or domain listed beside email rules, which the key reads in full; no_send_address, with address empty, a key that can send but lists only email rules. The notes that need the organization's mailboxes and domains (member_address, unowned_domain) are given only when the grant is written. Only rules not deleted and shares not ended count. Empty, or absent, for a key on a template, and empty for a revoked key."}},"required":["kind","id","name","prefix","client","grant","creator","created_at","last_used_at","last_used_ip","expires_at","conditions","flags","actions"],"additionalProperties":{}}},"counts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}}},"required":["items","counts"],"additionalProperties":{}},"Credentials_revokeCredential_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"revoked":{"type":"number"},"kind":{"type":"string","enum":["org_key","function_key","connected_key","oauth","agent_delegate","cli_user_access"]},"id":{"type":"string"},"kept":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":false}},"already_revoked":{"description":"true when the API key was already revoked. revoked then counts only the keys it replaced that were revoked now. Absent otherwise. GET \/v1\/keys\/{id} says when and by whom, to a caller who may read the key.","type":"boolean"},"keep_failed":{"description":"Items you chose to keep that could not be taken over after the revocation. Each stopped with it, unless someone else had taken it over in the meantime.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":false}},"standing_objects":{"description":"What the credential set up or owns, listed for review, and for an API key also what it carries on for the keys it was rotated from. Revoking deletes none of it.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","description":"endpoint, function_trigger, pull_subscription, recipient_route, filter_rule or wake_schedule."},"id":{"type":"string"},"label":{"type":"string","description":"A webhook's scheme and host, followed by \"\/... (path and query hidden)\" when its URL has more (GET \/v1\/endpoints\/{id} has the whole URL), a pull subscription's name (or pull when it has none), a route's or filter's pattern, or a wake schedule's command and the address it wakes (command to address). A Function trigger's label is function: look it up by id."},"org_owned":{"type":"boolean","description":"true: the organization owns it, and it keeps working. false: the credential owns it itself, as with everything a third-party app or agent delegate made, a webhook made with a narrowed API key, or a wake schedule made with a key that does not cover super_admin. It keeps working only while someone can still act for it, such as a key of the same rotation that still works, or whoever it was given to or kept it. Revoking a third-party app or agent delegate turns these off, except that a block filter it made keeps blocking, within the reach the app had, unless the person revoking chose to stop it."},"still_acting":{"description":"For an API key: whether anything still acts for it after this revoke (the organization, a key of the same rotation that still works, or whoever took it over). Null when that could not be read.","type":["boolean","null"]},"acting_key_id":{"description":"For an API key: the key that still acts for it, shown to a caller who may read the organization's API keys. Null otherwise, and when no key does.","type":["string","null"]},"delivering":{"description":"For an API key's webhook: whether it is still sent received mail, as GET \/v1\/endpoints\/{id} delivery.delivering says. Null when that could not be read.","type":["boolean","null"]}},"required":["kind","id","label","org_owned"],"additionalProperties":{}}},"predecessors_revoked_count":{"description":"For an API key: how many keys it replaced in its rotation, still in their overlap, were revoked with it. Keys that replaced it never are.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"predecessors_left":{"description":"For an API key: how many such keys were left working because you may not revoke them. They work until their overlap ends.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"predecessors_revoked":{"description":"The ids of the keys counted in predecessors_revoked_count, to a caller who may read the organization's API keys.","type":"array","items":{"type":"string"}},"predecessors_not_revoked":{"description":"The ids of the keys counted in predecessors_left, to a caller who may read the organization's API keys.","type":"array","items":{"type":"string"}}},"required":["revoked","kind","id"],"additionalProperties":{}},"Credentials_revokeCredential_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"kind":{"type":"string","enum":["org_key","function_key","connected_key","oauth","agent_delegate","cli_user_access"]},"id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"reason":{"type":"string","maxLength":200},"choices":{"maxItems":2000,"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["endpoint","function_trigger","pull_subscription","recipient_route","filter_rule","scheduled_send","repeating_send","wake_schedule","function"]},"id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"decision":{"type":"string","enum":["keep","stop"]}},"required":["kind","id","decision"],"additionalProperties":false}}},"required":["kind","id"],"additionalProperties":false},"Credentials_revokeAllCredentials_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"api_keys_revoked":{"type":"number"},"connected_keys_revoked":{"type":"number"},"oauth_grants_revoked":{"type":"number"},"cli_logins_revoked":{"type":"number"},"functions_suspended":{"type":"number"},"org_function_key":{"type":"string","enum":["rotated","rotation_incomplete","rotation_disabled","none"]},"per_function_keys_to_rotate":{"type":"array","items":{"type":"string"}},"per_function_rotation_queued":{"type":"array","items":{"type":"string"}}},"required":["api_keys_revoked","connected_keys_revoked","oauth_grants_revoked","functions_suspended","org_function_key","per_function_keys_to_rotate"],"additionalProperties":{}},"Credentials_revokeAllCredentials_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"since":{"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)))$"},"suspend_function_ids":{"default":[],"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}$"}},"confirm":{"type":"string","const":"revoke all"}},"required":["since","confirm"],"additionalProperties":false},"Credentials_listCredentialEvents_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"occurred_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)))$"},"event_type":{"type":"string"},"credential_kind":{"type":["string","null"]},"credential_id":{"type":["string","null"]},"credential_prefix":{"type":["string","null"]},"actor_kind":{"type":["string","null"]},"actor_id":{"type":["string","null"]},"grant_ref":{"type":["string","null"]},"catalog_version":{"type":["string","null"]},"request_id":{"type":["string","null"]},"details":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","occurred_at","event_type","credential_kind","credential_id","credential_prefix","actor_kind","actor_id","grant_ref","catalog_version","request_id","details"],"additionalProperties":{}}},"next_cursor":{"type":["string","null"]}},"required":["items","next_cursor"],"additionalProperties":{}},"Credentials_getCredentialPolicy_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"max_key_lifetime_days":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"require_key_expiry":{"type":"boolean"},"super_admin_key_creators":{"type":"string","enum":["owners_and_admins","owners"]},"members_may_mint_keys":{"type":"boolean"},"allow_org_mail_scope_keys":{"type":"boolean"},"cap_agents_below_person":{"type":"boolean"},"removal_suspends_created":{"type":"boolean"},"oauth_consent_admin_only":{"type":"boolean"},"cli_writes_require_approval":{"type":"boolean"},"cli_access":{"type":"string","enum":["owners_admins_and_operators","owners_and_admins","all_members","off"]}},"required":["max_key_lifetime_days","require_key_expiry","super_admin_key_creators","members_may_mint_keys","allow_org_mail_scope_keys","cap_agents_below_person","removal_suspends_created","oauth_consent_admin_only"],"additionalProperties":{}},"Credentials_updateCredentialPolicy_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"max_key_lifetime_days":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"require_key_expiry":{"type":"boolean"},"super_admin_key_creators":{"type":"string","enum":["owners_and_admins","owners"]},"members_may_mint_keys":{"type":"boolean"},"allow_org_mail_scope_keys":{"type":"boolean"},"cap_agents_below_person":{"type":"boolean"},"removal_suspends_created":{"type":"boolean"},"oauth_consent_admin_only":{"type":"boolean"},"cli_writes_require_approval":{"type":"boolean"},"cli_access":{"type":"string","enum":["owners_admins_and_operators","owners_and_admins","all_members","off"]}},"required":["max_key_lifetime_days","require_key_expiry","super_admin_key_creators","members_may_mint_keys","allow_org_mail_scope_keys","cap_agents_below_person","removal_suspends_created","oauth_consent_admin_only"],"additionalProperties":{}},"Credentials_updateCredentialPolicy_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"max_key_lifetime_days":{"anyOf":[{"type":"integer","minimum":1,"maximum":3650},{"type":"null"}]},"require_key_expiry":{"type":"boolean"},"super_admin_key_creators":{"type":"string","enum":["owners_and_admins","owners"]},"members_may_mint_keys":{"type":"boolean"},"allow_org_mail_scope_keys":{"type":"boolean"},"cap_agents_below_person":{"type":"boolean"},"removal_suspends_created":{"type":"boolean"},"oauth_consent_admin_only":{"type":"boolean"},"cli_writes_require_approval":{"type":"boolean"},"cli_access":{"type":"string","enum":["owners_admins_and_operators","owners_and_admins","all_members","off"]}},"additionalProperties":false},"Credentials_getKeyBuilder_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"enabled":{"type":"boolean"},"presets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"mail_reach":{"type":"string","enum":["org","listed","chosen"]},"available":{"type":"boolean"},"reason":{"type":["string","null"]},"message":{"type":["string","null"]}},"required":["id","label","description","available","reason","message"],"additionalProperties":{}}},"mail_limits_available":{"description":"Whether a key limited to some mail (addresses, domains or email rules) would be created now. While false, creating one is refused, so a client offers neither the listed-address presets nor a listed mail reach for a custom key.","type":"boolean"},"org_reach_available":{"description":"Whether the credential policy lets a new key reach every mailbox. While false, creating one is refused, so a client offers no preset that reaches every mailbox, and a custom key lists its addresses or domains. With mail_limits_available false as well, no key can be created and every preset is unavailable.","type":"boolean"},"matrix":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"read":{"type":"array","items":{"type":"string"}},"write":{"type":"array","items":{"type":"string"}},"resource":{"type":["string","null"]},"org_wide_only":{"type":"boolean"}},"required":["id","label","description","read","write","resource"],"additionalProperties":{}}},"policy":{"type":"object","properties":{"max_key_lifetime_days":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"require_key_expiry":{"type":"boolean"},"super_admin_key_creators":{"type":"string","enum":["owners_and_admins","owners"]},"members_may_mint_keys":{"type":"boolean"},"allow_org_mail_scope_keys":{"type":"boolean"},"cap_agents_below_person":{"type":"boolean"},"removal_suspends_created":{"type":"boolean"},"oauth_consent_admin_only":{"type":"boolean"},"cli_writes_require_approval":{"type":"boolean"},"cli_access":{"type":"string","enum":["owners_admins_and_operators","owners_and_admins","all_members","off"]}},"required":["max_key_lifetime_days","require_key_expiry","super_admin_key_creators","members_may_mint_keys","allow_org_mail_scope_keys","cap_agents_below_person","removal_suspends_created","oauth_consent_admin_only"],"additionalProperties":{}}},"required":["enabled","presets","matrix","policy"],"additionalProperties":{}},"Credentials_createKey_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"key":{"type":"string"},"key_prefix":{"type":"string"},"grant":{"type":"object","properties":{"template":{"type":["string","null"]},"credential_grant_id":{"type":["string","null"]},"permissions":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mail_scope":{},"resources":{"anyOf":[{},{"type":"null"}]}},"required":["template","credential_grant_id","permissions","mail_scope","resources"],"additionalProperties":{}},"expires_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"}]},"conditions":{"anyOf":[{},{"type":"null"}]},"warnings":{"type":"array","items":{"type":"object","properties":{"permission":{"type":"string"},"adds":{"type":"array","items":{"type":"string"}},"why":{"type":"string"},"description":{"type":"string"}},"required":["permission","adds","why","description"],"additionalProperties":{}}},"address_warnings":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"reason":{"type":"string"},"description":{"type":"string"}},"required":["address","reason","description"],"additionalProperties":{}}},"replaced_key_ids":{"type":"array","items":{"type":"string"}}},"required":["id","name","key","key_prefix","grant","expires_at","conditions","warnings"],"additionalProperties":{}},"Credentials_createKey_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"grant":{"anyOf":[{"type":"string","enum":["super_admin","send_only","read_only","own_addresses","own_addresses_read_only","custom"]},{"type":"object","properties":{"preset":{"type":"string","enum":["super_admin","send_only","read_only","own_addresses","own_addresses_read_only","custom"]},"matrix":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","enum":["none","read","write"]}},"permissions":{"maxItems":200,"type":"array","items":{"type":"string"}},"addresses":{"maxItems":100,"type":"array","items":{"type":"string"}},"domains":{"maxItems":50,"type":"array","items":{"type":"string"}},"rules":{"maxItems":20,"type":"array","items":{"type":"string"}},"depth":{"description":"Deprecated: every key that reaches mail reads the full email. Ignored, except that raw still adds mail.raw:read (Raw mail) to an address preset or a custom grant.","type":"string","enum":["metadata","content","raw"]},"resources":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"maxItems":100,"type":"array","items":{"type":"string"}}},"mail_scope":{"type":"string","const":"org"}},"required":["preset"],"additionalProperties":false}]},"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)))$"},"expires_in_days":{"type":"integer","minimum":1,"maximum":3650},"conditions":{"type":"object","properties":{"ip_allowlist":{"anyOf":[{"minItems":1,"maxItems":100,"type":"array","items":{"type":"string"}},{"type":"null"}]},"rate_limit_per_minute":{"anyOf":[{"type":"integer","minimum":1,"maximum":100000},{"type":"null"}]}},"additionalProperties":false},"onboarding":{"type":"boolean"}},"additionalProperties":false,"required":["grant"]},"Credentials_rotateKey_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"key_prefix":{"type":"string"},"expires_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"}]},"previous_key_id":{"type":"string"},"name":{"description":"The successor's name: the rotated key's, which it keeps.","type":["string","null"]},"grant":{"description":"The grant the successor holds, as a create answers it: the rotated key's own, carried over unchanged (rules, addresses and domains included).","type":"object","properties":{"template":{"type":["string","null"]},"credential_grant_id":{"type":["string","null"]},"permissions":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mail_scope":{},"resources":{"anyOf":[{},{"type":"null"}]}},"required":["template","credential_grant_id","permissions","mail_scope","resources"],"additionalProperties":{}},"rotated_from":{"description":"The rotated key: previous_key_id, under the name GET \/v1\/keys\/{id} gives it.","type":"string"},"previous_key_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)))$","description":"When the old key stops working: the time of the rotation plus overlap_seconds (24 hours when it was omitted, now when it was 0), or the key's own expires_at, or the expires_at given to the new key, whichever comes first."}},"required":["id","key","key_prefix","previous_key_id","previous_key_expires_at"],"additionalProperties":{}},"Credentials_rotateKey_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"overlap_seconds":{"description":"How long the old key keeps working, in seconds, up to 604800 (7 days), and never past its own expires_at. 86400 (24 hours) when omitted. 0 stops the old key at once: use it when rotating because the secret leaked.","type":"integer","minimum":0,"maximum":604800},"expires_at":{"description":"The new key's expiry. Omitted, it keeps the old key's expiry: rotating alone never extends a key. A time sets it, within the organization's credential policy (its maximum key lifetime counts from now); null makes it never expire, where the policy allows. Needs keys:manage, as changing a key does. The old key stops when the overlap ends, or at this expiry if that comes first.","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"}]}},"additionalProperties":false},"Credentials_updateKey_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"changed":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"object","properties":{"permission":{"type":"string"},"adds":{"type":"array","items":{"type":"string"}},"why":{"type":"string"},"description":{"type":"string"}},"required":["permission","adds","why","description"],"additionalProperties":{}}},"address_warnings":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"reason":{"type":"string"},"description":{"type":"string"}},"required":["address","reason","description"],"additionalProperties":{}}},"rules_added":{"description":"Each rule (or share) the edit put on a key that reads mail, with granted_emails, how many emails it has already shared, which the key can now read while it is active: counted up to 10,000, with more: true past that; null when not counted.","type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"granted_emails":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"more":{"type":"boolean","const":true}},"required":["id","granted_emails"],"additionalProperties":{}}},"grant_unguarded":{"description":"Present, as true, when the grant was changed without expected_grant_ref, so the change was not checked against changes made since the key was read. Send grant.ref from GET \/v1\/keys\/{id} as expected_grant_ref to have such a change refused instead (409 conflict, details.reason grant_changed).","type":"boolean","const":true}},"required":["id","changed","warnings"],"additionalProperties":{}},"Credentials_updateKey_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"expires_at":{"description":"A new expiry. A later time extends the key and an earlier one ends it sooner, within the organization's credential policy (its maximum key lifetime counts from now); null removes the expiry, where the policy allows. A rotated-out key's expiry can only be brought forward, and an expired key's cannot be changed.","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"}]},"grant":{"anyOf":[{"type":"string","enum":["super_admin","send_only","read_only","own_addresses","own_addresses_read_only","custom"]},{"type":"object","properties":{"preset":{"type":"string","enum":["super_admin","send_only","read_only","own_addresses","own_addresses_read_only","custom"]},"matrix":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","enum":["none","read","write"]}},"permissions":{"maxItems":200,"type":"array","items":{"type":"string"}},"addresses":{"maxItems":100,"type":"array","items":{"type":"string"}},"domains":{"maxItems":50,"type":"array","items":{"type":"string"}},"rules":{"maxItems":20,"type":"array","items":{"type":"string"}},"depth":{"description":"Deprecated: every key that reaches mail reads the full email. Ignored, except that raw still adds mail.raw:read (Raw mail) to an address preset or a custom grant.","type":"string","enum":["metadata","content","raw"]},"resources":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"maxItems":100,"type":"array","items":{"type":"string"}}},"mail_scope":{"type":"string","const":"org"}},"required":["preset"],"additionalProperties":false}]},"expected_grant_ref":{"type":"string","minLength":1,"maxLength":100},"conditions":{"anyOf":[{"type":"object","properties":{"ip_allowlist":{"anyOf":[{"minItems":1,"maxItems":100,"type":"array","items":{"type":"string"}},{"type":"null"}]},"rate_limit_per_minute":{"anyOf":[{"type":"integer","minimum":1,"maximum":100000},{"type":"null"}]}},"additionalProperties":false},{"type":"null"}]},"suspended":{"type":"boolean"}},"additionalProperties":false},"Credentials_getKeySuggestion_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"key_id":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"matrix":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"calls":{"type":"number"},"unmatched":{"type":"number"},"covers_super_admin":{"type":"boolean"},"unused":{"type":"boolean"},"warnings":{"type":"array","items":{"type":"object","properties":{"permission":{"type":"string"},"adds":{"type":"array","items":{"type":"string"}},"why":{"type":"string"},"description":{"type":"string"}},"required":["permission","adds","why","description"],"additionalProperties":{}}},"super_admin_only_calls":{"type":"number"},"areas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"level":{"type":"string","enum":["read","write"]},"partial":{"type":"boolean"}},"required":["id","label","level","partial"],"additionalProperties":{}}},"narrowing":{"type":"object","properties":{"available":{"type":"boolean"},"reason":{"type":["string","null"]},"message":{"type":["string","null"]}},"required":["available","reason","message"],"additionalProperties":{}},"mail_scope":{"anyOf":[{"type":"object","properties":{"available":{"type":"boolean"},"unavailable_message":{"type":["string","null"]},"addresses":{"type":"array","items":{"type":"string"}},"domains":{"type":"array","items":{"type":"string"}},"depth":{"type":"string","enum":["metadata","content","raw"],"description":"Deprecated: raw when the key downloaded raw MIME, otherwise content."},"depth_reason":{"type":"string"},"complete":{"type":"boolean"},"blocked_by":{"type":"array","items":{"type":"string"}},"notes":{"type":"array","items":{"type":"string"}}},"required":["available","unavailable_message","addresses","domains","depth","depth_reason","complete","blocked_by","notes"],"additionalProperties":{}},{"type":"null"}]},"usage":{"type":"object","properties":{"days":{"type":"number"},"daily":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string"},"requests":{"type":"number"},"errors":{"type":"number"}},"required":["day","requests","errors"],"additionalProperties":{}}},"total_requests":{"type":"number"},"error_requests":{"type":"number"},"active_days":{"type":"number"},"distinct_routes":{"type":"number"},"top_route_share":{"type":["number","null"]},"routes":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"route":{"type":["string","null"]},"requests":{"type":"number"},"permissions":{"type":"array","items":{"type":"string"}},"super_admin_only":{"type":"boolean"}},"required":["method","route","requests","permissions","super_admin_only"],"additionalProperties":{}}},"routes_truncated":{"type":"boolean"},"history_truncated":{"type":"boolean"},"mailboxes":{"anyOf":[{"type":"object","properties":{"read":{"type":["number","null"]},"sent_from":{"type":["number","null"]},"complete":{"type":"boolean"},"unavailable_message":{"type":["string","null"]}},"required":["read","sent_from","complete","unavailable_message"],"additionalProperties":{}},{"type":"null"}]}},"required":["days","daily","total_requests","error_requests","active_days","distinct_routes","top_route_share","routes","routes_truncated","history_truncated","mailboxes"],"additionalProperties":{}}},"required":["key_id","permissions","matrix","calls","unmatched","covers_super_admin","unused","warnings"],"additionalProperties":{}},"Credentials_listKeys_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["org_key","function_key","connected_key"]},"name":{"type":["string","null"]},"key_prefix":{"type":"string"},"grant":{"type":"object","properties":{"ref":{"description":"The grant this read shows. Send it as expected_grant_ref on PATCH \/v1\/keys\/{id} so the change is refused (409 conflict, details.reason grant_changed) if the grant has changed since this read.","type":"string"},"template":{"type":["string","null"]},"permissions":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mail_scope":{},"resources":{"description":"The Functions, domains, endpoints or routes the key is limited to, by type; null when it has no such limits. A grant change on a key with limits must drop them on purpose, with grant.resources {} (otherwise 409 conflict, details.reason key_has_resource_limits).","anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}},{"type":"null"}]}},"required":["template","permissions","mail_scope"],"additionalProperties":{}},"agent_address":{"type":["string","null"]},"created_by_user_id":{"type":["string","null"]},"created_at":{"type":["string","null"]},"last_used_at":{"type":["string","null"]},"expires_at":{"type":["string","null"]},"suspended":{"type":"boolean"},"status":{"description":"Whether the key works now: active; suspended (refused until the suspension is lifted); expired (refused since expires_at); inactive, for an agent connection key whose connection cannot act (not claimed, revoked, its domain not verified, or its owner no longer a member); or revoked, which it stays for good and which no unrevoked key ever shows. A revoked key is listed only with include_revoked=true, and GET \/v1\/keys\/{id} answers it. An endpoint's owner_key_status reports an expired key as suspended.","type":"string","enum":["active","suspended","expired","inactive","revoked"]},"revoked_at":{"description":"When the key was revoked (ISO 8601); null for a key that is not revoked.","type":["string","null"]},"revoked_by":{"description":"The user id of the person who revoked the key from their own dashboard session, step-up or Primitive CLI login; null for a key that is not revoked, and for one revoked any other way (by an API key, another app or token, or an automatic process such as secret scanning).","type":["string","null"]},"rotated_from":{"description":"The id of the key this key was rotated from (POST \/v1\/keys\/{id}\/rotate made this key in its place); null for a key not made by rotation, when the caller may not read that key, or when its record is gone. A revoked one is still named: GET \/v1\/keys\/{id} answers it.","type":["string","null"]},"rotated_to":{"description":"The id of the newest key rotated from this key that the caller may read; null until it is rotated, or when the caller may read no key rotated from it. A revoked one is still named: GET \/v1\/keys\/{id} answers it.","type":["string","null"]},"address_warnings":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"reason":{"type":"string"},"description":{"type":"string"}},"required":["address","reason","description"],"additionalProperties":{}},"description":"Notes on the key's grant, worked out from it on every read: whole_mailbox, an address or domain listed beside email rules, which the key reads in full; no_send_address, with address empty, a key that can send but lists only email rules. The notes that need the organization's mailboxes and domains (member_address, unowned_domain) are given only when the grant is written. Only rules not deleted and shares not ended count. Empty, or absent, for a key on a template, and empty for a revoked key."}},"required":["id","kind","name","key_prefix","grant","agent_address","created_by_user_id","created_at","last_used_at","expires_at","suspended"],"additionalProperties":{}}},"next_cursor":{"type":["string","null"]}},"required":["items","next_cursor"],"additionalProperties":{}},"Credentials_getKey_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["org_key","function_key","connected_key"]},"name":{"type":["string","null"]},"key_prefix":{"type":"string"},"grant":{"type":"object","properties":{"ref":{"description":"The grant this read shows. Send it as expected_grant_ref on PATCH \/v1\/keys\/{id} so the change is refused (409 conflict, details.reason grant_changed) if the grant has changed since this read.","type":"string"},"template":{"type":["string","null"]},"permissions":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mail_scope":{},"resources":{"description":"The Functions, domains, endpoints or routes the key is limited to, by type; null when it has no such limits. A grant change on a key with limits must drop them on purpose, with grant.resources {} (otherwise 409 conflict, details.reason key_has_resource_limits).","anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}},{"type":"null"}]}},"required":["template","permissions","mail_scope"],"additionalProperties":{}},"agent_address":{"type":["string","null"]},"created_by_user_id":{"type":["string","null"]},"created_at":{"type":["string","null"]},"last_used_at":{"type":["string","null"]},"expires_at":{"type":["string","null"]},"suspended":{"type":"boolean"},"status":{"description":"Whether the key works now: active; suspended (refused until the suspension is lifted); expired (refused since expires_at); inactive, for an agent connection key whose connection cannot act (not claimed, revoked, its domain not verified, or its owner no longer a member); or revoked, which it stays for good and which no unrevoked key ever shows. A revoked key is listed only with include_revoked=true, and GET \/v1\/keys\/{id} answers it. An endpoint's owner_key_status reports an expired key as suspended.","type":"string","enum":["active","suspended","expired","inactive","revoked"]},"revoked_at":{"description":"When the key was revoked (ISO 8601); null for a key that is not revoked.","type":["string","null"]},"revoked_by":{"description":"The user id of the person who revoked the key from their own dashboard session, step-up or Primitive CLI login; null for a key that is not revoked, and for one revoked any other way (by an API key, another app or token, or an automatic process such as secret scanning).","type":["string","null"]},"rotated_from":{"description":"The id of the key this key was rotated from (POST \/v1\/keys\/{id}\/rotate made this key in its place); null for a key not made by rotation, when the caller may not read that key, or when its record is gone. A revoked one is still named: GET \/v1\/keys\/{id} answers it.","type":["string","null"]},"rotated_to":{"description":"The id of the newest key rotated from this key that the caller may read; null until it is rotated, or when the caller may read no key rotated from it. A revoked one is still named: GET \/v1\/keys\/{id} answers it.","type":["string","null"]},"address_warnings":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"reason":{"type":"string"},"description":{"type":"string"}},"required":["address","reason","description"],"additionalProperties":{}},"description":"Notes on the key's grant, worked out from it on every read: whole_mailbox, an address or domain listed beside email rules, which the key reads in full; no_send_address, with address empty, a key that can send but lists only email rules. The notes that need the organization's mailboxes and domains (member_address, unowned_domain) are given only when the grant is written. Only rules not deleted and shares not ended count. Empty, or absent, for a key on a template, and empty for a revoked key."}},"required":["id","kind","name","key_prefix","grant","agent_address","created_by_user_id","created_at","last_used_at","expires_at","suspended"],"additionalProperties":{}},"OrgSettings_listMyGrants_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["oauth","cli_login"]},"id":{"type":"string"},"client":{"anyOf":[{"type":"object","properties":{"client_id":{"type":"string"},"name":{"type":["string","null"]},"uri":{"type":["string","null"]}},"required":["client_id","name","uri"],"additionalProperties":false},{"type":"null"}]},"device_name":{"type":["string","null"]},"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)))$"},"last_used_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"}]},"reuse_held":{"type":"boolean"},"logged_out":{"type":"boolean"}},"required":["kind","id","client","device_name","created_at","last_used_at","reuse_held","logged_out"],"additionalProperties":{}}},"next_cursor":{"type":["string","null"]}},"required":["items","next_cursor"],"additionalProperties":{}},"OrgSettings_revokeMyGrant_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string"},"revoked":{"type":"boolean","const":true}},"required":["id","kind","revoked"],"additionalProperties":{}},"OrgSettings_revokeMyGrant_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"kind":{"type":"string","enum":["oauth","cli_login"]}},"required":["kind"],"additionalProperties":false},"OrgSettings_listOAuthGrants_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"client":{"type":"object","properties":{"client_id":{"type":"string"},"name":{"type":["string","null"]},"uri":{"type":["string","null"]}},"required":["client_id","name","uri"],"additionalProperties":false},"user_id":{"type":"string"},"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)))$"},"last_used_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"}]},"reuse_held":{"type":"boolean"}},"required":["id","client","user_id","created_at","last_used_at","reuse_held"],"additionalProperties":{}}},"next_cursor":{"type":["string","null"]}},"required":["items","next_cursor"],"additionalProperties":{}},"OrgSettings_revokeOAuthGrant_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string"},"revoked":{"type":"boolean","const":true}},"required":["id","kind","revoked"],"additionalProperties":{}},"OrgSettings_getOrganization_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"logo_url":{"type":["string","null"]}},"required":["id","name","logo_url"],"additionalProperties":{}},"OrgSettings_updateOrganization_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"logo_url":{"type":["string","null"]}},"required":["id","name","logo_url"],"additionalProperties":{}},"OrgSettings_updateOrganization_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100}},"required":["name"],"additionalProperties":false},"OrgSettings_updateOrganizationLogo_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"logo_url":{"type":["string","null"]}},"required":["id","name","logo_url"],"additionalProperties":{}},"OrgSettings_updateOrganizationLogo_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"content_type":{"type":"string","enum":["image\/png","image\/jpeg","image\/gif","image\/webp"]},"data_base64":{"type":"string","minLength":1,"maxLength":1228804}},"required":["content_type","data_base64"],"additionalProperties":false},"OrgSettings_removeOrganizationLogo_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"logo_url":{"type":["string","null"]}},"required":["id","name","logo_url"],"additionalProperties":{}},"OrgSettings_getSearchSettings_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"semantic_search_opt_in":{"type":"boolean"},"paid_plan":{"type":"boolean"},"available":{"type":"boolean"},"hipaa_mode":{"type":"boolean"},"active":{"type":"boolean"}},"required":["semantic_search_opt_in","paid_plan","available","hipaa_mode","active"],"additionalProperties":{}},"OrgSettings_updateSearchSettings_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"semantic_search_opt_in":{"type":"boolean"},"paid_plan":{"type":"boolean"},"available":{"type":"boolean"},"hipaa_mode":{"type":"boolean"},"active":{"type":"boolean"}},"required":["semantic_search_opt_in","paid_plan","available","hipaa_mode","active"],"additionalProperties":{}},"OrgSettings_updateSearchSettings_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"semantic_search_opt_in":{"type":"boolean"}},"required":["semantic_search_opt_in"],"additionalProperties":false},"EmailNotifications_getEmailNotificationSettings_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"entitled":{"type":"boolean"},"kinds":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"class":{"type":"string","enum":["platform_required","org_requirable","optional","org_only"]},"entitlement_required":{"type":"boolean"},"entitled":{"type":"boolean"},"org_mode":{"anyOf":[{"type":"string","enum":["off","optional_on","optional_off","required"]},{"type":"null"}]},"org_mode_is_default":{"type":"boolean"},"org_modes":{"type":"array","items":{"type":"string","enum":["off","optional_on","optional_off","required"]}},"member_enabled":{"type":"boolean"},"member_can_change":{"type":"boolean"}},"required":["kind","label","description","class","entitlement_required","entitled","org_mode","org_mode_is_default","org_modes","member_enabled","member_can_change"],"additionalProperties":false}}},"required":["entitled","kinds"],"additionalProperties":false},"EmailNotifications_updateEmailNotificationSettings_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"entitled":{"type":"boolean"},"kinds":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"class":{"type":"string","enum":["platform_required","org_requirable","optional","org_only"]},"entitlement_required":{"type":"boolean"},"entitled":{"type":"boolean"},"org_mode":{"anyOf":[{"type":"string","enum":["off","optional_on","optional_off","required"]},{"type":"null"}]},"org_mode_is_default":{"type":"boolean"},"org_modes":{"type":"array","items":{"type":"string","enum":["off","optional_on","optional_off","required"]}},"member_enabled":{"type":"boolean"},"member_can_change":{"type":"boolean"}},"required":["kind","label","description","class","entitlement_required","entitled","org_mode","org_mode_is_default","org_modes","member_enabled","member_can_change"],"additionalProperties":false}}},"required":["entitled","kinds"],"additionalProperties":false},"EmailNotifications_updateEmailNotificationSettings_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"modes":{"type":"object","propertyNames":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"},"additionalProperties":{"type":"string","enum":["off","optional_on","optional_off","required"]}}},"required":["modes"],"additionalProperties":false},"EmailNotifications_getEmailNotificationPreferences_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"entitled":{"type":"boolean"},"kinds":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"class":{"type":"string","enum":["platform_required","org_requirable","optional","org_only"]},"entitlement_required":{"type":"boolean"},"entitled":{"type":"boolean"},"org_mode":{"anyOf":[{"type":"string","enum":["off","optional_on","optional_off","required"]},{"type":"null"}]},"org_mode_is_default":{"type":"boolean"},"org_modes":{"type":"array","items":{"type":"string","enum":["off","optional_on","optional_off","required"]}},"member_enabled":{"type":"boolean"},"member_can_change":{"type":"boolean"}},"required":["kind","label","description","class","entitlement_required","entitled","org_mode","org_mode_is_default","org_modes","member_enabled","member_can_change"],"additionalProperties":false}}},"required":["entitled","kinds"],"additionalProperties":false},"EmailNotifications_updateEmailNotificationPreferences_Result":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"entitled":{"type":"boolean"},"kinds":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"class":{"type":"string","enum":["platform_required","org_requirable","optional","org_only"]},"entitlement_required":{"type":"boolean"},"entitled":{"type":"boolean"},"org_mode":{"anyOf":[{"type":"string","enum":["off","optional_on","optional_off","required"]},{"type":"null"}]},"org_mode_is_default":{"type":"boolean"},"org_modes":{"type":"array","items":{"type":"string","enum":["off","optional_on","optional_off","required"]}},"member_enabled":{"type":"boolean"},"member_can_change":{"type":"boolean"}},"required":["kind","label","description","class","entitlement_required","entitled","org_mode","org_mode_is_default","org_modes","member_enabled","member_can_change"],"additionalProperties":false}}},"required":["entitled","kinds"],"additionalProperties":false},"EmailNotifications_updateEmailNotificationPreferences_Input":{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/schema","type":"object","properties":{"preferences":{"type":"object","propertyNames":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$"},"additionalProperties":{"type":"boolean"}}},"required":["preferences"],"additionalProperties":false},"ListRolesResult":{"type":"object","properties":{"roles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"kind":{"type":"string","enum":["builtin","template","custom"]},"name":{"type":"string"},"description":{"type":["string","null"]},"permissions":{"type":"array","items":{"type":"string"}},"mail_scope":{},"version":{"type":"number"},"catalog_version":{"type":["string","null"]},"predates_catalog":{"type":"boolean"}},"required":["id","key","kind","name","description","permissions","mail_scope","version","catalog_version","predates_catalog"],"additionalProperties":{}}},"templates":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}}},"required":["key","name","description","permissions"],"additionalProperties":{}}},"assignment":{"type":"object","properties":{"available":{"type":"boolean"},"reason":{"type":["string","null"]}},"required":["available","reason"],"additionalProperties":{}},"invitations":{"type":"object","properties":{"limited_roles_available":{"type":"boolean"},"members_read_all_mail":{"type":"boolean"}},"required":["limited_roles_available","members_read_all_mail"],"additionalProperties":false},"unassigned":{"description":"What role restricted means on a member with no role_id: no role of their own. Not a role, never assignable.","type":"object","properties":{"key":{"type":"string","const":"restricted"},"name":{"type":"string"},"description":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}}},"required":["key","name","description","permissions"],"additionalProperties":{}}},"required":["roles","templates","assignment"],"additionalProperties":{}},"CreateRoleRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"permissions":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"string","maxLength":100}},"mail_scope":{"anyOf":[{"type":"string","const":"org"},{"type":"object","properties":{"addresses":{"default":[],"maxItems":500,"type":"array","items":{"type":"string","maxLength":320}},"domains":{"default":[],"maxItems":100,"type":"array","items":{"type":"string","maxLength":253}},"depth":{"description":"Deprecated and ignored: every scope that reaches mail reads the full email, and raw MIME follows mail.raw:read.","type":"string","enum":["metadata","content","raw"]}},"additionalProperties":false},{"type":"object","properties":{"self":{"type":"boolean","const":true},"depth":{"description":"Deprecated and ignored: every scope that reaches mail reads the full email, and raw MIME follows mail.raw:read.","type":"string","enum":["metadata","content","raw"]}},"required":["self"],"additionalProperties":false}]}},"required":["name","permissions"],"additionalProperties":false},"CreateRoleResult":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"kind":{"type":"string","enum":["builtin","template","custom"]},"name":{"type":"string"},"description":{"type":["string","null"]},"permissions":{"type":"array","items":{"type":"string"}},"mail_scope":{},"version":{"type":"number"},"catalog_version":{"type":["string","null"]},"predates_catalog":{"type":"boolean"}},"required":["id","key","kind","name","description","permissions","mail_scope","version","catalog_version","predates_catalog"],"additionalProperties":{}},"UpdateRoleRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"permissions":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"string","maxLength":100}},"mail_scope":{"anyOf":[{"type":"string","const":"org"},{"type":"object","properties":{"addresses":{"default":[],"maxItems":500,"type":"array","items":{"type":"string","maxLength":320}},"domains":{"default":[],"maxItems":100,"type":"array","items":{"type":"string","maxLength":253}},"depth":{"description":"Deprecated and ignored: every scope that reaches mail reads the full email, and raw MIME follows mail.raw:read.","type":"string","enum":["metadata","content","raw"]}},"additionalProperties":false},{"type":"object","properties":{"self":{"type":"boolean","const":true},"depth":{"description":"Deprecated and ignored: every scope that reaches mail reads the full email, and raw MIME follows mail.raw:read.","type":"string","enum":["metadata","content","raw"]}},"required":["self"],"additionalProperties":false}]}},"additionalProperties":false},"UpdateRoleResult":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"kind":{"type":"string","enum":["builtin","template","custom"]},"name":{"type":"string"},"description":{"type":["string","null"]},"permissions":{"type":"array","items":{"type":"string"}},"mail_scope":{},"version":{"type":"number"},"catalog_version":{"type":["string","null"]},"predates_catalog":{"type":"boolean"}},"required":["id","key","kind","name","description","permissions","mail_scope","version","catalog_version","predates_catalog"],"additionalProperties":{}},"DeleteRoleResult":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string"}},"required":["deleted","id"],"additionalProperties":{}},"SetMemberRoleRequest":{"type":"object","properties":{"role":{"type":"string","minLength":1,"maxLength":100}},"required":["role"],"additionalProperties":false},"SetMemberRoleResult":{"type":"object","properties":{"user_id":{"type":"string"},"role":{"type":"string"},"role_id":{"type":"string"},"stored_role":{"type":"string"},"reduction":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]}},"required":["user_id","role","stored_role","reduction"],"additionalProperties":{}},"RemoveMemberResult":{"type":"object","properties":{"removed":{"type":"boolean","const":true},"id":{"type":"string"}},"required":["removed","id"],"additionalProperties":{}},"LeaveOrganizationResult":{"type":"object","properties":{"left":{"type":"boolean","const":true},"id":{"type":"string"}},"required":["left","id"],"additionalProperties":{}},"GetAccessModeResult":{"type":"object","properties":{"mode":{"type":"string","enum":["whole_org","delegated"]},"version":{"type":"number"},"changed_at":{"type":["string","null"]},"preview":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"notices":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"notices_truncated":{"type":"boolean"},"notices_unavailable":{"type":"boolean"},"narrowed_agents":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"narrowed_agents_truncated":{"type":"boolean"},"narrowed_agents_unavailable":{"type":"boolean"},"decisions":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"gate":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["mode","version","changed_at"],"additionalProperties":{}},"DecideAccessModeRequest":{"type":"object","properties":{"decisions":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["mailbox_loss","rule2_function","rule2_key","rule2_oauth_grant","api_key","function","github_binding","endpoint","recipient_route","filter_rule","pull_subscription","wake_schedule","function_trigger","mailbox_self_grant"]},"id":{"type":"string","minLength":1,"maxLength":400},"decision":{"type":"string","enum":["keep","narrow","revoke"]},"note":{"type":"string","maxLength":500},"confirm":{"type":"boolean"}},"required":["kind","id","decision"],"additionalProperties":false}}},"required":["decisions"],"additionalProperties":false},"DecideAccessModeResult":{"type":"object","properties":{"recorded":{"type":"number"}},"required":["recorded"],"additionalProperties":{}},"SetAccessModeRequest":{"type":"object","properties":{"mode":{"type":"string","enum":["whole_org","delegated"]}},"required":["mode"],"additionalProperties":false},"SetAccessModeResult":{"type":"object","properties":{"mode":{"type":"string","enum":["whole_org","delegated"]},"changed":{"type":"boolean"},"items_listed":{},"revoked":{},"settled":{"type":"number"},"shared_agents_narrowed":{"type":"number"}},"required":["mode","changed"],"additionalProperties":{}},"GetRelayAutoJoinResult":{"type":"object","properties":{"option":{"type":["string","null"]},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"}},"required":["id","label","description"],"additionalProperties":{}}}},"required":["option","options"],"additionalProperties":{}},"SetRelayAutoJoinRequest":{"type":"object","properties":{"option":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}]}},"required":["option"],"additionalProperties":false},"SetRelayAutoJoinResult":{"type":"object","properties":{"option":{"type":["string","null"]},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"}},"required":["id","label","description"],"additionalProperties":{}}},"changed":{"type":"boolean"}},"required":["option","options","changed"],"additionalProperties":{}},"ListBreakGlassResult":{"type":"object","properties":{"grants":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"expires_at":{"type":"string"}},"required":["id","expires_at"],"additionalProperties":{}}}},"required":["grants"],"additionalProperties":{}},"StartBreakGlassRequest":{"type":"object","properties":{"reason":{"type":"string","minLength":10,"maxLength":1000},"hours":{"default":1,"type":"integer","minimum":1,"maximum":24}},"required":["reason"],"additionalProperties":false},"StartBreakGlassResult":{"type":"object","properties":{"id":{"type":"string"},"expires_at":{"type":"string"},"owners_notified":{"type":"number"}},"required":["id","expires_at","owners_notified"],"additionalProperties":{}},"EndBreakGlassResult":{"type":"object","properties":{"ended":{"type":"boolean","const":true},"id":{"type":"string"}},"required":["ended","id"],"additionalProperties":{}},"ListOwnedItemsResult":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":{}}}},"required":["items"],"additionalProperties":{}},"TransferOwnedItemsRequest":{"type":"object","properties":{"to_user_id":{"type":"string","format":"uuid"},"items":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["api_key","function","github_binding","endpoint","function_trigger","pull_subscription","recipient_route","filter_rule","wake_schedule","repeating_send","scheduled_send"]},"id":{"type":"string","format":"uuid"},"rotate":{"type":"boolean"}},"required":["kind","id"],"additionalProperties":false}}},"required":["to_user_id","items"],"additionalProperties":false},"TransferOwnedItemsResult":{"type":"object","properties":{"transferred":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"},"rotated":{"type":"boolean"}},"required":["kind","id"],"additionalProperties":{}}},"refused":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"rotated":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"unrotated_keys":{"type":"array","items":{"type":"string"}},"ended_predecessors":{"type":"array","items":{"type":"string"}},"stopped_until_redeploy":{"type":"array","items":{"type":"string"}},"stopped_until_enabled":{"type":"array","items":{"type":"string"}},"transfer_stop_pending":{"type":"array","items":{"type":"string"}},"redeploy_failed":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"warning":{"type":["string","null"]}},"required":["transferred","refused","rotated","unrotated_keys","redeploy_failed","warning"],"additionalProperties":{}},"ListSuspensionsResult":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":{}}}},"required":["items"],"additionalProperties":{}},"DecideSuspensionRequest":{"type":"object","properties":{"decision":{"type":"string","enum":["keep","narrow","revoke"]},"grant":{"anyOf":[{"type":"string","enum":["super_admin","send_only","read_only","own_addresses","own_addresses_read_only","custom"]},{"type":"object","properties":{"preset":{"type":"string","enum":["super_admin","send_only","read_only","own_addresses","own_addresses_read_only","custom"]},"matrix":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","enum":["none","read","write"]}},"permissions":{"maxItems":200,"type":"array","items":{"type":"string"}},"addresses":{"maxItems":100,"type":"array","items":{"type":"string"}},"domains":{"maxItems":50,"type":"array","items":{"type":"string"}},"rules":{"maxItems":20,"type":"array","items":{"type":"string"}},"depth":{"description":"Deprecated: every key that reaches mail reads the full email. Ignored, except that raw still adds mail.raw:read (Raw mail) to an address preset or a custom grant.","type":"string","enum":["metadata","content","raw"]},"resources":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"maxItems":100,"type":"array","items":{"type":"string"}}},"mail_scope":{"type":"string","const":"org"}},"required":["preset"],"additionalProperties":false}]},"confirm":{"type":"boolean"},"rotate":{"type":"boolean"}},"required":["decision"],"additionalProperties":false},"DecideSuspensionResult":{"type":"object","properties":{"id":{"type":"string"},"decision":{"type":"string","enum":["keep","narrow","revoke"]},"function_stopped":{"type":"object","properties":{"by":{"type":"string","const":"owner_or_admin"},"stop_rank":{"type":"string","enum":["owner","admin"]},"message":{"type":"string"}},"required":["by","stop_rank","message"],"additionalProperties":false}},"required":["id","decision"],"additionalProperties":{}},"RemoveOwnAgentAccessResult":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"mailbox_address":{"type":["string","null"]},"mailbox_domain":{"type":["string","null"]},"mailbox_kind":{"anyOf":[{"type":"string","enum":["personal","shared","agent","app"]},{"type":"null"}]},"principal_kind":{"type":"string","enum":["member","group","agent","app","address"]},"principal_user_id":{"type":["string","null"]},"principal_group_id":{"type":["string","null"]},"principal_agent_connection_id":{"type":["string","null"]},"principal_address":{"type":["string","null"]},"actions":{"type":"array","items":{"type":"string","enum":["read","draft","send_as","send_on_behalf"]}},"depth":{"type":"string","enum":["metadata","content","raw"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","mailbox_address","mailbox_domain","mailbox_kind","principal_kind","principal_user_id","principal_group_id","principal_agent_connection_id","principal_address","actions","depth","created_at","updated_at"],"additionalProperties":{}}}},"required":["entries"],"additionalProperties":{}},"SetOwnMailboxAccessRequest":{"type":"object","properties":{"entries":{"maxItems":500,"type":"array","items":{"type":"object","properties":{"principal":{"type":"object","properties":{"kind":{"type":"string","enum":["member","group","agent","address"]},"id":{"type":"string","minLength":1,"maxLength":320}},"required":["kind","id"],"additionalProperties":false},"actions":{"minItems":1,"maxItems":4,"type":"array","items":{"type":"string","enum":["read","draft","send_as","send_on_behalf"]}},"depth":{"default":"content","description":"Deprecated and ignored: every scope that reaches mail reads the full email, and raw MIME follows mail.raw:read. New entries record content.","type":"string","enum":["metadata","content","raw"]}},"required":["principal","actions"],"additionalProperties":false}}},"required":["entries"],"additionalProperties":false},"SetOwnMailboxAccessResult":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"mailbox_address":{"type":["string","null"]},"mailbox_domain":{"type":["string","null"]},"mailbox_kind":{"anyOf":[{"type":"string","enum":["personal","shared","agent","app"]},{"type":"null"}]},"principal_kind":{"type":"string","enum":["member","group","agent","app","address"]},"principal_user_id":{"type":["string","null"]},"principal_group_id":{"type":["string","null"]},"principal_agent_connection_id":{"type":["string","null"]},"principal_address":{"type":["string","null"]},"actions":{"type":"array","items":{"type":"string","enum":["read","draft","send_as","send_on_behalf"]}},"depth":{"type":"string","enum":["metadata","content","raw"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","mailbox_address","mailbox_domain","mailbox_kind","principal_kind","principal_user_id","principal_group_id","principal_agent_connection_id","principal_address","actions","depth","created_at","updated_at"],"additionalProperties":{}}}},"required":["entries"],"additionalProperties":{}},"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-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-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,"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}}},"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,"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}}},"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-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":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"task_id":{"anyOf":[{"type":"string","minLength":1,"maxLength":256},{"type":"null"}]},"text":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]},"completed":{"type":"boolean"},"dismissed":{"type":"boolean"},"evidence":{"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","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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"item":{"type":"object","properties":{"edit":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"membership"},"topic_id":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"task_id":{"anyOf":[{"type":"string","minLength":1,"maxLength":256},{"type":"null"}]},"text":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]},"completed":{"type":"boolean"},"dismissed":{"type":"boolean"},"evidence":{"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","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-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-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","enum":["apple-on-device","primitive-coreml-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-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":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"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","enum":["apple-on-device","primitive-coreml-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":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":256},{"type":"null"}]},"text":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]}},"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-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"item":{"type":"object","properties":{"generation":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"membership"},"topic_id":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"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","enum":["apple-on-device","primitive-coreml-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":{"anyOf":[{"type":"string","minLength":1,"maxLength":26000},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":256},{"type":"null"}]},"text":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]},"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":{"anyOf":[{"type":"string","minLength":1,"maxLength":20000,"pattern":"\\S"},{"type":"null"}]}},"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},"SendPermissionPrimitiveManagedDomain":{"type":"object","description":"The caller can send to recipients on active domains hosted by Primitive. The hosted domains are not listed; check a specific recipient with `\/sendability`.","properties":{"type":{"type":"string","enum":["primitive_managed_domain"]},"description":{"type":"string","description":"Human-prose summary of the rule."}},"required":["type","description"]},"SendPermissionOptedInDomains":{"type":"object","description":"The caller can send to recipients on any domain that authorizes mail from agents on this platform with an `_agents` DNS record. Those domains are not listed; check a specific recipient with `\/sendability`.","properties":{"type":{"type":"string","enum":["opted_in_domains"]},"description":{"type":"string","description":"Human-prose summary of the rule."}},"required":["type","description"]},"SendPermissionOrgMemberEmail":{"type":"object","description":"The caller can send to the signup email of a member of its organization. Emitted once per member the caller can see.","properties":{"type":{"type":"string","enum":["org_member_email"]},"address":{"type":"string","description":"The member's signup email address."},"description":{"type":"string","description":"Human-prose summary of the rule."}},"required":["type","address","description"]},"SendPermissionOrgMemberEmails":{"type":"object","description":"The caller can send to the signup email of any member of its organization, but those addresses are listed only for a credential that can read member emails (`members:read`). Check a specific recipient with `\/sendability`.","properties":{"type":{"type":"string","enum":["org_member_emails"]},"description":{"type":"string","description":"Human-prose summary of the rule."}},"required":["type","description"]},"TemplateSchedule":{"type":"object","properties":{"command":{"type":"string","minLength":1},"cron":{"type":"string","minLength":1},"timezone":{"default":"UTC","type":"string","minLength":1},"args":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"senderLocalpart":{"default":"scheduler","type":"string","pattern":"^[a-z0-9][a-z0-9._-]{0,62}$"},"allowlistSecret":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"},"recipientsSecret":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"}},"required":["command","cron","timezone","args","senderLocalpart"],"additionalProperties":false,"description":"A default wake schedule the install creates for the function: the command it sends, a 5-field cron expression and timezone, and the scheduler sender localpart on the installed address's domain. The owner can change or delete it after install."},"EndpointCreateWarning":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"endpoint_id":{"description":"The other webhook a same_path_as_another_endpoint warning names.","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)$"}},"required":["code","message"],"additionalProperties":{}},"CreatedEndpoint":{"allOf":[{"$ref":"#\/components\/schemas\/Endpoint"},{"type":"object","properties":{"warnings":{"description":"For a webhook (kind http). same_path_as_another_endpoint: another webhook you can see, on and not deleted, sends to the same scheme, host and path with any query string, so each email both receive is delivered twice; endpoint_id names it (at most 5, newest first). The URL is never echoed. Empty when none; absent when it could not be checked (a caller who may not read endpoints, or a failed read). Other codes may be added.","type":"array","items":{"$ref":"#\/components\/schemas\/EndpointCreateWarning"}}}}]},"EndpointUpdateWarning":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":{}},"UpdatedEndpoint":{"allOf":[{"$ref":"#\/components\/schemas\/Endpoint"},{"type":"object","required":["warnings"],"properties":{"warnings":{"default":[],"description":"When this update turned the webhook off or on without the request setting enabled. turned_off_owner_changed: the webhook was given to a new owner, so it is now off (disabled_reason owner_changed) until it is turned on or given a new URL. turned_on_new_url: it was off because its owner changed, and the new URL turned it back on. Empty when the update did neither. Other codes may be added.","type":"array","items":{"$ref":"#\/components\/schemas\/EndpointUpdateWarning"}}}}]},"EndpointSigningSecretResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"secret":{"type":"string"}},"required":["secret"],"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":{}},"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. Also set for a `parent_already_replied` replay matched inside that window; null for one matched past it, which only a retry from the same Function can be. Null for `explicit` and `function_trigger` keys matched on their key, which have no window."}}},"BuiltinRoleKey":{"type":"string","enum":["owner","admin","member"],"description":"The key of a built-in role. Every other role, including the `operator` template, is a template or custom role with a key of its own. A member's `role` (and the `stored_role` a role assignment returns) can also be `restricted`, which is not a built-in role and cannot be assigned: it stands for a template or custom role that does not fully include `member`, or for no role at all."}},"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"}