{"openapi":"3.1.0","info":{"title":"Freightbox Platform Email API","version":"0.1.0","description":"Public API for the Freightbox platform email infrastructure.\n\n## Authentication\n\nAll endpoints require a bearer API key: `Authorization: Bearer fb_live_...`\n\n## Scopes\n\n| Scope | Operations |\n|---|---|\n| `platform:inbox:read` | List/get inboxes, read messages and webhook logs |\n| `platform:inbox:write` | Create, update, delete inboxes |\n| `platform:identity:read` | List/get sending identities |\n| `platform:identity:write` | Create, update, delete identities |\n| `platform:message:send` | Send outbound emails |\n\n## Spec maintenance\n\nHand-authored against `src/routes/platform*.routes.ts`. Changes to those files MUST be mirrored here. Regen SDKs via `npm run sdk:ts` / `npm run sdk:py`.\n","contact":{"name":"Freightbox Support","url":"https://getfreightbox.com"},"license":{"name":"Proprietary","identifier":"LicenseRef-Proprietary"}},"servers":[{"url":"https://api.getfreightbox.com","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key from the Freightbox dashboard. Format: fb_live_..."}},"schemas":{"ErrorResponse":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"message":{"type":"string","description":"Human-readable error description"}}},"PlatformInbox":{"type":"object","description":"Full inbox record (GET by ID response). Includes webhookSecretConfigured.","required":["id","name","address","inboxType","autopilotMode","createdAt","updatedAt"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"address":{"type":"string","description":"Full email address, e.g. acme-support@getfreightbox.com"},"slug":{"type":["string","null"]},"inboxType":{"type":"string","enum":["shared","load","customer"]},"entityType":{"type":["string","null"]},"entityId":{"type":["string","null"]},"webhookUrl":{"type":["string","null"]},"webhookSecretConfigured":{"type":"boolean","description":"True if a signing secret is set (the secret itself is never returned)"},"description":{"type":["string","null"]},"autopilotMode":{"type":"string","enum":["disabled","assisted"]},"autopilotPrompt":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"PlatformInboxSummary":{"type":"object","description":"Inbox summary returned in list responses (no webhookSecretConfigured).","required":["id","name","address","inboxType","autopilotMode","createdAt","updatedAt"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"address":{"type":"string"},"slug":{"type":["string","null"]},"inboxType":{"type":"string","enum":["shared","load","customer"]},"entityType":{"type":["string","null"]},"entityId":{"type":["string","null"]},"webhookUrl":{"type":["string","null"]},"description":{"type":["string","null"]},"autopilotMode":{"type":"string","enum":["disabled","assisted"]},"autopilotPrompt":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"CreateInboxRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"slug":{"type":"string","pattern":"^[a-z0-9-]+$","minLength":2,"maxLength":50,"description":"Auto-generated from name if omitted. shared inboxes only."},"inboxType":{"type":"string","enum":["shared","load","customer"],"default":"shared"},"entityId":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","minLength":1,"maxLength":100,"description":"Required for load and customer inboxes."},"webhookUrl":{"type":"string","format":"uri"},"webhookSecret":{"type":"string","minLength":16,"description":"Stored encrypted; never returned."},"description":{"type":"string","maxLength":500},"autopilotMode":{"type":"string","enum":["disabled","assisted"]},"autopilotPrompt":{"type":["string","null"],"maxLength":2000}}},"CreateInboxResponse":{"type":"object","required":["id","name","slug","address","inboxType","createdAt","updatedAt"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":["string","null"]},"address":{"type":"string"},"inboxType":{"type":"string","enum":["shared","load","customer"]},"entityType":{"type":["string","null"]},"entityId":{"type":["string","null"]},"webhookUrl":{"type":["string","null"]},"description":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"UpdateInboxRequest":{"type":"object","description":"At least one field must be provided.","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"webhookUrl":{"type":["string","null"],"format":"uri","description":"Set to null to remove."},"webhookSecret":{"type":["string","null"],"minLength":16,"description":"Set to null to remove."},"description":{"type":["string","null"],"maxLength":500},"autopilotMode":{"type":"string","enum":["disabled","assisted"]},"autopilotPrompt":{"type":["string","null"],"maxLength":2000}}},"UpdateInboxResponse":{"type":"object","required":["id","name","address","inboxType","autopilotMode","updatedAt"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"address":{"type":"string"},"slug":{"type":["string","null"]},"inboxType":{"type":"string","enum":["shared","load","customer"]},"webhookUrl":{"type":["string","null"]},"description":{"type":["string","null"]},"autopilotMode":{"type":"string","enum":["disabled","assisted"]},"autopilotPrompt":{"type":["string","null"]},"updatedAt":{"type":"string","format":"date-time"}}},"LoadInboxResponse":{"type":"object","required":["id","name","address","loadId","createdAt"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"address":{"type":"string"},"loadId":{"type":"string","description":"Canonicalized (lowercased) load ID"},"webhookUrl":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}}},"GetOrCreateLoadInboxRequest":{"type":"object","properties":{"webhookUrl":{"type":"string","format":"uri"},"webhookSecret":{"type":"string","minLength":16}}},"WebhookLogEntry":{"type":"object","required":["id","webhookUrl","attemptNumber","createdAt"],"properties":{"id":{"type":"integer"},"webhookUrl":{"type":"string"},"responseStatus":{"type":["integer","null"]},"deliveredAt":{"type":["string","null"],"format":"date-time"},"error":{"type":["string","null"]},"attemptNumber":{"type":"integer","description":"1 = first attempt"},"createdAt":{"type":"string","format":"date-time"}}},"InboxMessage":{"type":"object","description":"A single email message. NOTE v1: 'from' sender info is not included (requires participant join not yet implemented).","required":["id","threadId","direction","createdAt"],"properties":{"id":{"type":"integer"},"threadId":{"type":"integer"},"direction":{"type":"string","enum":["inbound","outbound"]},"subject":{"type":["string","null"],"description":"From the parent thread"},"bodyText":{"type":["string","null"]},"bodyHtml":{"type":["string","null"]},"messageIdHeader":{"type":["string","null"],"description":"RFC 5322 Message-ID"},"inReplyTo":{"type":["string","null"]},"deliveryStatus":{"type":["string","null"],"description":"Outbound delivery status"},"receivedAt":{"type":["string","null"],"format":"date-time","description":"Inbound only"},"createdAt":{"type":"string","format":"date-time"}}},"PlatformIdentity":{"type":"object","description":"A sending identity (platform email address used to send outbound email).","required":["id","address","identityType","isActive","createdAt","updatedAt"],"properties":{"id":{"type":"integer"},"address":{"type":"string","format":"email"},"displayName":{"type":["string","null"]},"identityType":{"type":"string","enum":["primary","alias","system"]},"replyToAddress":{"type":["string","null"],"format":"email"},"mailboxId":{"type":["integer","null"]},"mailboxName":{"type":["string","null"],"description":"Bound inbox name (list response)"},"isActive":{"type":"boolean"},"verifiedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"PlatformIdentityDetail":{"type":"object","description":"Identity detail (GET by ID) — includes mailboxAddress.","required":["id","address","identityType","isActive","createdAt","updatedAt"],"properties":{"id":{"type":"integer"},"address":{"type":"string","format":"email"},"displayName":{"type":["string","null"]},"identityType":{"type":"string","enum":["primary","alias","system"]},"replyToAddress":{"type":["string","null"],"format":"email"},"mailboxId":{"type":["integer","null"]},"mailboxName":{"type":["string","null"]},"mailboxAddress":{"type":["string","null"],"description":"Email address of the bound inbox"},"isActive":{"type":"boolean"},"verifiedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"CreateIdentityRequest":{"type":"object","required":["address"],"properties":{"address":{"type":"string","format":"email","description":"Must end in @getfreightbox.com"},"displayName":{"type":"string","maxLength":100},"identityType":{"type":"string","enum":["primary","alias","system"],"default":"alias"},"replyToAddress":{"type":"string","format":"email"},"mailboxId":{"type":"integer"}}},"CreateIdentityResponse":{"type":"object","required":["id","address","identityType","isActive","verifiedAt","createdAt","updatedAt"],"properties":{"id":{"type":"integer"},"address":{"type":"string","format":"email"},"displayName":{"type":["string","null"]},"identityType":{"type":"string","enum":["primary","alias","system"]},"replyToAddress":{"type":["string","null"],"format":"email"},"mailboxId":{"type":["integer","null"]},"isActive":{"type":"boolean"},"verifiedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"UpdateIdentityRequest":{"type":"object","description":"At least one field must be provided.","properties":{"displayName":{"type":["string","null"],"maxLength":100},"replyToAddress":{"type":["string","null"],"format":"email"},"mailboxId":{"type":["integer","null"]},"isActive":{"type":"boolean"}}},"UpdateIdentityResponse":{"type":"object","required":["id","address","identityType","isActive","updatedAt"],"properties":{"id":{"type":"integer"},"address":{"type":"string","format":"email"},"displayName":{"type":["string","null"]},"identityType":{"type":"string","enum":["primary","alias","system"]},"replyToAddress":{"type":["string","null"],"format":"email"},"mailboxId":{"type":["integer","null"]},"isActive":{"type":"boolean"},"updatedAt":{"type":"string","format":"date-time"}}},"SendEmailRequest":{"type":"object","required":["identityId","to","subject"],"description":"Either bodyText or bodyHtml (or both) must be non-empty.","properties":{"identityId":{"type":"integer","description":"Platform identity ID to send from (must be active)"},"to":{"type":"array","items":{"type":"string","format":"email"},"minItems":1,"maxItems":50},"cc":{"type":"array","items":{"type":"string","format":"email"},"maxItems":50},"bcc":{"type":"array","items":{"type":"string","format":"email"},"maxItems":50},"subject":{"type":"string","minLength":1,"maxLength":1000},"bodyText":{"type":"string","description":"Plain-text body (max 5MB)"},"bodyHtml":{"type":"string","description":"HTML body (max 5MB)"},"inReplyTo":{"type":"string","maxLength":500,"description":"RFC 5322 Message-ID for In-Reply-To threading header"},"threadId":{"type":"integer","description":"Associate with existing thread. New thread created if omitted."}}},"SendEmailResponse":{"type":"object","required":["status","providerMessageId","messageIdHeader","persistenceFailed"],"properties":{"status":{"type":"string","enum":["sent"]},"providerMessageId":{"type":"string","description":"SES message ID for reconciliation"},"messageIdHeader":{"type":"string","description":"RFC 5322 Message-ID assigned to this email"},"threadId":{"type":["integer","null"],"description":"Associated thread ID (null if persistence failed)"},"messageId":{"type":["integer","null"],"description":"email_message row ID (null if persistence failed)"},"persistenceFailed":{"type":"boolean","description":"True if SES accepted but DB persistence failed. Email WAS sent — do NOT retry."}}},"WebhookEmailReceived":{"type":"object","description":"Payload POSTed to your webhookUrl when an inbound email arrives. Signed with X-Webhook-Signature: sha256=<hmac> when a secret is configured.","required":["event","timestamp","data"],"properties":{"event":{"type":"string","enum":["email.received"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["inboxId","messageId","threadId","organizationId","from","to","subject","receivedAt"],"properties":{"inboxId":{"type":"integer"},"messageId":{"type":"integer"},"threadId":{"type":"integer"},"organizationId":{"type":"integer"},"from":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"name":{"type":"string"}}},"to":{"type":"array","items":{"type":"string","format":"email"}},"cc":{"type":"array","items":{"type":"string","format":"email"}},"subject":{"type":"string"},"bodyText":{"type":"string"},"bodyHtml":{"type":"string"},"messageIdHeader":{"type":"string","description":"RFC 5322 Message-ID"},"inReplyTo":{"type":"string"},"references":{"type":"string"},"receivedAt":{"type":"string","format":"date-time"},"attachments":{"type":"array","items":{"type":"object","required":["filename","contentType","size","storageUrl"],"properties":{"filename":{"type":"string"},"contentType":{"type":"string"},"size":{"type":"integer","description":"Bytes"},"storageUrl":{"type":"string"}}}}}}}},"DeliveryEventData":{"type":"object","required":["inboxId","messageId","organizationId","providerMessageId"],"description":"Common data for delivery lifecycle events. PII rule: no recipient addresses.","properties":{"inboxId":{"type":"integer"},"messageId":{"type":"integer"},"threadId":{"type":["integer","null"]},"organizationId":{"type":"integer"},"providerMessageId":{"type":"string","description":"SES message ID to correlate with send response"},"bounceType":{"type":"string","description":"email.bounced only. E.g. Permanent, Transient."},"bounceSubType":{"type":"string","description":"email.bounced only. E.g. General, NoEmail."},"recipientCount":{"type":"integer","description":"Number of affected recipients. No addresses (PII rule)."}}},"WebhookEmailDelivered":{"type":"object","required":["event","timestamp","data"],"properties":{"event":{"type":"string","enum":["email.delivered"]},"timestamp":{"type":"string","format":"date-time"},"data":{"$ref":"#/components/schemas/DeliveryEventData"}}},"WebhookEmailBounced":{"type":"object","required":["event","timestamp","data"],"description":"PII rule: bounceType and recipientCount only, no addresses.","properties":{"event":{"type":"string","enum":["email.bounced"]},"timestamp":{"type":"string","format":"date-time"},"data":{"$ref":"#/components/schemas/DeliveryEventData"}}},"WebhookEmailComplained":{"type":"object","required":["event","timestamp","data"],"properties":{"event":{"type":"string","enum":["email.complained"]},"timestamp":{"type":"string","format":"date-time"},"data":{"$ref":"#/components/schemas/DeliveryEventData"}}}}},"paths":{"/api/platform/inboxes":{"get":{"operationId":"listPlatformInboxes","summary":"List platform inboxes","description":"Returns all platform-owned inboxes. If the API key is scoped to specific inboxes, only those are returned.\n\nRequired scope: `platform:inbox:read`","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of inboxes","content":{"application/json":{"schema":{"type":"object","required":["inboxes"],"properties":{"inboxes":{"type":"array","items":{"$ref":"#/components/schemas/PlatformInboxSummary"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"operationId":"createPlatformInbox","summary":"Create a platform inbox","description":"Creates a new platform-owned inbox. Requires org admin. API keys with inbox restrictions cannot create inboxes.\n\nRequired scope: `platform:inbox:write`\n\nAddress format: `{org-slug}-{inbox-slug}@getfreightbox.com` (shared), `{org-slug}-load-{entityId}@...` (load), `{org-slug}-customer-{entityId}@...` (customer).","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInboxRequest"}}}},"responses":{"201":{"description":"Inbox created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInboxResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — slug or address already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/platform/inboxes/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Platform inbox ID"}],"get":{"operationId":"getPlatformInbox","summary":"Get a platform inbox","description":"Returns the inbox including `webhookSecretConfigured` (boolean — secret never returned).\n\nRequired scope: `platform:inbox:read`","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Inbox detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformInbox"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"operationId":"updatePlatformInbox","summary":"Update a platform inbox","description":"Updates one or more fields. Requires org admin.\n\nRequired scope: `platform:inbox:write`","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInboxRequest"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInboxResponse"}}}},"400":{"description":"No fields provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deletePlatformInbox","summary":"Delete a platform inbox","description":"Returns 409 if threads still reference the inbox. Requires org admin.\n\nRequired scope: `platform:inbox:write`","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Inbox has threads","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/platform/inboxes/{id}/webhook-logs":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Platform inbox ID"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"description":"Max entries (default 20, max 50)"}],"get":{"operationId":"listWebhookLogs","summary":"List webhook delivery logs","description":"Returns recent webhook delivery attempts for an inbox, ordered newest-first.\n\nRequired scope: `platform:inbox:read`","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Delivery logs","content":{"application/json":{"schema":{"type":"object","required":["logs"],"properties":{"logs":{"type":"array","items":{"$ref":"#/components/schemas/WebhookLogEntry"}}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/platform/inboxes/{id}/messages":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Platform inbox ID"},{"name":"since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Return messages at or after this time (ISO 8601). Default: 24h ago. Pass previous response nextSince to page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Max messages (default 50, max 200)"}],"get":{"operationId":"listInboxMessages","summary":"List messages in an inbox","description":"Polling endpoint. Results ordered oldest-first. Pass nextSince as since to page forward. Deduplicate by id (same createdAt may straddle pages).\n\nNOTE v1: from sender info not included (participant join pending).\n\nRequired scope: `platform:inbox:read`","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Messages","content":{"application/json":{"schema":{"type":"object","required":["inboxId","messages"],"properties":{"inboxId":{"type":"integer"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/InboxMessage"}},"nextSince":{"type":["string","null"],"format":"date-time","description":"Pass as since in next request. Null if no messages returned."}}}}}},"400":{"description":"Invalid query params","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/platform/inboxes/load/{loadId}":{"parameters":[{"name":"loadId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","maxLength":100},"description":"Load identifier. Canonicalized to lowercase."}],"post":{"operationId":"getOrCreateLoadInbox","summary":"Get or create a load inbox","description":"Idempotent. Creates the inbox if it doesn't exist; returns it if it does. Optionally sets webhook URL and secret. Requires org admin. API keys with inbox restrictions cannot use this endpoint.\n\nRequired scope: `platform:inbox:write`","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOrCreateLoadInboxRequest"}}}},"responses":{"200":{"description":"Load inbox","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoadInboxResponse"}}}},"400":{"description":"Invalid load ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/platform/identities":{"get":{"operationId":"listPlatformIdentities","summary":"List sending identities","description":"Returns all platform sending identities for the organization.\n\nRequired scope: `platform:identity:read`","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of identities","content":{"application/json":{"schema":{"type":"object","required":["identities"],"properties":{"identities":{"type":"array","items":{"$ref":"#/components/schemas/PlatformIdentity"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"operationId":"createPlatformIdentity","summary":"Create a sending identity","description":"Address must be on the platform domain (@getfreightbox.com). Requires org admin.\n\nRequired scope: `platform:identity:write`","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIdentityRequest"}}}},"responses":{"201":{"description":"Identity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIdentityResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Address already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/platform/identities/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Platform identity ID"}],"get":{"operationId":"getPlatformIdentity","summary":"Get a sending identity","description":"Returns full identity including mailboxAddress.\n\nRequired scope: `platform:identity:read`","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Identity detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformIdentityDetail"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"operationId":"updatePlatformIdentity","summary":"Update a sending identity","description":"Requires org admin.\n\nRequired scope: `platform:identity:write`","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateIdentityRequest"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateIdentityResponse"}}}},"400":{"description":"No fields provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deletePlatformIdentity","summary":"Delete a sending identity","description":"Requires org admin.\n\nRequired scope: `platform:identity:write`","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/platform/send":{"post":{"operationId":"sendPlatformEmail","summary":"Send an email","description":"Sends outbound email via AWS SES from a platform identity.\n\nRate limited: 20/15min per IP. Idempotency-Key header enables safe retries (replays cached response 24h, bypasses rate limit).\n\nRequired scope: `platform:message:send`","security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Client-generated unique key. Same key returns original response for 24h without re-sending."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailRequest"}}}},"responses":{"200":{"description":"Email accepted by SES","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden (inactive identity, mailbox membership, or scope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Identity or thread not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/platform/openapi.json":{"get":{"operationId":"getOpenApiSpec","summary":"Get the OpenAPI spec","description":"Returns this OpenAPI 3.1 specification as JSON. Public — no auth required.","security":[],"responses":{"200":{"description":"OpenAPI specification","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found (spec file missing from deployment)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Spec unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/platform/docs":{"get":{"operationId":"getPlatformDocs","summary":"Interactive API docs","description":"Scalar API reference UI. Public — no auth required.","security":[],"responses":{"200":{"description":"HTML docs page","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Not found","content":{"text/html":{"schema":{"type":"string"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"webhooks":{"emailReceived":{"post":{"summary":"Email received","description":"POSTed to your webhookUrl when an inbound email arrives. Signed X-Webhook-Signature: sha256=<hmac> when secret configured. Return 2xx to acknowledge. 4xx stops retries; 5xx triggers up to 3 retries (1s/5s/30s backoff).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEmailReceived"}}}},"responses":{"200":{"description":"Acknowledged."}},"operationId":"onEmailReceived"}},"emailDelivered":{"post":{"summary":"Email delivered","description":"Sent when SES confirms delivery.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEmailDelivered"}}}},"responses":{"200":{"description":"Acknowledged."}},"operationId":"onEmailDelivered"}},"emailBounced":{"post":{"summary":"Email bounced","description":"Sent on bounce. Includes bounceType and recipientCount — no addresses (PII rule).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEmailBounced"}}}},"responses":{"200":{"description":"Acknowledged."}},"operationId":"onEmailBounced"}},"emailComplained":{"post":{"summary":"Email complained","description":"Sent when a recipient marks email as spam.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEmailComplained"}}}},"responses":{"200":{"description":"Acknowledged."}},"operationId":"onEmailComplained"}}}}