diff --git a/README.md b/README.md index 8d986000..68d7b6f1 100644 --- a/README.md +++ b/README.md @@ -196,10 +196,10 @@ The organization view also exposes these management resources: - `securityIncidents`: list and acknowledge leaked-credential incidents - `projectTokens`: list token metadata for an explicit project - `billing`: retrieve balance and currency, inspect usage meters, list - transactions and tier pricing, and update low-balance reminders + transactions and tier pricing - `projects`: list, create, request production enrollment, approve, and cancel - `sessions`: list, start, stop, or delete one session; stop or delete a bounded - batch; set tier override; and create a testing session + batch; review and confirm a tier change; and create a testing session - `campaigns`: list, create, retrieve, update, delete, lifecycle actions, analytics, events, and recipients - `customers`: enable Customers for a project; create, list, retrieve, update, diff --git a/contracts/README.md b/contracts/README.md index 5c160ded..ab27b26f 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -2,17 +2,17 @@ The snapshots are byte-identical copies of the Messaging and Platform OpenAPI files at `polymorfa/polymorfa` commit -`7540c0cef0d6a9552476a1240c37c072c4781033`. `source.json` records their original +`1600403db9dcf7996dbbb882375b1165859829d3`. `source.json` records their original paths and SHA-256 hashes. `coverage.json` uses the same source revision. | Status | Operations | | ------------------- | ---------: | | Covered | 265 | | Missing | 37 | -| Excluded | 154 | +| Excluded | 158 | | Partial | 0 | | Changed fingerprint | 0 | -| Total | 456 | +| Total | 460 | Coverage spans the TypeScript server SDK, browser transport, and Calls package. It does not claim coverage in other languages, package publication, or a @@ -20,7 +20,9 @@ successful live call. ## Reconciliation -This refresh adopts public Number, conversation, user and message identifiers. +This refresh retires direct session creation and observation-policy writes. QuickLinks supply typed configuration and test simulation. The SDK adds project history fixture upload, saved defaults, and trusted-server Meta continuation tied to an existing QuickLink. + +Public Number, conversation, user and message identifiers remain supported. Message responses retain the exact provider ID in `whatsapp_id`. Channel actions use the public message ID; call participants expose public identities; history events carry a public message index and a separate provider archive. @@ -72,3 +74,11 @@ The cross-repository workflow compares an exact source revision and reports missing or changed contracts for follow-up. Its dedicated GitHub App must have access to read SDK contents and write coverage issues. A token-creation failure occurs before comparison and says nothing about SDK parity. + +The merged Platform and billing adjustments reuse reviewed source from SDK commit +`75146778f7a20257a6c0f0f3139329ab308f40f0` (billing API parity): client-token administration, session lifecycle +routes, paid-number expiry, tier quotes and confirmation, and payment-required +errors. They are checked against these same canonical API snapshots. Removed +dashboard-only billing reminders and client-token session start/status helpers +are not retained as compatibility aliases. BanSafe methods remain explicitly +missing where the ledger says so. diff --git a/contracts/coverage.json b/contracts/coverage.json index e545c7ab..3e66c7ca 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "7540c0cef0d6a9552476a1240c37c072c4781033", + "sourceCommit": "1600403db9dcf7996dbbb882375b1165859829d3", "excludedContracts": [ { "family": "graph", @@ -129,28 +129,6 @@ "method": "MessagingClient.quickLinks.cancel" } }, - { - "family": "messaging", - "method": "DELETE", - "path": "/messaging/sessions/{session}", - "operationId": "deleteSession", - "fingerprint": "1218595e97caff424c92c5357b46608f5897b27f7f228cfd338e2d8df925a312", - "typescript": { - "status": "covered", - "method": "MessagingClient.sessions.delete" - } - }, - { - "family": "messaging", - "method": "DELETE", - "path": "/messaging/sessions/{session}/client-rules", - "operationId": "deleteClientRules", - "fingerprint": "0685c4d6876265a4e8f1972b110e1a6adffccb50be275fe4f22eac3ac0b1ae33", - "typescript": { - "status": "covered", - "method": "MessagingClient.clientTokens.deleteRules" - } - }, { "family": "messaging", "method": "DELETE", @@ -767,56 +745,12 @@ "method": "GET", "path": "/messaging/quicklinks/{id}", "operationId": "getQuickLink", - "fingerprint": "304ed64510b790479c4d88d1a65cf00e575c38586ab6b39497d37ac2b3228193", + "fingerprint": "7c74cd57a888556611ab0d8c188ae9064046672391b03eeccca1a51dff1b6e1d", "typescript": { "status": "covered", "method": "MessagingClient.quickLinks.retrieve" } }, - { - "family": "messaging", - "method": "GET", - "path": "/messaging/sessions", - "operationId": "listSessions", - "fingerprint": "5fb4cb7241a2548710c1d55b2ce1bbaabc1a5b3f3a80371a5cbbfe3fadd6be03", - "typescript": { - "status": "covered", - "method": "MessagingClient.sessions.list" - } - }, - { - "family": "messaging", - "method": "GET", - "path": "/messaging/sessions/{session}", - "operationId": "getSession", - "fingerprint": "146c537453893e59d1564ec2e8ce9e04e7d93c207da9cfe84838ee3e14ceac7a", - "typescript": { - "status": "covered", - "method": "MessagingClient.sessions.retrieve" - } - }, - { - "family": "messaging", - "method": "GET", - "path": "/messaging/sessions/{session}/client-rules", - "operationId": "getClientRules", - "fingerprint": "3030b34a9434b7cb01d866fec7427d0c89e8445253693daac533dc465ba12285", - "typescript": { - "status": "covered", - "method": "MessagingClient.clientTokens.retrieveRules" - } - }, - { - "family": "messaging", - "method": "GET", - "path": "/messaging/sessions/{session}/me", - "operationId": "getSessionAccount", - "fingerprint": "75ca98789eca025fcd87b8cae49c5a7e1fb8b624c608fe3c4fea737779873744", - "typescript": { - "status": "covered", - "method": "MessagingClient.sessions.account" - } - }, { "family": "messaging", "method": "GET", @@ -867,7 +801,7 @@ "method": "GET", "path": "/quicklink/{token}", "operationId": "getQuickLinkState", - "fingerprint": "1c727d050b0f307ffa7bff646ff3f4dcb409700aff27ef168755aff0c7f0e420", + "fingerprint": "4e672b39cdd7dc3d5db29b682f2a1b940f2e7349b4664698109cf231fe5e1922", "typescript": { "status": "excluded", "reason": "Credential-free public QuickLink participant route owned by browser UI transport, not the server SDK.", @@ -879,7 +813,7 @@ "method": "GET", "path": "/quicklink/{token}/logo", "operationId": "getQuickLinkLogo", - "fingerprint": "74d33c263ee0752edfa022ef51322636d1dbc2f8fbea64d964f9a9b0a9daf1f0", + "fingerprint": "c7306496e68ec5885a449967f205c0aecec7b3d7e2beb22bf5ef807bcb82f6a6", "typescript": { "status": "excluded", "reason": "Credential-free public QuickLink participant route owned by browser UI transport, not the server SDK.", @@ -941,6 +875,17 @@ "method": "MessagingClient.templates.update" } }, + { + "family": "messaging", + "method": "PATCH", + "path": "/quicklink/{token}/testing", + "operationId": "customizeQuickLinkTesting", + "fingerprint": "dd599ccea4df40eb422cb17cec705414e8bc5501058348178eb6c912223632c6", + "typescript": { + "status": "excluded", + "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." + } + }, { "family": "messaging", "method": "POST", @@ -1276,7 +1221,7 @@ "method": "POST", "path": "/messaging/{session}/messages/react", "operationId": "sendReaction", - "fingerprint": "fc7c301c07fedcc756f1b7379497b805f25fc5572f1a451511e29f378c4ddf03", + "fingerprint": "f2970b40e43188ada7321ae1956fbae7dcccd9d64ef885e544e8e9198962b31a", "typescript": { "status": "covered", "method": "MessagingClient.messages.react" @@ -1287,7 +1232,7 @@ "method": "POST", "path": "/messaging/{session}/messages/seen", "operationId": "sendSeen", - "fingerprint": "d15d788c469db361b4ec6c39a96d63870620af71ca8ff9f8d01cfbba1123dca8", + "fingerprint": "efffa3d47d2a85f02bf167a5fdb9d1e916d4a1cfea5fa798bbd64681e405253b", "typescript": { "status": "covered", "method": "MessagingClient.messages.markSeen" @@ -1298,7 +1243,7 @@ "method": "POST", "path": "/messaging/{session}/messages/send", "operationId": "sendMessage", - "fingerprint": "849df52df9a850a645b3e89b169ab75e49bea3f8275e6c0d1f6a1fe1776853b8", + "fingerprint": "65fe8e665bc990931d2a5a7cc99108642ef49dae083749bbf679d2b1dbc38f11", "typescript": { "status": "covered", "method": "MessagingClient.messages.send" @@ -1309,7 +1254,7 @@ "method": "POST", "path": "/messaging/{session}/messages/star", "operationId": "starMessage", - "fingerprint": "c984d1a84c60acae4bbd4feb66b32b84def90c6fbb289a3d5603b44f106faacb", + "fingerprint": "9ef762c488d5d7bc690fb4b0980c3ad14fff089f677b4c54d838b0c4dd6609e6", "typescript": { "status": "covered", "method": "MessagingClient.messages.star" @@ -1320,7 +1265,7 @@ "method": "POST", "path": "/messaging/{session}/messages/typing", "operationId": "setTyping", - "fingerprint": "b72161d2c5d53621f7538e619e78e58849ae50ce4a1aa0cdb24d51d9f693f0d9", + "fingerprint": "a06ed5cfa18c62a5d9f069affce9389900d0cd5a166432da510c6d726f5d6627", "typescript": { "status": "covered", "method": "MessagingClient.messages.setTyping" @@ -1359,26 +1304,15 @@ "method": "MessagingClient.presence.subscribe" } }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/client-tokens", - "operationId": "mintClientToken", - "fingerprint": "58b4cce6f4686a4374cd5697f2c4750ee0957b55c22658a793733894726eb7a8", - "typescript": { - "status": "covered", - "method": "MessagingClient.clientTokens.mint" - } - }, { "family": "messaging", "method": "POST", "path": "/messaging/cloud-api/embedded-signup", "operationId": "embeddedSignup", - "fingerprint": "d2eb0b016598f4d832a08b5034b6d86c755813f15b574b1f3b2e573e2fa5e03c", + "fingerprint": "810222a6ad0428eb2b7d8d9798204a8898ebeb5ec348f69eaec650489df357f1", "typescript": { - "status": "excluded", - "reason": "Embedded signup is owned by the browser onboarding flow and is not implemented by this SDK." + "status": "covered", + "method": "MessagingClient.cloudOnboarding.advance" } }, { @@ -1507,7 +1441,7 @@ "method": "POST", "path": "/messaging/quicklinks", "operationId": "createQuickLink", - "fingerprint": "0b5ba5f3635e38a1f8ef974e46ed843b35c2caf5340c9d9a09ba5f4d0dff07aa", + "fingerprint": "ee9f03f069b7618094ff348bb43ce990c543c89ca15f78c4789edbe0c6342126", "typescript": { "status": "covered", "method": "MessagingClient.quickLinks.create" @@ -1516,104 +1450,12 @@ { "family": "messaging", "method": "POST", - "path": "/messaging/sessions/{projectId}/", - "operationId": "createSession", - "fingerprint": "5a921f8a26c06cd978328ebb7f4917775e2c0aeec60669a68641c68b97b36e30", - "typescript": { - "status": "covered", - "method": "MessagingClient.sessions.create" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/sessions/{session}/handoff-ack", - "operationId": "sessionHandoffAck", - "fingerprint": "91e2aa83d95f46e7a0c409b931c25b5f1dce3f42364c51add577fdb074f89f77", - "typescript": { - "status": "excluded", - "reason": "Encrypted session handoff remains outside the public SDK until its dedicated security and atomicity contract is complete.", - "milestone": "security-design" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/sessions/{session}/handoff-import", - "operationId": "sessionHandoffImport", - "fingerprint": "022f5a23225a52c6a830f1ec3abb238207367bc18d095a770e070c00903a83c4", - "typescript": { - "status": "excluded", - "reason": "Encrypted session handoff remains outside the public SDK until its dedicated security and atomicity contract is complete.", - "milestone": "security-design" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/sessions/{session}/handoff-keys", - "operationId": "sessionHandoffKeys", - "fingerprint": "066830e00efdbc6dc2d366b9f8a690bf642cb0e7a9a215bd28aef8dda5d70b60", - "typescript": { - "status": "excluded", - "reason": "Encrypted session handoff remains outside the public SDK until its dedicated security and atomicity contract is complete.", - "milestone": "security-design" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/sessions/{session}/logout", - "operationId": "logoutSession", - "fingerprint": "3289ea279b276dba235309af4bd1478568eb44cbc2ce5ca490bbf06ccf6966bf", - "typescript": { - "status": "covered", - "method": "MessagingClient.sessions.logout" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/sessions/{session}/restart", - "operationId": "restartSession", - "fingerprint": "7c518ddabdc96089477062e67defbb7dbea0b80db79bb3cdba7333e164fef27f", - "typescript": { - "status": "covered", - "method": "MessagingClient.sessions.restart" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/sessions/{session}/start", - "operationId": "startSession", - "fingerprint": "350d745f2b04c842e01652c4a79fbe0637e4bdededde26a29d6d00d5a81a25f7", - "typescript": { - "status": "covered", - "method": "MessagingClient.sessions.start" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/sessions/{session}/stop", - "operationId": "stopSession", - "fingerprint": "de08ab4048a3a64984cd117692924e081f4746d9d05d69b611bda558ed4850db", + "path": "/messaging/testing/{projectId}/history-fixtures", + "operationId": "createTestingHistoryFixture", + "fingerprint": "556e7f6751984568cc8aa09d802336eb74c171a18401804be5ebf00ce4c83e0c", "typescript": { "status": "covered", - "method": "MessagingClient.sessions.stop" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/sessions/{session}/upload-appstate-keys", - "operationId": "sessionUploadAppStateKeys", - "fingerprint": "33f3739ac905d0d9f1a9cfee9325b1f4fc15c6fcbbafe7c29386c6dcca7ca880", - "typescript": { - "status": "excluded", - "reason": "Encrypted session handoff remains outside the public SDK until its dedicated security and atomicity contract is complete.", - "milestone": "security-design" + "method": "MessagingClient.testing.createHistoryFixture" } }, { @@ -1755,7 +1597,7 @@ "method": "POST", "path": "/quicklink/{token}/cancel", "operationId": "cancelQuickLinkByToken", - "fingerprint": "d639fcf73bcb3e297b7cbf09731f5e330574baf5fbbb11556a2d1c72ce6b353d", + "fingerprint": "1e8e0e2d3a0f5022722f9a4ce20ab1c42e5e6149a6ec7d3a4d37f7d4e481537d", "typescript": { "status": "excluded", "reason": "Credential-free public QuickLink participant route owned by browser UI transport, not the server SDK.", @@ -1767,7 +1609,7 @@ "method": "POST", "path": "/quicklink/{token}/cloud", "operationId": "completeQuickLinkCloudSignup", - "fingerprint": "2cad92665e56d4718f7a53acea71d5fd32997efd70ab60869a9826e5faaa0d53", + "fingerprint": "4737cd1a70237a10a2fd46b339cefde693e064644b61e0d2874f8ae2e7060bb1", "typescript": { "status": "excluded", "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." @@ -1778,7 +1620,7 @@ "method": "POST", "path": "/quicklink/{token}/code", "operationId": "requestQuickLinkCode", - "fingerprint": "46d9df5f4b283478cf1b5741112d2d876fe04537f864b7fa331cffc30f3fd9fe", + "fingerprint": "4760de4a52464d5f9b6c524694dd57798fef13fed80e4be28810021573cbedb9", "typescript": { "status": "excluded", "reason": "Credential-free public QuickLink participant route owned by browser UI transport, not the server SDK.", @@ -1790,7 +1632,7 @@ "method": "POST", "path": "/quicklink/{token}/confirm", "operationId": "confirmQuickLink", - "fingerprint": "2cd2448d8e536cfff46713cd312307c0f9f38cfe1cbcf5c99b282c97c4c91fa2", + "fingerprint": "91be047b4af0855599d1e0c80c57511e6e771fb71a5cb33ca3a5bb86b7f91a2b", "typescript": { "status": "excluded", "reason": "Credential-free public QuickLink participant route owned by browser UI transport, not the server SDK.", @@ -1802,18 +1644,29 @@ "method": "POST", "path": "/quicklink/{token}/connection", "operationId": "selectQuickLinkConnection", - "fingerprint": "42dd82695c10fa09b91243b5e2693b48113fa8a8999d06667affb31384009feb", + "fingerprint": "c33269e7e8020f5a24d7edea80025c27c6cbd72a8fae197e6a604a40e9f3c92e", "typescript": { "status": "excluded", "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." } }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/history", + "operationId": "chooseQuickLinkHistory", + "fingerprint": "b633d7318a62fb39b55279d0087bb223dfe776a870b5dc0174026a51621a9a69", + "typescript": { + "status": "excluded", + "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." + } + }, { "family": "messaging", "method": "POST", "path": "/quicklink/{token}/phone", "operationId": "confirmQuickLinkPhone", - "fingerprint": "88b3d30d790478fb28a5247e957c8b5b94f7d686329e6cea8b2b65f25b2050e5", + "fingerprint": "4a28673d52f6b1a93649c08e94e930e4d6d6b57898b2fd8d7e16200c5221208c", "typescript": { "status": "excluded", "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." @@ -1824,12 +1677,23 @@ "method": "POST", "path": "/quicklink/{token}/phone/reset", "operationId": "resetQuickLinkPhone", - "fingerprint": "f9b305276c769cd7355a448b15531d7822145156d082abd1b9cbd148b877fe21", + "fingerprint": "ce13c7b679d3cd62f740ff8231e3f682020d2f86f053a246b489050b22aeecaf", "typescript": { "status": "excluded", "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." } }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/testing/start", + "operationId": "startQuickLinkTesting", + "fingerprint": "88b89e8bb98a55d4af5e12e29e177c6163fafd6798c672f05e2222a5c477f251", + "typescript": { + "status": "excluded", + "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." + } + }, { "family": "messaging", "method": "PUT", @@ -1995,17 +1859,6 @@ "method": "MessagingClient.labels.replaceForChat" } }, - { - "family": "messaging", - "method": "PUT", - "path": "/messaging/{session}/observation-policy", - "operationId": "updateSessionObservationPolicy", - "fingerprint": "91551ffaebb805f46fee7801dbe647f0eb58088f952bb2efbb236cabceca848c", - "typescript": { - "status": "covered", - "method": "MessagingClient.observationPolicies.updateForSession" - } - }, { "family": "messaging", "method": "PUT", @@ -2097,17 +1950,6 @@ "milestone": "bansafe-sdk" } }, - { - "family": "messaging", - "method": "PUT", - "path": "/messaging/projects/{projectId}/observation-policy", - "operationId": "updateProjectObservationPolicy", - "fingerprint": "9e13d93f2c894114eeaf1baef9c5e205c3e9c52b7815a7351c5b0d92e6d5abb6", - "typescript": { - "status": "covered", - "method": "MessagingClient.observationPolicies.updateForProject" - } - }, { "family": "messaging", "method": "PUT", @@ -2132,28 +1974,6 @@ "milestone": "bansafe-sdk" } }, - { - "family": "messaging", - "method": "PUT", - "path": "/messaging/sessions/{session}", - "operationId": "updateSession", - "fingerprint": "9e6f06a678aa417bfbbab3806dc956f7b07661469d20f4019726476892638c2e", - "typescript": { - "status": "covered", - "method": "MessagingClient.sessions.update" - } - }, - { - "family": "messaging", - "method": "PUT", - "path": "/messaging/sessions/{session}/client-rules", - "operationId": "setClientRules", - "fingerprint": "aeed3b849cd466c2de629adaa5b8db2ab0e256757d056b9cc7c577b77131b138", - "typescript": { - "status": "covered", - "method": "MessagingClient.clientTokens.updateRules" - } - }, { "family": "messaging", "method": "PUT", @@ -2165,78 +1985,6 @@ "method": "MessagingClient.webhooks.update" } }, - { - "family": "platform", - "method": "DELETE", - "path": "/admin/keys/{keyId}", - "operationId": "adminRevokeApiKeyApiKey", - "fingerprint": "ad3fb98e6b38fb8ec6768cf46bceeefdb3c7a388b1e1191bb6ffa5838ef250df", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "DELETE", - "path": "/admin/members/{memberId}", - "operationId": "adminRemoveMember", - "fingerprint": "75e97f3ec515dc4517c18b292c4519e0d2cae3d11407d81774aaf9595cc1438f", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "DELETE", - "path": "/admin/organization-invitations/{invitationId}", - "operationId": "adminRevokeOrganizationInvitation", - "fingerprint": "b701384882940998fdddfa78fcb5dfe02b5b382d698b4c2489c1c037ec9c071c", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "DELETE", - "path": "/admin/orgs/{orgId}", - "operationId": "adminCascadeDeleteOrganization", - "fingerprint": "f6eda7cd690044a374318154763ad041b2a3e0bda1ce7d8b3f796c2d766374b9", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "DELETE", - "path": "/admin/sessions/{sessionId}", - "operationId": "adminForceDeleteSession", - "fingerprint": "2eb87a5ec83007ba6d913a3fa177755f49d0b1039bb0c3735d0fd0256c4f90db", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "DELETE", - "path": "/admin/users/{userId}/onboarding", - "operationId": "adminResetOnboardingUser", - "fingerprint": "b23836e078c69c93e6746d3623b1a2fa5ad1777d609e76a2b7094d0452bcbe75", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, { "family": "platform", "method": "DELETE", @@ -2414,6 +2162,17 @@ "method": "Client.project(projectId).webhooks.delete" } }, + { + "family": "platform", + "method": "DELETE", + "path": "/platform/sessions/{session}/client-rules", + "operationId": "deleteClientRules", + "fingerprint": "5fffea2a4d46c0f9389dd2d3549d2b052f2d7315652acebc952af8d8c294b04b", + "typescript": { + "status": "covered", + "method": "MessagingClient.clientTokens.deleteRules" + } + }, { "family": "platform", "method": "DELETE", @@ -2451,294 +2210,31 @@ { "family": "platform", "method": "GET", - "path": "/admin/audit", - "operationId": "adminListAuditLogGlobal", - "fingerprint": "e5477965b7cc7cd4f4337cca70a1915ed9cbbe5ee68c086ef0215f11f5e45de6", + "path": "/api/account/meta-app", + "operationId": "getTeamMetaApp", + "fingerprint": "ddf2db2a91596fdb6fb8bd1d1951eefa43dfad45ea220967c1847d163cae2208", "typescript": { "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" + "reason": "Dashboard identity and account controls require a live browser account session; not part of the machine SDK." } }, { "family": "platform", "method": "GET", - "path": "/admin/audit/actions", - "operationId": "adminListAuditLogAdminActions", - "fingerprint": "df191f47422bf978cce0748865472244b5ace165374c63147cc2a1af57fdd0d7", + "path": "/console/auth/methods", + "operationId": "getUserProfileAuthMethods", + "fingerprint": "ffa79599aa50f22cc0e45f61f2f3d60969e1baf39f2981081ff7ca62380dcb47", "typescript": { "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" + "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, { "family": "platform", "method": "GET", - "path": "/admin/bans", - "operationId": "adminListRecentSessionBans", - "fingerprint": "cfd539cbdc66d1a9c8b85056c385a05545467bd261902328b896dce3da8092c5", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/bansafe/claims", - "operationId": "adminListBanSafeClaims", - "fingerprint": "a34081546c2402d8ea40df7f32edf843c081767b8daecb9630dfb00953645b47", - "typescript": { - "status": "excluded", - "reason": "Staff-only operation; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/billing/leaderboard", - "operationId": "adminTopOrgsByRevenueBilling", - "fingerprint": "d31daed2c1c0da08088703dc983c9fb53df90f20bf753730f2f4c912d8a8ff79", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/billing/lowbalance", - "operationId": "adminLowBalanceOrgsBilling", - "fingerprint": "5d924793098ac39f47510faef2ddab0fa7638be559e93ebcf457414108ece6ec", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/billing/revenue", - "operationId": "adminPlatformRevenueBilling", - "fingerprint": "0ad1bd44e376246a4b2dc97cb244ade316350327867643109265632419ae4048", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/campaigns", - "operationId": "adminListCampaigns", - "fingerprint": "fd8f3883ede44e0e6c213bc701f03df9d48e54388ff8cce8f09ee8a77df08e82", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/campaigns/optouts", - "operationId": "adminOptOutLeaderboardCampaign", - "fingerprint": "01d722c58eec761a06d1336587417ccb6963316dd47770bd654b9beb76260677", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/keys", - "operationId": "adminListApiKeys", - "fingerprint": "2d293fe8b9939d3256e612f8f19f91a56a893ed97465d90409748d1df6dc94af", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/kpis", - "operationId": "adminPlatformKpisPlatform", - "fingerprint": "dccaafd0658b8b9a13cb325616b9a7598b43c76918e69db056690e05d8eaa4be", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/organization-invitations", - "operationId": "adminListOrganizationInvitations", - "fingerprint": "8d4b607f062803467aa446e102951bd0370c3c404f0f33c6d2e861b8978ec688", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/orgs", - "operationId": "adminListOrganizations", - "fingerprint": "ffaebb8eac23863e4e802c511625a3a58d3f002526e4cb52cc842526d4288251", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/orgs/{orgId}", - "operationId": "adminGetOrganization", - "fingerprint": "bec7994abe925f71a1bdf7a667cee0c2b891e9f72eca47e049ddfadaedbd4692", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/orgs/features", - "operationId": "adminListOrganizationFeatureGroups", - "fingerprint": "2ce372b2a7f04b05d4b7164e3862638eb89d9d0943635082bad47ca9d12dfd36", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/pricing", - "operationId": "adminListPricing", - "fingerprint": "d02ecd212b73dc8858b3bb5489da8e0150e5643b353a8b814730ceaaea74da9a", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/sessions", - "operationId": "adminListSessions", - "fingerprint": "50c1e733bee6c7d90f9b52a5d67e4bde3eb55b0bbc0cf40c31e25b2387af58f6", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/signups", - "operationId": "adminRecentSignupsPlatform", - "fingerprint": "cfdba3ad7a7ebcd3af8f567190597dc4aec9976286c81b5f5df43ecb7b7affa2", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/tenants", - "operationId": "adminListTenants", - "fingerprint": "fdbde8f64e54cb69ddcc3e8ae7ce079c2e7c1f011d09a1d0bee56096c0ec2874", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/users", - "operationId": "adminSearchUser", - "fingerprint": "2ab1f78c94e4be651a08f5eb03efa029650905095170d6860e95975f35b5e95c", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/users/{userId}", - "operationId": "adminGetUser", - "fingerprint": "f3292d0669a786237e1fc3cd53f59217a0b869d0905666b00e2d694e1d973939", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/admin/webhooks", - "operationId": "adminListWebhooks", - "fingerprint": "a34600666cb5c7936c521619f6b2222ffea886fb657f5e7939addbdf66d395ac", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/api/account/meta-app", - "operationId": "getTeamMetaApp", - "fingerprint": "ddf2db2a91596fdb6fb8bd1d1951eefa43dfad45ea220967c1847d163cae2208", - "typescript": { - "status": "excluded", - "reason": "Dashboard identity and account controls require a live browser account session; not part of the machine SDK." - } - }, - { - "family": "platform", - "method": "GET", - "path": "/console/auth/methods", - "operationId": "getUserProfileAuthMethods", - "fingerprint": "ffa79599aa50f22cc0e45f61f2f3d60969e1baf39f2981081ff7ca62380dcb47", - "typescript": { - "status": "excluded", - "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "GET", - "path": "/console/auth/providers", - "operationId": "getUserProfileProviderConnectionUrl", - "fingerprint": "74f9f73669d60e5c41f2badd1542cc45045ba697a42ea707cf2799d0e8df8bbc", + "path": "/console/auth/providers", + "operationId": "getUserProfileProviderConnectionUrl", + "fingerprint": "74f9f73669d60e5c41f2badd1542cc45045ba697a42ea707cf2799d0e8df8bbc", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -2953,6 +2449,17 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "GET", + "path": "/console/session-configuration/{sessionId}", + "operationId": "getSessionConfiguration", + "fingerprint": "41ef7c74309f396237388dc9ee45e8f39942f768cf99da89906d388fc4878db4", + "typescript": { + "status": "excluded", + "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." + } + }, { "family": "platform", "method": "GET", @@ -3035,7 +2542,7 @@ "method": "GET", "path": "/platform/bansafe/claims", "operationId": "listBanSafeClaims", - "fingerprint": "a7ccdc2dc6d79435e97b265ae9e25aa708a6503b8faa2091604d3654122485b8", + "fingerprint": "63f132c3b7528d56b2f4bc2dd113521adb1c67799f5354ff3e40aef173f4b8e6", "typescript": { "status": "missing", "reason": "BanSafe policy and health resources have no handwritten SDK implementation in this revision.", @@ -3047,7 +2554,7 @@ "method": "GET", "path": "/platform/bansafe/claims/{claimId}", "operationId": "getBanSafeClaim", - "fingerprint": "3afc562fb6ab7525239320d8c5062c65bfb810f33b4cd86a3c4169c328417204", + "fingerprint": "b3bc0389bf050e435c065d25c2e31500ea838f2422324d29ac2381d6305512df", "typescript": { "status": "missing", "reason": "BanSafe policy and health resources have no handwritten SDK implementation in this revision.", @@ -3610,17 +3117,61 @@ "method": "Client.quickLinkSettings.retrieve" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/session-configuration", + "operationId": "getSessionConfigurationDefaults", + "fingerprint": "1304eb3cf35c0ebdd19c9b2e647f51eb1741681f2012a79b018a1174514ee0a9", + "typescript": { + "status": "covered", + "method": "Client.sessionConfiguration.retrieve" + } + }, { "family": "platform", "method": "GET", "path": "/platform/sessions", "operationId": "listPlatformSessions", - "fingerprint": "e9ff8f65460952f3324c354dfd285c107a075f50a6c074e98483c5d2d034ce56", + "fingerprint": "3f970eec5bff102fc4929ee2061c3bef4cb5f553bce5452d0abfc95ab552ea0b", "typescript": { "status": "covered", "method": "Client.sessions.list" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/sessions/{session}/client-rules", + "operationId": "getClientRules", + "fingerprint": "c3fbc3bf67159d7a92c52fe1901028854640fc517f8b4c5ba59cdf72ed2aecca", + "typescript": { + "status": "covered", + "method": "MessagingClient.clientTokens.retrieveRules" + } + }, + { + "family": "platform", + "method": "GET", + "path": "/platform/sessions/{sessionId}", + "operationId": "getSession", + "fingerprint": "55b25c2f9bc8e73807255f202b2b75adebacfb7f62b9232c136b96ca6ea83e53", + "typescript": { + "status": "covered", + "method": "Client.sessions.retrieve" + } + }, + { + "family": "platform", + "method": "GET", + "path": "/platform/sessions/{sessionId}/me", + "operationId": "getSessionAccount", + "fingerprint": "c00aeae61b1a22a3087170a197ca7d1f8fb85eacd5cd1b921babc346745fc283", + "typescript": { + "status": "covered", + "method": "MessagingClient.sessions.account" + } + }, { "family": "platform", "method": "GET", @@ -3633,6 +3184,17 @@ "milestone": "bansafe-sdk" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/sessions/{sessionId}/tier-quotes/{quoteId}", + "operationId": "getSessionTierChange", + "fingerprint": "79ce1d14ddd4b51c9ba59980026804bd4a89e0ea8c6cea1bf9faa12a449db4f4", + "typescript": { + "status": "covered", + "method": "Client.sessions.retrieveTierChange" + } + }, { "family": "platform", "method": "GET", @@ -3748,129 +3310,58 @@ { "family": "platform", "method": "PATCH", - "path": "/admin/members/{memberId}", - "operationId": "adminSetMemberRole", - "fingerprint": "127f1cd224c3e52a457570acf91592e6a5daa224802bbaa8ce5c7fd5c0bc4ecf", + "path": "/console/members/{memberId}", + "operationId": "updateMemberRole", + "fingerprint": "e2aea14bd2f072744ef866550eae42431b1fb3a317fd4fa0d2ee7af3b665ca91", "typescript": { "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" + "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, { "family": "platform", "method": "PATCH", - "path": "/admin/orgs/{orgId}", - "operationId": "adminRenameOrganization", - "fingerprint": "e5d51c28a9b0f245e9addf4b1303ac8e976b235cb4e858579ebf891ac1ebf1c0", + "path": "/console/organization", + "operationId": "updateOrganization", + "fingerprint": "70cef57aa18889aa825cb7bf85f7bc3718d19777ff4c83809596b4072710156c", "typescript": { "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" + "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, { "family": "platform", "method": "PATCH", - "path": "/admin/orgs/{orgId}/features", - "operationId": "adminSetOrganizationFeatureGroups", - "fingerprint": "b72fd41063a093f063ecbeda25da0e3156f4f67a80055075f7bcb6fed4f45e87", + "path": "/console/profile", + "operationId": "updateUserProfileField", + "fingerprint": "f391c72c2b8c67ac6bff47c4e95881d5a2ed06813c9f8289bd7c5c99a6c2ed69", "typescript": { "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" + "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, { "family": "platform", "method": "PATCH", - "path": "/admin/pricing/{tier}", - "operationId": "adminSetPricingActive", - "fingerprint": "66790bad7d680548c5a6cd1ca51b1820540acc9217841680e76e3c0fff79b15f", + "path": "/console/projects/{projectId}", + "operationId": "updateProject", + "fingerprint": "a935851ca44d63a27487ceb269f99b0ab1dfa8b122c5f2081bbc0eb6748528d9", "typescript": { "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" + "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, { "family": "platform", "method": "PATCH", - "path": "/admin/tenants/{tenantId}", - "operationId": "adminSetTenantActive", - "fingerprint": "82b8991488c9b616cb75c8280b91879e41e4936a0925a17227bef737ce3a9651", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "PATCH", - "path": "/console/members/{memberId}", - "operationId": "updateMemberRole", - "fingerprint": "e2aea14bd2f072744ef866550eae42431b1fb3a317fd4fa0d2ee7af3b665ca91", - "typescript": { - "status": "excluded", - "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "PATCH", - "path": "/console/organization", - "operationId": "updateOrganization", - "fingerprint": "70cef57aa18889aa825cb7bf85f7bc3718d19777ff4c83809596b4072710156c", + "path": "/console/webhooks/{webhookId}", + "operationId": "updateWebhook", + "fingerprint": "e6b17482be3dda8b30698dced920373db12c8d7441c3cc872dac57377e68e73f", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, - { - "family": "platform", - "method": "PATCH", - "path": "/console/profile", - "operationId": "updateUserProfileField", - "fingerprint": "f391c72c2b8c67ac6bff47c4e95881d5a2ed06813c9f8289bd7c5c99a6c2ed69", - "typescript": { - "status": "excluded", - "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "PATCH", - "path": "/console/projects/{projectId}", - "operationId": "updateProject", - "fingerprint": "a935851ca44d63a27487ceb269f99b0ab1dfa8b122c5f2081bbc0eb6748528d9", - "typescript": { - "status": "excluded", - "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "PATCH", - "path": "/console/webhooks/{webhookId}", - "operationId": "updateWebhook", - "fingerprint": "e6b17482be3dda8b30698dced920373db12c8d7441c3cc872dac57377e68e73f", - "typescript": { - "status": "excluded", - "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "PATCH", - "path": "/platform/billing/reminders", - "operationId": "updateBillingReminderSettings", - "fingerprint": "0a543d6338478d78b5ba06ab17f8b14ed5ed658fc099b6780ff99829d17fedab", - "typescript": { - "status": "covered", - "method": "Client.billing.updateReminderSettings" - } - }, { "family": "platform", "method": "PATCH", @@ -3921,7 +3412,7 @@ "method": "PATCH", "path": "/platform/sessions/{sessionId}", "operationId": "setSessionTierOverride", - "fingerprint": "07b7c8cebbe44acca79ba8e4ee40c4016a0ac0f31f482c864aeda45556fe5c53", + "fingerprint": "e33c0b4fee8ed6018f8103e82603e5fa15ce18fa05801435912050aaf0f30df2", "typescript": { "status": "covered", "method": "Client.sessions.setTierOverride" @@ -3950,230 +3441,6 @@ "method": "Client.webhooks.update" } }, - { - "family": "platform", - "method": "POST", - "path": "/admin/bansafe/claims/{claimId}/approve", - "operationId": "adminApproveBanSafeClaim", - "fingerprint": "afb58500d98361121e2c82d5e875feb6b904fd66562ae68890b03adf2af25ec9", - "typescript": { - "status": "excluded", - "reason": "Staff-only operation; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/bansafe/claims/{claimId}/deny", - "operationId": "adminDenyBanSafeClaim", - "fingerprint": "f0034a2b5e150ef9c1d7b1a12cf6b8581d1e28c94f5e3386bab09ed539030fee", - "typescript": { - "status": "excluded", - "reason": "Staff-only operation; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/bansafe/claims/{claimId}/reverse", - "operationId": "adminReverseBanSafeClaim", - "fingerprint": "5a1adcb22e84b1866e08345d2d13a839ac296f8ecdafbb3492170db26377cd83", - "typescript": { - "status": "excluded", - "reason": "Staff-only operation; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/bansafe/enforcement/{orgId}/{sessionId}/lift", - "operationId": "adminLiftBanSafeEnforcement", - "fingerprint": "d0161a4a2815db2a5c6ad3487b6554ed680af89da8a02b0c924ad819bd6a2300", - "typescript": { - "status": "excluded", - "reason": "Staff-only operation; not available to SDK credentials." - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/billing/credits", - "operationId": "adminCompCreditsBilling", - "fingerprint": "d192bf12f1e89727ab17eb33def248a5e19960cd167d11fcd1401a7356bfa138", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/billing/refunds", - "operationId": "adminRefundBilling", - "fingerprint": "4a287e42a26d0f0ca1b294b8c83d144fc8d8e3f4987077957b0cb4fc4a6ddf06", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/campaigns/{campaignId}/pause", - "operationId": "adminForcePauseCampaign", - "fingerprint": "8b7199bbdec1b72680bb0910fe782b44f510a91fb063fc6a6291848ea6f2ca65", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/campaigns/{campaignId}/stop", - "operationId": "adminForceStopCampaign", - "fingerprint": "5ed62146a21b408c1410ea6a2e0b2da1c9ceb23ae36c5b5f87a900e313ec4c04", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/members/invites", - "operationId": "adminInviteMember", - "fingerprint": "f811d6820060d3e775f95c48467ee49cbc35825a48a5f3e43aee10b49ffb0fc3", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/organization-invitations", - "operationId": "adminCreateOrganizationInvitation", - "fingerprint": "d4f01d177f165f9f8cfcc236bc91e04e535eab599e246fff2100a63b07a3e968", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/orgs/{orgId}/freeze", - "operationId": "adminFreezeOrganization", - "fingerprint": "0a6fd21fccc917e71c23c755c19a930f9f13f0b24ea263f0b194c618c0243e14", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/orgs/{orgId}/unfreeze", - "operationId": "adminUnfreezeOrganization", - "fingerprint": "b17365d46734682f186d9ad50098b89a656c7b65056f2c841ded8ec1392361ae", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/sessions/{sessionId}/comp", - "operationId": "adminCompSessionDaySession", - "fingerprint": "50bead5c3e70d64cdee76f39d0c994c043fa5ef6273ac9aa21c71600991f62fc", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/sessions/{sessionId}/stop", - "operationId": "adminForceStopSession", - "fingerprint": "d274e8b0da00a9304d24a35b2f476cf24717aaeedbb96cb53f1b76726a338f2e", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/sync", - "operationId": "adminEnsureCurrentUserStaffSyncStaffSync", - "fingerprint": "d679be6a9d2c68f86958d4397c7da4dc403d6e92118bbbc46556ac34e22c2da6", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/users/{userId}/signout", - "operationId": "adminForceSignOutUser", - "fingerprint": "39e7206b997cce91d02f71db8e048514302cadd2ac34b5e9f8642428336d81c0", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/users/{userId}/suspend", - "operationId": "adminSuspendUser", - "fingerprint": "eecb797b37f6e2d36fad2f99ef525ba74699e08af6e6452a9e3f55f801f762d2", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/users/{userId}/unsuspend", - "operationId": "adminUnsuspendUser", - "fingerprint": "9669e2f1f4ccbd4b4b4061482d9fce7201b465bf4d06ae121a835c11ffc22551", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/admin/webhooks/{webhookId}/disable", - "operationId": "adminForceDisableWebhook", - "fingerprint": "1213dfc05c2fa87078704b2e27606a2d5ecb7a4c80d0b74af5eb5e75c435f296", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, { "family": "platform", "method": "POST", @@ -4210,20 +3477,20 @@ { "family": "platform", "method": "POST", - "path": "/api/admin/features/control", - "operationId": "setFeatureControl", - "fingerprint": "f93edce20a263a411345668c9ada68ab9d2ab423dd0ecababc3b370c3b94322b", + "path": "/console/auth/totp", + "operationId": "enrollTotpUserProfile", + "fingerprint": "ed41939c43994d7af2ac9b3634806a832dd4f22e7257aa1eb799e8a27363292b", "typescript": { "status": "excluded", - "reason": "Staff-only operation; not available to SDK credentials." + "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, { "family": "platform", "method": "POST", - "path": "/console/auth/totp", - "operationId": "enrollTotpUserProfile", - "fingerprint": "ed41939c43994d7af2ac9b3634806a832dd4f22e7257aa1eb799e8a27363292b", + "path": "/console/auth/totp/verify", + "operationId": "verifyTotpUserProfile", + "fingerprint": "4ae5afc998a3b81666c0e3dd4a9e47fd3a95ff0e230d9536fd15e1a3cc1b25bb", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -4232,9 +3499,20 @@ { "family": "platform", "method": "POST", - "path": "/console/auth/totp/verify", - "operationId": "verifyTotpUserProfile", - "fingerprint": "4ae5afc998a3b81666c0e3dd4a9e47fd3a95ff0e230d9536fd15e1a3cc1b25bb", + "path": "/console/bansafe/enforcement/{session}/appeal", + "operationId": "appealBanSafeEnforcement", + "fingerprint": "c7ba6128a484f1549270e0d3462dbd275d03f18d9523852b40b45ae0b91e0e70", + "typescript": { + "status": "excluded", + "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." + } + }, + { + "family": "platform", + "method": "POST", + "path": "/console/bansafe/findings/{findingId}/acknowledge", + "operationId": "acknowledgeBanSafeFinding", + "fingerprint": "c783f82822c624258d75082e59c1dba38a8517866832b07d749624300dbaa20d", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -4504,30 +3782,6 @@ "method": "Client.audiences.createUpload" } }, - { - "family": "platform", - "method": "POST", - "path": "/platform/bansafe/enforcement/{session}/appeal", - "operationId": "appealBanSafeEnforcement", - "fingerprint": "867fe3af738efe95669cc08e2782e794cda47960730aec7e28717b10eaba7555", - "typescript": { - "status": "missing", - "reason": "BanSafe policy and health resources have no handwritten SDK implementation in this revision.", - "milestone": "bansafe-sdk" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/platform/bansafe/findings/{findingId}/acknowledge", - "operationId": "acknowledgeBanSafeFinding", - "fingerprint": "2400d9c5b8144328e2f76a86464105ce031f778d6570b3aa77ac8f4f4dc0d670", - "typescript": { - "status": "missing", - "reason": "BanSafe policy and health resources have no handwritten SDK implementation in this revision.", - "milestone": "bansafe-sdk" - } - }, { "family": "platform", "method": "POST", @@ -4640,6 +3894,17 @@ "method": "Client.campaigns.stop" } }, + { + "family": "platform", + "method": "POST", + "path": "/platform/client-tokens", + "operationId": "mintClientToken", + "fingerprint": "f5118aefb6a929d473b36cc711ffa7460120b88766bf283e7a696a2215b237e4", + "typescript": { + "status": "covered", + "method": "MessagingClient.clientTokens.mint" + } + }, { "family": "platform", "method": "POST", @@ -4872,12 +4137,70 @@ "method": "Client.project(projectId).webhooks.test" } }, + { + "family": "platform", + "method": "POST", + "path": "/platform/sessions/{sessionId}/handoff-ack", + "operationId": "sessionHandoffAck", + "fingerprint": "c06814ef2f4f1f6c18dac3e05a8fca165c3e4fed06ee3a2591545b4a9814b328", + "typescript": { + "status": "excluded", + "reason": "Encrypted session handoff remains outside the public SDK until its dedicated security and atomicity contract is complete.", + "milestone": "security-design" + } + }, + { + "family": "platform", + "method": "POST", + "path": "/platform/sessions/{sessionId}/handoff-import", + "operationId": "sessionHandoffImport", + "fingerprint": "d456d3a1f97abf9e68aa52fdccdb3ad37a9f28bcabf81030ba722fe5538bae86", + "typescript": { + "status": "excluded", + "reason": "Encrypted session handoff remains outside the public SDK until its dedicated security and atomicity contract is complete.", + "milestone": "security-design" + } + }, + { + "family": "platform", + "method": "POST", + "path": "/platform/sessions/{sessionId}/handoff-keys", + "operationId": "sessionHandoffKeys", + "fingerprint": "c2cebd98d925e3c4aeb9502718ac98ccbca44ce2916fb80b8419849af0f7344e", + "typescript": { + "status": "excluded", + "reason": "Encrypted session handoff remains outside the public SDK until its dedicated security and atomicity contract is complete.", + "milestone": "security-design" + } + }, + { + "family": "platform", + "method": "POST", + "path": "/platform/sessions/{sessionId}/logout", + "operationId": "logoutSession", + "fingerprint": "b8b071fea00af83ff6aa490cfde76b6c02f1cffdae51b5216eb64c3e9341be3e", + "typescript": { + "status": "covered", + "method": "MessagingClient.sessions.logout" + } + }, + { + "family": "platform", + "method": "POST", + "path": "/platform/sessions/{sessionId}/restart", + "operationId": "restartSession", + "fingerprint": "5358b91281263bdc168bad36a54c9d9555899e40fe20e919f952ce4d621c0975", + "typescript": { + "status": "covered", + "method": "MessagingClient.sessions.restart" + } + }, { "family": "platform", "method": "POST", "path": "/platform/sessions/{sessionId}/start", "operationId": "startPlatformSession", - "fingerprint": "3aaffac72ee663399124f68c26b6b8aec93fdfa74e5f5a437ed40e5946e3f0f4", + "fingerprint": "4c542237b33d3c6a86ccf3f8f938e46ce021f90827ea52c12205da7e231b8df4", "typescript": { "status": "covered", "method": "Client.sessions.start" @@ -4897,34 +4220,46 @@ { "family": "platform", "method": "POST", - "path": "/platform/sessions/delete", - "operationId": "deleteSessions", - "fingerprint": "6d12a65fc14b96c4fad4656544fb036a8839c86dcc66457086c03407284ce987", + "path": "/platform/sessions/{sessionId}/tier-quotes", + "operationId": "quoteSessionTierChange", + "fingerprint": "fe210e4bcf1111b76f0be6eff3a2f9d3723b281df313ef0801981acba7f307b4", "typescript": { "status": "covered", - "method": "Client.sessions.deleteMany" + "method": "Client.sessions.quoteTierChange" } }, { "family": "platform", "method": "POST", - "path": "/platform/sessions/stop", - "operationId": "stopSessions", - "fingerprint": "acbdb5bf30110c5984cb8e8cda57e99ce7e05e4221b35b7be9c53f31541e2fc3", + "path": "/platform/sessions/{sessionId}/upload-appstate-keys", + "operationId": "sessionUploadAppStateKeys", + "fingerprint": "924282b08bb78e9236b191865a4c069f17ae64653fe0ac7699c48c6bc69691e0", + "typescript": { + "status": "excluded", + "reason": "Encrypted session handoff remains outside the public SDK until its dedicated security and atomicity contract is complete.", + "milestone": "security-design" + } + }, + { + "family": "platform", + "method": "POST", + "path": "/platform/sessions/delete", + "operationId": "deleteSessions", + "fingerprint": "802829ea55ee5a1a82319541c7eebce7e504aaba78c89cc59889a431e89a4e97", "typescript": { "status": "covered", - "method": "Client.sessions.stopMany" + "method": "Client.sessions.deleteMany" } }, { "family": "platform", "method": "POST", - "path": "/platform/sessions/testing", - "operationId": "createTestingSession", - "fingerprint": "90fc5729b10ba544a24366525b300cd3baa8eda2480baf47c575bf58331edeae", + "path": "/platform/sessions/stop", + "operationId": "stopSessions", + "fingerprint": "74e62980b1175423073e824243c0c5b7c9c585d1f12954d045e6e4e55f72502e", "typescript": { "status": "covered", - "method": "Client.sessions.createTesting" + "method": "Client.sessions.stopMany" } }, { @@ -4983,18 +4318,6 @@ "method": "Client.webhooks.test" } }, - { - "family": "platform", - "method": "PUT", - "path": "/admin/pricing/{tier}", - "operationId": "adminUpdatePricing", - "fingerprint": "186c89615d21dd90e3a7e22e7a532af8a0ff64a89dfcca47d59fb082801a9136", - "typescript": { - "status": "excluded", - "reason": "Staff administration route authenticated with ConsoleSession; it is outside the public server SDK credential boundary.", - "milestone": "credential-boundary" - } - }, { "family": "platform", "method": "PUT", @@ -5061,6 +4384,17 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "PUT", + "path": "/console/session-configuration/{sessionId}", + "operationId": "updateSessionConfiguration", + "fingerprint": "ad18136611bc488d39e8d606356f40c6816933d69693d43389af6cfe84cd8be2", + "typescript": { + "status": "excluded", + "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." + } + }, { "family": "platform", "method": "PUT", @@ -5120,6 +4454,39 @@ "method": "Client.quickLinkSettings.update" } }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/session-configuration", + "operationId": "updateSessionConfigurationDefaults", + "fingerprint": "66124c83e129f5d345df8916ef875912a79ea9ff7d077b0867323ac9065e0e1b", + "typescript": { + "status": "covered", + "method": "Client.sessionConfiguration.update" + } + }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/sessions/{session}/client-rules", + "operationId": "setClientRules", + "fingerprint": "dbe0bbd3a9044ad290f4029ae627a5b1cae80bbaca22dd64ec6dad6bc494483e", + "typescript": { + "status": "covered", + "method": "MessagingClient.clientTokens.updateRules" + } + }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/sessions/{sessionId}", + "operationId": "updateSession", + "fingerprint": "6c2e8c579273379404cb68ae6d4a0e9586eeed7d9fa295cb7c728e32a6d04d23", + "typescript": { + "status": "covered", + "method": "Client.sessions.update" + } + }, { "family": "platform", "method": "PUT", diff --git a/contracts/openapi.messaging.json b/contracts/openapi.messaging.json index 4144737d..b70f4d1a 100644 --- a/contracts/openapi.messaging.json +++ b/contracts/openapi.messaging.json @@ -4912,21 +4912,24 @@ "example": "2026-09-06T14:03:12.000Z" }, "measuredCents": { - "type": "integer", + "type": "number", + "description": "Credit quantity this number consumed inside the window, with up to six decimal places.", "minimum": 0, - "description": "Credits this number consumed inside the window, in cents.", + "multipleOf": 0.000001, "example": 1080 }, "capCents": { - "type": "integer", + "type": "number", + "description": "Maximum credit quantity this claim can return, with up to six decimal places.", "minimum": 0, - "description": "The most a claim on this number's plan can return, in cents.", + "multipleOf": 0.000001, "example": 1080 }, "amountCents": { - "type": "integer", + "type": "number", + "description": "Credit quantity this claim returns, with up to six decimal places.", "minimum": 0, - "description": "What this claim returns, in cents.", + "multipleOf": 0.000001, "example": 1080 }, "summary": { @@ -6016,23 +6019,20 @@ "success" ] }, - "ClientToken": { + "QRResponse": { "type": "object", "properties": { - "token": { + "qr": { "type": "string", - "description": "Client token with pmfa_ct_ prefix" + "description": "QR code data to encode into a scannable image" }, - "expiresAt": { - "type": "string" + "event": { + "type": "string", + "description": "QR channel event (e.g. success, timeout)" } - }, - "required": [ - "token", - "expiresAt" - ] + } }, - "MintClientTokenResponse": { + "GetQRCodeResponse": { "type": "object", "properties": { "success": { @@ -6042,7 +6042,7 @@ ] }, "data": { - "$ref": "#/components/schemas/ClientToken" + "$ref": "#/components/schemas/QRResponse" } }, "required": [ @@ -6050,64 +6050,19 @@ "data" ] }, - "MintClientTokenRequest": { + "PairCodeResponse": { "type": "object", "properties": { - "session": { - "type": "string", - "minLength": 1 - }, - "ephemeralId": { + "code": { "type": "string", - "minLength": 1 - }, - "ttlSeconds": { - "type": "integer" - } - }, - "required": [ - "session", - "ephemeralId" - ] - }, - "ClientRules": { - "type": "object", - "properties": { - "actions": { - "type": "array", - "items": { - "type": "string" - } - }, - "recipientMode": { - "type": "string" - }, - "verifiedJids": { - "type": "array", - "items": { - "type": "string" - } - }, - "rateLimits": { - "type": "object", - "properties": { - "perMinute": { - "type": "integer", - "minimum": 0 - }, - "perDay": { - "type": "integer", - "minimum": 0 - } - } + "description": "Numeric pairing code for device linking" } }, "required": [ - "actions", - "recipientMode" + "code" ] }, - "GetClientRulesResponse": { + "RequestPairCodeResponse": { "type": "object", "properties": { "success": { @@ -6117,7 +6072,7 @@ ] }, "data": { - "$ref": "#/components/schemas/ClientRules" + "$ref": "#/components/schemas/PairCodeResponse" } }, "required": [ @@ -6125,288 +6080,92 @@ "data" ] }, - "SetClientRulesRequest": { + "PairCodeRequest": { "type": "object", "properties": { - "recipientMode": { - "type": "string" - }, - "allowedActions": { - "type": "string", - "default": "" - }, - "rateLimit": { - "type": "integer", - "default": 0 - }, - "maxDaily": { - "type": "integer", - "default": 0 - }, - "allowedOrigins": { - "type": "string", - "default": "" - }, - "enabled": { - "type": "boolean" - }, - "maxConcurrency": { - "type": "integer", - "minimum": 0, - "default": 0 - }, - "allowedNumber": { + "phone": { "type": "string", - "default": "" - }, - "maxSetupsPerMinute": { - "type": "integer", - "minimum": 0, - "maximum": 600, - "default": 0 + "minLength": 1 } }, "required": [ - "recipientMode", - "enabled" + "phone" ] }, - "Session": { + "ConversationIdentity": { "type": "object", "properties": { - "sessionId": { - "type": "string", - "description": "Canonical per-org session identifier. Use this in /messaging/sessions/{session}/* paths." - }, - "name": { + "id": { "type": "string", - "description": "Display label, or the session identifier when no label is set." + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" }, - "externalId": { + "phoneNumber": { "type": "string", - "description": "Integrator correlation supplied when creating the QuickLink for this session." - }, - "tenantId": { - "type": "string" + "pattern": "^\\+[1-9]\\d{1,14}$" }, - "type": { + "bsuid": { "type": "string", - "enum": [ - "linked_device", - "cloud_api" - ], - "description": "How the session connects to WhatsApp. 'linked_device' pairs as a WhatsApp linked device; 'cloud_api' uses the Meta Cloud API. Immutable after create." - }, - "testMode": { - "type": "boolean", - "description": "Whether this session was created as a simulated testing number." + "pattern": "^[A-Z]{2}\\.[A-Za-z0-9]{1,128}$" }, - "status": { + "username": { "type": "string", - "description": "Current status: CONNECTING, CONNECTED, or DISCONNECTED" - }, - "statusReason": { - "type": "string" - }, - "config": { - "type": "object", - "properties": { - "historySync": { - "type": "object", - "properties": { - "mode": { - "type": "string", - "enum": [ - "metadata_only", - "deliver" - ] - }, - "requestFull": { - "type": "boolean" - } - }, - "required": [ - "mode", - "requestFull" - ] - } - } - }, - "createdAt": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - }, - "required": [ - "sessionId", - "name", - "tenantId", - "type", - "testMode", - "status", - "config", - "createdAt", - "updatedAt" - ] - }, - "ListSessionsResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Session" - } - } - }, - "required": [ - "success", - "data" - ] - }, - "SessionOperation": { - "allOf": [ - { - "$ref": "#/components/schemas/Session" - }, - { - "type": "object", - "properties": {} - } - ] - }, - "CreateSessionResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/SessionOperation" + "minLength": 1, + "maxLength": 256 } }, "required": [ - "success", - "data" - ] + "id" + ], + "additionalProperties": false, + "description": "The message's conversation, including its public ID and known aliases." }, - "CloudApiCredentials": { + "SentMessage": { "type": "object", "properties": { - "phoneNumberId": { - "type": "string", - "minLength": 1 - }, - "wabaId": { - "type": "string", - "minLength": 1 - }, - "businessAccountId": { - "type": "string" - }, - "appId": { + "id": { "type": "string", - "minLength": 1 + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" }, - "appSecret": { + "whatsapp_id": { "type": "string", "minLength": 1 }, - "systemUserToken": { - "type": "string", - "minLength": 1 + "conversation": { + "$ref": "#/components/schemas/ConversationIdentity" }, - "webhookVerifyToken": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "phoneNumberId", - "wabaId", - "appId", - "appSecret", - "systemUserToken", - "webhookVerifyToken" - ], - "description": "Credentials required when type is cloud_api. Stored encrypted." - }, - "HistorySyncPolicy": { - "type": "object", - "properties": { - "mode": { + "timestamp": { "type": "string", - "enum": [ - "metadata_only", - "deliver" - ], - "default": "metadata_only" + "format": "date-time" }, - "requestFull": { - "type": "boolean", - "default": false - } - }, - "additionalProperties": false, - "description": "History processing for linked-device sessions. Defaults to metadata-only processing." - }, - "SessionConfig": { - "type": "object", - "properties": { - "historySync": { - "$ref": "#/components/schemas/HistorySyncPolicy" - } - }, - "additionalProperties": false - }, - "CreateSessionRequest": { - "type": "object", - "properties": { - "name": { + "status": { "type": "string", - "minLength": 1, - "description": "Optional display label. The server generates the session identifier." - }, - "startOnConnect": { - "type": "boolean", - "default": false, - "description": "Start connecting immediately after creation. Pairing or credentials are still required." + "description": "Upstream acceptance status. Delivery and read receipts arrive separately." }, "type": { - "type": "string", - "enum": [ - "linked_device", - "cloud_api" - ], - "default": "linked_device", - "description": "Connection type. Immutable after creation." - }, - "testMode": { - "type": "boolean", - "default": false, - "description": "Create a simulated testing session. Requires team enrollment in the Test numbers beta; unavailable teams receive 403." + "type": "string" }, - "cloudApi": { - "$ref": "#/components/schemas/CloudApiCredentials" + "content": { + "$ref": "#/components/schemas/MessageContent" }, - "config": { - "$ref": "#/components/schemas/SessionConfig" + "mediaId": { + "type": "string" } }, - "additionalProperties": false + "required": [ + "id", + "whatsapp_id", + "conversation", + "timestamp", + "status", + "type", + "content" + ] }, - "GetSessionResponse": { + "SendMessageResponse": { "type": "object", "properties": { "success": { @@ -6416,7 +6175,7 @@ ] }, "data": { - "$ref": "#/components/schemas/Session" + "$ref": "#/components/schemas/SentMessage" } }, "required": [ @@ -6424,7 +6183,7 @@ "data" ] }, - "UpdateSessionResponse": { + "AsyncAcceptedResponse": { "type": "object", "properties": { "success": { @@ -6434,7 +6193,16 @@ ] }, "data": { - "$ref": "#/components/schemas/Session" + "type": "object", + "properties": { + "requestId": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "requestId" + ] } }, "required": [ @@ -6442,609 +6210,162 @@ "data" ] }, - "UpdateSessionRequest": { + "PublicError": { "type": "object", "properties": { - "config": { + "error": { "type": "object", - "additionalProperties": true - }, - "historySync": { - "$ref": "#/components/schemas/HistorySyncPolicy" - } - } - }, - "OperationAccepted": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true + "properties": { + "type": { + "type": "string", + "enum": [ + "invalid_request_error", + "authentication_error", + "permission_error", + "conflict_error", + "rate_limit_error", + "api_error", + "upstream_error" + ] + }, + "code": { + "type": "string", + "enum": [ + "invalid_parameter", + "invalid_credential", + "entitlement_required", + "method_not_allowed", + "permission_denied", + "missing_scope", + "resource_not_found", + "media_fetch_failed", + "resource_gone", + "state_conflict", + "rate_limit_exceeded", + "internal_error", + "operation_not_supported", + "upstream_failure", + "service_unavailable", + "credential_verification_unavailable", + "session_not_ready", + "command_dispatch_failed", + "result_unknown" + ] + }, + "message": { + "type": "string" + }, + "param": { + "type": "string", + "nullable": true + } + }, + "required": [ + "type", + "code", + "message", + "param" ] }, - "message": { - "type": "string" + "data": { + "type": "string", + "nullable": true, + "enum": [ + null + ], + "description": "Always null for a failed request." }, - "immediate": { - "type": "boolean" + "docs": { + "type": "string", + "format": "uri" } }, "required": [ - "success", - "message" + "error", + "data", + "docs" ] }, - "WhatsAppAccount": { + "ConversationReference": { "type": "object", "properties": { "id": { "type": "string", "pattern": "^[1-9][0-9]{0,18}$", - "description": "Account ID when supplied by the connection.", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", "example": "739182640518203" }, "phoneNumber": { - "type": "string" + "type": "string", + "pattern": "^\\+[1-9]\\d{1,14}$" }, "bsuid": { - "type": "string" + "type": "string", + "pattern": "^[A-Z]{2}\\.[A-Za-z0-9]{1,128}$" }, "username": { - "type": "string" - }, - "pushName": { - "type": "string" - }, - "businessName": { - "type": "string" - }, - "platform": { - "type": "string" - }, - "profilePicUrl": { - "type": "string" - } - }, - "required": [ - "pushName" - ] - }, - "GetSessionAccountResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/WhatsAppAccount" - } - }, - "required": [ - "success", - "data" - ] - }, - "HandoffKeysResponse": { - "type": "object", - "properties": { - "runnerPublicKeyB64": { "type": "string", - "description": "Curve25519 public key the customer must seal their snapshot to. Base64 (URL-safe, no padding)." - }, - "handoffId": { - "type": "string" + "minLength": 1, + "maxLength": 256, + "description": "Known username. This field alone does not select a conversation." } }, - "required": [ - "runnerPublicKeyB64", - "handoffId" - ] - }, - "HandoffKeysSuccess": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "id" ] }, - "data": { - "$ref": "#/components/schemas/HandoffKeysResponse" - } - }, - "required": [ - "success", - "data" - ] - }, - "HandoffImportSuccess": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true + { + "required": [ + "phoneNumber" ] }, - "data": { - "$ref": "#/components/schemas/SuccessResponse" + { + "required": [ + "bsuid" + ] } - }, - "required": [ - "success", - "data" ] }, - "HandoffImportRequest": { + "QuotedMessage": { "type": "object", "properties": { - "handoffId": { + "id": { "type": "string", - "description": "Round-trip id returned by POST /handoff-keys." + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Polymorfa resource ID. Keep this value as a string.", + "example": "739182640518203" }, - "sealedEnvelopeB64": { - "type": "string", - "description": "Sealed-box envelope (ephemeral_pub(32) || ciphertext) wrapping the customer's SignalStoreSnapshot. Base64." + "type": { + "type": "string" }, - "snapshotHashHex": { - "type": "string", - "description": "SHA-256 of the unsealed snapshot JSON, hex-encoded. The runner verifies after decrypt before flipping bartenderMode." + "text": { + "type": "string" } }, "required": [ - "handoffId", - "sealedEnvelopeB64", - "snapshotHashHex" - ] + "id" + ], + "description": "Message being replied to." }, - "UploadAppStateKeysSuccess": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/SuccessResponse" - } - }, - "required": [ - "success", - "data" - ] - }, - "UploadAppStateKeysRequest": { - "type": "object", - "properties": { - "keys": { - "type": "array", - "items": { - "type": "object", - "properties": { - "keyId": { - "type": "string", - "description": "Base64-encoded AppStateSyncKey id (URL-safe-no-pad)." - }, - "keyB64": { - "type": "string", - "description": "Base64-encoded 32-byte symmetric key bytes." - }, - "fingerprintB64": { - "type": "string", - "description": "Base64-encoded WA fingerprint blob (optional)." - }, - "timestamp": { - "type": "number", - "description": "Unix-seconds timestamp WA stamped on the key." - } - }, - "required": [ - "keyId", - "keyB64", - "timestamp" - ] - } - }, - "upgradeTo": { - "type": "string", - "enum": [ - "passthrough_plus" - ], - "description": "When set, atomically flips sessions.config.bartenderMode → passthrough_plus after the keys persist. Eliminates the window where mode is set but no keys are staged." - } - }, - "required": [ - "keys" - ] - }, - "HandoffAckSuccess": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/SuccessResponse" - } - }, - "required": [ - "success", - "data" - ] - }, - "HandoffAckRequest": { - "type": "object", - "properties": { - "handoffId": { - "type": "string" - }, - "snapshotHashHex": { - "type": "string", - "description": "SHA-256 of the snapshot the customer received via mode.handoff_export. API verifies this matches what the runner exported before flipping bartenderMode." - } - }, - "required": [ - "handoffId", - "snapshotHashHex" - ] - }, - "QRResponse": { - "type": "object", - "properties": { - "qr": { - "type": "string", - "description": "QR code data to encode into a scannable image" - }, - "event": { - "type": "string", - "description": "QR channel event (e.g. success, timeout)" - } - } - }, - "GetQRCodeResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/QRResponse" - } - }, - "required": [ - "success", - "data" - ] - }, - "PairCodeResponse": { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Numeric pairing code for device linking" - } - }, - "required": [ - "code" - ] - }, - "RequestPairCodeResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/PairCodeResponse" - } - }, - "required": [ - "success", - "data" - ] - }, - "PairCodeRequest": { - "type": "object", - "properties": { - "phone": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "phone" - ] - }, - "ConversationIdentity": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^[1-9][0-9]{0,18}$", - "description": "Opaque Polymorfa resource ID. Keep this value as a string.", - "example": "739182640518203" - }, - "phoneNumber": { - "type": "string", - "pattern": "^\\+[1-9]\\d{1,14}$" - }, - "bsuid": { - "type": "string", - "pattern": "^[A-Z]{2}\\.[A-Za-z0-9]{1,128}$" - }, - "username": { - "type": "string", - "minLength": 1, - "maxLength": 256 - } - }, - "required": [ - "id" - ], - "additionalProperties": false, - "description": "The message's conversation, including its public ID and known aliases." - }, - "SentMessage": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^[1-9][0-9]{0,18}$", - "description": "Opaque Polymorfa resource ID. Keep this value as a string.", - "example": "739182640518203" - }, - "whatsapp_id": { - "type": "string", - "minLength": 1 - }, - "conversation": { - "$ref": "#/components/schemas/ConversationIdentity" - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "Upstream acceptance status. Delivery and read receipts arrive separately." - }, - "type": { - "type": "string" - }, - "content": { - "$ref": "#/components/schemas/MessageContent" - }, - "mediaId": { - "type": "string" - } - }, - "required": [ - "id", - "whatsapp_id", - "conversation", - "timestamp", - "status", - "type", - "content" - ] - }, - "SendMessageResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/SentMessage" - } - }, - "required": [ - "success", - "data" - ] - }, - "AsyncAcceptedResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "type": "object", - "properties": { - "requestId": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "requestId" - ] - } - }, - "required": [ - "success", - "data" - ] - }, - "PublicError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "invalid_request_error", - "authentication_error", - "permission_error", - "conflict_error", - "rate_limit_error", - "api_error", - "upstream_error" - ] - }, - "code": { - "type": "string", - "enum": [ - "invalid_parameter", - "invalid_credential", - "entitlement_required", - "method_not_allowed", - "permission_denied", - "missing_scope", - "resource_not_found", - "media_fetch_failed", - "resource_gone", - "state_conflict", - "rate_limit_exceeded", - "internal_error", - "operation_not_supported", - "upstream_failure", - "service_unavailable", - "credential_verification_unavailable", - "session_not_ready", - "command_dispatch_failed", - "result_unknown" - ] - }, - "message": { - "type": "string" - }, - "param": { - "type": "string", - "nullable": true - } - }, - "required": [ - "type", - "code", - "message", - "param" - ] - }, - "data": { - "type": "string", - "nullable": true, - "enum": [ - null - ], - "description": "Always null for a failed request." - }, - "docs": { - "type": "string", - "format": "uri" - } - }, - "required": [ - "error", - "data", - "docs" - ] - }, - "ConversationReference": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^[1-9][0-9]{0,18}$", - "description": "Opaque Polymorfa resource ID. Keep this value as a string.", - "example": "739182640518203" - }, - "phoneNumber": { - "type": "string", - "pattern": "^\\+[1-9]\\d{1,14}$" - }, - "bsuid": { - "type": "string", - "pattern": "^[A-Z]{2}\\.[A-Za-z0-9]{1,128}$" - }, - "username": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "Known username. This field alone does not select a conversation." - } - }, - "additionalProperties": false, - "anyOf": [ - { - "required": [ - "id" - ] - }, - { - "required": [ - "phoneNumber" - ] - }, - { - "required": [ - "bsuid" - ] - } - ] - }, - "QuotedMessage": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^[1-9][0-9]{0,18}$", - "description": "Opaque Polymorfa resource ID. Keep this value as a string.", - "example": "739182640518203" - }, - "type": { - "type": "string" - }, - "text": { - "type": "string" - } - }, - "required": [ - "id" - ], - "description": "Message being replied to." - }, - "MessageContent": { - "description": "Exactly one message kind: text, image, location, or another supported content field.", - "oneOf": [ - { - "type": "object", - "properties": { - "text": { - "type": "string", - "minLength": 1, - "description": "Text to send." - } - }, - "required": [ - "text" - ], - "additionalProperties": false + "MessageContent": { + "description": "Exactly one message kind: text, image, location, or another supported content field.", + "oneOf": [ + { + "type": "object", + "properties": { + "text": { + "type": "string", + "minLength": 1, + "description": "Text to send." + } + }, + "required": [ + "text" + ], + "additionalProperties": false }, { "type": "object", @@ -12782,24 +12103,6 @@ "data" ] }, - "UpdateProjectObservationPolicyResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/ProjectObservationPolicy" - } - }, - "required": [ - "success", - "data" - ] - }, "SessionObservationPolicy": { "type": "object", "properties": { @@ -12973,24 +12276,6 @@ "data" ] }, - "UpdateSessionObservationPolicyResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/SessionObservationPolicy" - } - }, - "required": [ - "success", - "data" - ] - }, "ProfileData": { "type": "object", "properties": { @@ -14236,76 +13521,6 @@ } } }, - "EmbeddedSignupResult": { - "type": "object", - "properties": { - "code": { - "type": "string", - "minLength": 1, - "maxLength": 4096 - }, - "wabaId": { - "type": "string", - "pattern": "^\\d+$" - }, - "phoneNumberId": { - "type": "string", - "pattern": "^\\d+$" - }, - "coexistence": { - "type": "boolean", - "default": true - }, - "historySync": { - "type": "boolean", - "default": false - } - }, - "required": [ - "code", - "wabaId", - "phoneNumberId" - ], - "additionalProperties": false - }, - "EmbeddedSignupRequest": { - "type": "object", - "properties": { - "session": { - "type": "string", - "minLength": 1 - }, - "projectId": { - "type": "string", - "format": "uuid" - }, - "result": { - "$ref": "#/components/schemas/EmbeddedSignupResult" - }, - "metaApp": { - "type": "object", - "properties": { - "appId": { - "type": "string", - "pattern": "^\\d+$" - }, - "appSecret": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "appId", - "appSecret" - ], - "additionalProperties": false - } - }, - "required": [ - "session" - ], - "additionalProperties": false - }, "QuickLink": { "type": "object", "properties": { @@ -14323,7 +13538,8 @@ }, "expiresAt": { "type": "string", - "description": "ISO-8601 expiry. QuickLinks live 15 minutes unless `expiresInSeconds` was set." + "nullable": true, + "description": "Null for new persistent QuickLinks. Legacy links retain their ISO-8601 expiry." } }, "required": [ @@ -14351,18 +13567,97 @@ "data" ] }, - "CreateQuickLinkRequest": { + "QuickLinkConfiguration": { "type": "object", "properties": { - "projectId": { + "testing": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "configuration": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "status": { + "type": "string", + "maxLength": 139 + } + }, + "additionalProperties": false + }, + "accountType": { + "type": "string", + "enum": [ + "personal", + "business" + ] + }, + "replyBehavior": { + "type": "string", + "enum": [ + "off", + "echo" + ] + }, + "failureScenario": { + "type": "string", + "enum": [ + "none", + "reject-send" + ] + }, + "historyFixtureId": { + "type": "string", + "format": "uuid" + } + }, + "additionalProperties": false + }, + "editable": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "profile.name", + "profile.status", + "accountType", + "replyBehavior", + "failureScenario", + "historyFixtureId" + ] + }, + "maxItems": 3 + } + }, + "additionalProperties": false + }, + "prefillPhone": { "type": "string", - "format": "uuid", - "description": "Project that receives the connected number. Required for organization keys with more than one project." + "pattern": "^\\+?\\d{6,15}$" }, - "customerId": { + "connectionPreference": { "type": "string", - "format": "uuid", - "description": "Customer that owns the connected number when Customers is enabled for the project." + "enum": [ + "cloud", + "linked", + "both" + ] + }, + "connectionEnforcement": { + "type": "string", + "enum": [ + "prefer", + "force" + ] }, "methods": { "type": "array", @@ -14375,7 +13670,163 @@ }, "minItems": 1, "maxItems": 2, - "description": "Connection methods offered on the hosted page. Defaults to the saved settings, then to both." + "description": "Linked Devices methods offered for this connection." + }, + "defaultMethod": { + "type": "string", + "nullable": true, + "enum": [ + "qr", + "pairing", + null + ], + "description": "Linked Devices method opened first. Omit to inherit the saved project setting, or set null to choose by recipient device." + }, + "allowPhoneChange": { + "type": "boolean" + }, + "historySync": { + "type": "object", + "properties": { + "consent": { + "type": "string", + "enum": [ + "ask", + "force_on", + "force_off" + ] + }, + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "hms": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + false + ] + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + true + ] + }, + "region": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" + }, + "policy": { + "type": "string", + "enum": [ + "short", + "standard", + "extended", + "compliance", + "enterprise_archive" + ] + }, + "retentionDays": { + "type": "integer", + "minimum": 1, + "maximum": 2555 + }, + "policyVersion": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "legalHold": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "region", + "policy" + ], + "additionalProperties": false + } + ] + }, + "observation": { + "type": "object", + "properties": { + "presenceMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "typingMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "labelMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache", + "project" + ] + }, + "quickReplyMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "description": "Connection, pairing, phone, and history overrides. Omitted fields inherit saved defaults. Presentation, legal links, and callbacks belong to saved project settings." + }, + "CreateQuickLinkRequest": { + "type": "object", + "properties": { + "projectId": { + "type": "string", + "format": "uuid", + "description": "Project that receives the connected number. Required for organization keys with more than one project." + }, + "customerId": { + "type": "string", + "format": "uuid", + "description": "Customer that owns the connected number when Customers is enabled for the project." }, "externalId": { "type": "string", @@ -14384,16 +13835,8 @@ "pattern": "^[^\\x00-\\x1f\\x7f]+$", "description": "Integrator correlation copied to the resulting session. May repeat across invitations; does not grant access." }, - "prefillPhone": { - "type": "string", - "pattern": "^\\+?\\d{6,15}$", - "description": "Phone to prefill in international format. Creation checks WhatsApp registration; confirmed nonregistration is rejected, and lookup unavailability is retryable." - }, - "expiresInSeconds": { - "type": "integer", - "minimum": 300, - "maximum": 3600, - "description": "Link lifetime in seconds, 300–3600. Defaults to 900." + "configuration": { + "$ref": "#/components/schemas/QuickLinkConfiguration" } }, "additionalProperties": false @@ -14419,7 +13862,8 @@ "type": "string" }, "expiresAt": { - "type": "string" + "type": "string", + "nullable": true }, "openedAt": { "type": "string", @@ -14543,7 +13987,9 @@ "type": "string" }, "registered": { - "type": "boolean" + "type": "boolean", + "nullable": true, + "description": "Whether WhatsApp registration was confirmed. Null means the result is unknown." }, "pushName": { "type": "string", @@ -14583,9 +14029,158 @@ "error" ] }, + "EmbeddedSignupResult": { + "type": "object", + "properties": { + "code": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "wabaId": { + "type": "string", + "pattern": "^\\d+$" + }, + "phoneNumberId": { + "type": "string", + "pattern": "^\\d+$" + }, + "coexistence": { + "type": "boolean", + "default": true + }, + "historySync": { + "type": "boolean", + "default": false + } + }, + "required": [ + "code", + "wabaId", + "phoneNumberId" + ], + "additionalProperties": false + }, "QuickLinkState": { "type": "object", "properties": { + "testing": { + "type": "object", + "nullable": true, + "properties": { + "country": { + "type": "string" + }, + "configuration": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "status": { + "type": "string", + "maxLength": 139 + } + }, + "additionalProperties": false + }, + "accountType": { + "type": "string", + "enum": [ + "personal", + "business" + ] + }, + "replyBehavior": { + "type": "string", + "enum": [ + "off", + "echo" + ] + }, + "failureScenario": { + "type": "string", + "enum": [ + "none", + "reject-send" + ] + }, + "historyFixtureId": { + "type": "string", + "format": "uuid" + } + }, + "additionalProperties": false + }, + "editable": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "profile.name", + "profile.status", + "accountType", + "replyBehavior", + "failureScenario", + "historyFixtureId" + ] + } + }, + "state": { + "type": "string", + "enum": [ + "pending", + "starting", + "ready" + ] + }, + "phone": { + "type": "string" + } + }, + "required": [ + "country", + "configuration", + "editable", + "state", + "phone" + ] + }, + "historyConfiguration": { + "type": "object", + "nullable": true, + "properties": { + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "required": [ + "mode", + "requestFull" + ] + }, + "historyConsent": { + "type": "string", + "nullable": true, + "enum": [ + "pending", + "accepted", + "declined", + null + ] + }, "id": { "type": "string" }, @@ -14601,7 +14196,8 @@ ] }, "expiresAt": { - "type": "string" + "type": "string", + "nullable": true }, "redirectUrl": { "type": "string", @@ -14819,7 +14415,9 @@ "nullable": true, "properties": { "registered": { - "type": "boolean" + "type": "boolean", + "nullable": true, + "description": "Whether WhatsApp registration was confirmed. Null means the result is unknown." }, "phone": { "type": "string" @@ -14889,6 +14487,9 @@ } }, "required": [ + "testing", + "historyConfiguration", + "historyConsent", "id", "status", "expiresAt", @@ -14943,6 +14544,26 @@ "type": "boolean" } } + }, + "EmbeddedSignupRequest": { + "type": "object", + "properties": { + "quicklinkId": { + "type": "string", + "pattern": "^ql_[a-zA-Z0-9]+$" + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "result": { + "$ref": "#/components/schemas/EmbeddedSignupResult" + } + }, + "required": [ + "quicklinkId" + ], + "additionalProperties": false } }, "securitySchemes": { @@ -15165,190 +14786,144 @@ "description": "**Required scope:** `bridge:connect`" } }, - "/messaging/client-tokens": { + "/messaging/features/evaluate": { "post": { - "operationId": "mintClientToken", + "operationId": "evaluateFeatures", "tags": [ - "Client Tokens" + "Features" ], - "summary": "Mint a short-lived browser-safe client token", + "summary": "Evaluate feature availability", + "description": "Returns feature decisions for the authenticated organization or project. Client credentials cannot access this operation. Machine requests do not record experiment exposure.", "security": [ { "BearerAuth": [] } ], - "x-required-scopes": [ - "sessions:manage", - "messages:write", - "contacts:read", - "presence:read", - "presence:observe", - "mcp" - ], - "x-required-scope-mode": "all", "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MintClientTokenRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Token minted", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MintClientTokenResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null + "type": "object", + "properties": { + "surface": { + "type": "string", + "maxLength": 100, + "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$" }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null + "keys": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$" + }, + "maxItems": 50 }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" + "measurementAllowed": { + "type": "boolean", + "default": false + } }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } + "required": [ + "surface", + "keys" + ], + "additionalProperties": false } } } }, - "x-mint": { - "content": "sessions:manage messages:write contacts:read presence:read presence:observe mcp" - }, - "description": "**Required scopes (all):** `sessions:manage` · `messages:write` · `contacts:read` · `presence:read` · `presence:observe` · `mcp`" - } - }, - "/messaging/sessions/{session}/client-rules": { - "get": { - "operationId": "getClientRules", - "tags": [ - "Client Tokens" - ], - "summary": "Get client-token rules for a session", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], "responses": { "200": { - "description": "Client rules", + "description": "Feature decisions", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetClientRulesResponse" + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "decision": { + "type": "object", + "properties": { + "key": { + "type": "string", + "maxLength": 100, + "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$" + }, + "version": { + "type": "integer" + }, + "available": { + "type": "boolean" + }, + "enrolled": { + "type": "boolean" + }, + "variant": { + "type": "string", + "nullable": true + }, + "experimentAssigned": { + "type": "boolean" + } + }, + "required": [ + "key", + "version", + "available", + "enrolled", + "variant", + "experimentAssigned" + ] + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsRevision": { + "type": "string", + "nullable": true + }, + "withdrawal": { + "type": "string", + "nullable": true + }, + "exposureToken": {} + }, + "required": [ + "decision", + "title", + "description", + "termsRevision", + "withdrawal", + "exposureToken" + ] + } + } + }, + "required": [ + "success", + "data" + ] } } } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Invalid feature request", "content": { "application/json": { "schema": { @@ -15368,7 +14943,7 @@ } }, "401": { - "description": "Missing or invalid authentication token", + "description": "Authentication required", "content": { "application/json": { "schema": { @@ -15388,7 +14963,7 @@ } }, "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", + "description": "Credential cannot access feature evaluation", "content": { "application/json": { "schema": { @@ -15407,546 +14982,8 @@ } } }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - }, - "put": { - "operationId": "setClientRules", - "tags": [ - "Client Tokens" - ], - "summary": "Set client-token rules for a session", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scopes": [ - "sessions:manage", - "messages:write", - "contacts:read", - "presence:read", - "presence:observe", - "mcp" - ], - "x-required-scope-mode": "all", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SetClientRulesRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Rules saved", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage messages:write contacts:read presence:read presence:observe mcp" - }, - "description": "**Required scopes (all):** `sessions:manage` · `messages:write` · `contacts:read` · `presence:read` · `presence:observe` · `mcp`" - }, - "delete": { - "operationId": "deleteClientRules", - "tags": [ - "Client Tokens" - ], - "summary": "Delete client-token rules for a session", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "responses": { - "200": { - "description": "Rules deleted", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/features/evaluate": { - "post": { - "operationId": "evaluateFeatures", - "tags": [ - "Features" - ], - "summary": "Evaluate feature availability", - "description": "Returns feature decisions for the authenticated organization or project. Client credentials cannot access this operation. Machine requests do not record experiment exposure.", - "security": [ - { - "BearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "surface": { - "type": "string", - "maxLength": 100, - "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$" - }, - "keys": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$" - }, - "maxItems": 50 - }, - "measurementAllowed": { - "type": "boolean", - "default": false - } - }, - "required": [ - "surface", - "keys" - ], - "additionalProperties": false - } - } - } - }, - "responses": { - "200": { - "description": "Feature decisions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "decision": { - "type": "object", - "properties": { - "key": { - "type": "string", - "maxLength": 100, - "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$" - }, - "version": { - "type": "integer" - }, - "available": { - "type": "boolean" - }, - "enrolled": { - "type": "boolean" - }, - "variant": { - "type": "string", - "nullable": true - }, - "experimentAssigned": { - "type": "boolean" - } - }, - "required": [ - "key", - "version", - "available", - "enrolled", - "variant", - "experimentAssigned" - ] - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "termsRevision": { - "type": "string", - "nullable": true - }, - "withdrawal": { - "type": "string", - "nullable": true - }, - "exposureToken": {} - }, - "required": [ - "decision", - "title", - "description", - "termsRevision", - "withdrawal", - "exposureToken" - ] - } - } - }, - "required": [ - "success", - "data" - ] - } - } - } - }, - "400": { - "description": "Invalid feature request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Authentication required", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Credential cannot access feature evaluation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "413": { - "description": "Request exceeds 16 KiB", + "413": { + "description": "Request exceeds 16 KiB", "content": { "application/json": { "schema": { @@ -15968,2063 +15005,6 @@ } } }, - "/messaging/sessions": { - "get": { - "operationId": "listSessions", - "tags": [ - "Sessions" - ], - "summary": "List all sessions for the tenant", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:read", - "responses": { - "200": { - "description": "Session list", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListSessionsResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:read" - }, - "description": "**Required scope:** `sessions:read`" - } - }, - "/messaging/sessions/{projectId}/": { - "post": { - "operationId": "createSession", - "tags": [ - "Sessions" - ], - "summary": "Create a new session", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1, - "description": "Project that owns the session." - }, - "required": true, - "name": "projectId", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateSessionRequest" - } - } - } - }, - "responses": { - "201": { - "description": "Session created; a testing session is connected and command-ready before this response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateSessionResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "409": { - "description": "Session with this name already exists", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "conflict_error", - "code": "state_conflict", - "message": "The request conflicts with the resource state.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#state-conflict" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/sessions/{session}": { - "get": { - "operationId": "getSession", - "tags": [ - "Sessions" - ], - "summary": "Get session details", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:read", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "responses": { - "200": { - "description": "Session details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetSessionResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:read" - }, - "description": "**Required scope:** `sessions:read`" - }, - "put": { - "operationId": "updateSession", - "tags": [ - "Sessions" - ], - "summary": "Update session configuration", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateSessionRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Session updated", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateSessionResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - }, - "503": { - "description": "Session configuration was stored but the connected runner did not acknowledge it", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "service_unavailable", - "message": "A required service is unavailable. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - }, - "delete": { - "operationId": "deleteSession", - "tags": [ - "Sessions" - ], - "summary": "Delete a session", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "responses": { - "200": { - "description": "Testing session deleted immediately after its simulated runtime closes", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OperationAccepted" - } - } - } - }, - "202": { - "description": "Session deletion scheduled", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OperationAccepted" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/sessions/{session}/start": { - "post": { - "operationId": "startSession", - "tags": [ - "Sessions" - ], - "summary": "Start a session (connect to WhatsApp)", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "responses": { - "202": { - "description": "Testing-session state changed immediately, or production-session start scheduled", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OperationAccepted" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/sessions/{session}/stop": { - "post": { - "operationId": "stopSession", - "tags": [ - "Sessions" - ], - "summary": "Stop a session", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "responses": { - "202": { - "description": "Testing session stopped after backend teardown, or production-session stop scheduled", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OperationAccepted" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/sessions/{session}/restart": { - "post": { - "operationId": "restartSession", - "tags": [ - "Sessions" - ], - "summary": "Restart a session", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "responses": { - "202": { - "description": "Testing session reconnected and ready, or production-session restart scheduled", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OperationAccepted" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/sessions/{session}/logout": { - "post": { - "operationId": "logoutSession", - "tags": [ - "Sessions" - ], - "summary": "Log the session out of WhatsApp", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "responses": { - "202": { - "description": "Session logout scheduled", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OperationAccepted" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/sessions/{session}/me": { - "get": { - "operationId": "getSessionAccount", - "tags": [ - "Sessions" - ], - "summary": "Get the connected WhatsApp account info", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:read", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "responses": { - "200": { - "description": "Account info", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetSessionAccountResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:read" - }, - "description": "**Required scope:** `sessions:read`" - } - }, - "/messaging/sessions/{session}/handoff-keys": { - "post": { - "operationId": "sessionHandoffKeys", - "tags": [ - "Sessions" - ], - "summary": "Initiate M↔P toggle: fetch the destination Curve25519 pubkey", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "responses": { - "200": { - "description": "Destination pubkey + correlation id for the handoff", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HandoffKeysSuccess" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/sessions/{session}/handoff-import": { - "post": { - "operationId": "sessionHandoffImport", - "tags": [ - "Sessions" - ], - "summary": "Ship a sealed SignalStoreSnapshot from customer Baileys to the runner", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HandoffImportRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Import accepted; bartenderMode will flip after the runner acks", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HandoffImportSuccess" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/sessions/{session}/upload-appstate-keys": { - "post": { - "operationId": "sessionUploadAppStateKeys", - "tags": [ - "Sessions" - ], - "summary": "Upload selected AppStateSyncKeys (Track P+ tier)", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UploadAppStateKeysRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Keys persisted; mode may have been flipped per upgradeTo", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UploadAppStateKeysSuccess" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, - "/messaging/sessions/{session}/handoff-ack": { - "post": { - "operationId": "sessionHandoffAck", - "tags": [ - "Sessions" - ], - "summary": "Acknowledge receipt of an M→P export; API flips bartenderMode after hash verify", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "session", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HandoffAckRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Ack accepted; bartenderMode flipped from magic → passthrough", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HandoffAckSuccess" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, "/messaging/{session}/pair/qr": { "get": { "operationId": "getQRCode", @@ -18409,16 +15389,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } @@ -18429,16 +15399,16 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } + }, + "402": { + "description": "Paid service is restricted; review billing and entitlements", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" } } } @@ -18449,16 +15419,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" } } } @@ -18469,16 +15429,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" } } } @@ -18489,16 +15439,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } @@ -18509,16 +15449,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" } } } @@ -18529,16 +15459,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "upstream_error", - "code": "upstream_failure", - "message": "The messaging provider could not complete the request.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" } } } @@ -18549,16 +15469,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "service_unavailable", - "message": "A required service is unavailable. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" } } } @@ -18569,16 +15479,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "result_unknown", - "message": "The result was not obtained before the deadline. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#result-unknown" } } } @@ -18653,16 +15553,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } @@ -18673,16 +15563,16 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } + }, + "402": { + "description": "Paid service is restricted; review billing and entitlements", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" } } } @@ -18693,16 +15583,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" } } } @@ -18713,16 +15593,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" } } } @@ -18733,16 +15603,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" } } } @@ -18753,16 +15613,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "upstream_error", - "code": "upstream_failure", - "message": "The messaging provider could not complete the request.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" } } } @@ -18773,16 +15623,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "service_unavailable", - "message": "A required service is unavailable. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" } } } @@ -18793,16 +15633,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "result_unknown", - "message": "The result was not obtained before the deadline. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#result-unknown" } } } @@ -18878,16 +15708,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } @@ -18898,16 +15718,16 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } + }, + "402": { + "description": "Paid service is restricted; review billing and entitlements", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" } } } @@ -18918,16 +15738,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" } } } @@ -18938,16 +15748,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" } } } @@ -18958,16 +15758,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" } } } @@ -18978,16 +15768,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "upstream_error", - "code": "upstream_failure", - "message": "The messaging provider could not complete the request.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" } } } @@ -18998,16 +15778,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "service_unavailable", - "message": "A required service is unavailable. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" } } } @@ -19018,16 +15788,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "result_unknown", - "message": "The result was not obtained before the deadline. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#result-unknown" } } } @@ -19101,16 +15861,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } @@ -19121,16 +15871,16 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } + }, + "402": { + "description": "Paid service is restricted; review billing and entitlements", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" } } } @@ -19141,16 +15891,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" } } } @@ -19161,16 +15901,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" } } } @@ -19181,16 +15911,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" } } } @@ -19201,16 +15921,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "upstream_error", - "code": "upstream_failure", - "message": "The messaging provider could not complete the request.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" } } } @@ -19221,16 +15931,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "service_unavailable", - "message": "A required service is unavailable. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" } } } @@ -19241,16 +15941,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "result_unknown", - "message": "The result was not obtained before the deadline. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#result-unknown" } } } @@ -19326,16 +16016,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } @@ -19346,16 +16026,16 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } + }, + "402": { + "description": "Paid service is restricted; review billing and entitlements", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" } } } @@ -19366,16 +16046,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" } } } @@ -19386,16 +16056,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" } } } @@ -19406,16 +16066,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" } } } @@ -19426,16 +16076,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "upstream_error", - "code": "upstream_failure", - "message": "The messaging provider could not complete the request.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" } } } @@ -19446,16 +16086,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "service_unavailable", - "message": "A required service is unavailable. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" } } } @@ -19466,16 +16096,6 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "result_unknown", - "message": "The result was not obtained before the deadline. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#result-unknown" } } } @@ -36673,320 +33293,8 @@ } } }, - "503": { - "description": "Stored, but a connected number did not confirm the plan; retry the request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "service_unavailable", - "message": "A required service is unavailable. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - } - } - }, - "/messaging/projects/{projectId}/health-policy": { - "get": { - "operationId": "getProjectHealthPolicy", - "tags": [ - "BanSafe" - ], - "summary": "Get a project's health policy", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:read", - "parameters": [ - { - "schema": { - "type": "string", - "format": "uuid" - }, - "required": true, - "name": "projectId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "Project health threshold and configured actions", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetProjectHealthPolicyResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:read" - }, - "description": "**Required scope:** `sessions:read`" - }, - "put": { - "operationId": "updateProjectHealthPolicy", - "tags": [ - "BanSafe" - ], - "summary": "Set a project's health policy", - "description": "**Required scope:** `sessions:manage`\n\nReplaces the project policy when version matches the stored version. The rule triggers only when an available health estimate is below threshold. Stopping disconnects a session while retaining its linked credentials; logging out requires relinking.", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "parameters": [ - { - "schema": { - "type": "string", - "format": "uuid" - }, - "required": true, - "name": "projectId", - "in": "path" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateProjectHealthPolicyInput" - } - } - } - }, - "responses": { - "200": { - "description": "Project health policy updated", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateProjectHealthPolicyResponse" - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "409": { - "description": "Version conflict or selected notification integration unavailable", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "conflict_error", - "code": "state_conflict", - "message": "The request conflicts with the resource state.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#state-conflict" - } - } - } - }, - "500": { - "description": "Internal server error", + "503": { + "description": "Stored, but a connected number did not confirm the plan; retry the request", "content": { "application/json": { "schema": { @@ -36995,12 +33303,12 @@ "example": { "error": { "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", + "code": "service_unavailable", + "message": "A required service is unavailable. Check completion before repeating a write.", "param": null }, "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" + "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" } } } @@ -37011,14 +33319,13 @@ } } }, - "/messaging/projects/{projectId}/insurance-evidence": { + "/messaging/projects/{projectId}/health-policy": { "get": { - "operationId": "getProjectInsuranceEvidence", + "operationId": "getProjectHealthPolicy", "tags": [ - "Ban insurance" + "BanSafe" ], - "summary": "Get ban insurance evidence setting", - "description": "**Required scope:** `sessions:read`\n\nBan insurance evidence lets Polymorfa measure what the other devices linked to a number do with it: how many messages they send, to how many people, how fast, and in which hours. Only counts are kept, never messages, contacts, or device names. With it off, a claim can still be filed, but Polymorfa cannot say whether another device caused the ban and a person decides the claim.", + "summary": "Get a project's health policy", "security": [ { "BearerAuth": [] @@ -37038,11 +33345,11 @@ ], "responses": { "200": { - "description": "The project's ban insurance evidence setting", + "description": "Project health threshold and configured actions", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetProjectInsuranceEvidenceResponse" + "$ref": "#/components/schemas/GetProjectHealthPolicyResponse" } } } @@ -37150,15 +33457,16 @@ }, "x-mint": { "content": "sessions:read" - } + }, + "description": "**Required scope:** `sessions:read`" }, "put": { - "operationId": "updateProjectInsuranceEvidence", + "operationId": "updateProjectHealthPolicy", "tags": [ - "Ban insurance" + "BanSafe" ], - "summary": "Set ban insurance evidence setting", - "description": "**Required scope:** `sessions:manage`\n\nBan insurance evidence lets Polymorfa measure what the other devices linked to a number do with it: how many messages they send, to how many people, how fast, and in which hours. Only counts are kept, never messages, contacts, or device names. With it off, a claim can still be filed, but Polymorfa cannot say whether another device caused the ban and a person decides the claim.", + "summary": "Set a project's health policy", + "description": "**Required scope:** `sessions:manage`\n\nReplaces the project policy when version matches the stored version. The rule triggers only when an available health estimate is below threshold. Stopping disconnects a session while retaining its linked credentials; logging out requires relinking.", "security": [ { "BearerAuth": [] @@ -37177,30 +33485,22 @@ } ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - }, - "required": [ - "enabled" - ], - "additionalProperties": false + "$ref": "#/components/schemas/UpdateProjectHealthPolicyInput" } } } }, "responses": { "200": { - "description": "The project's ban insurance evidence setting was updated", + "description": "Project health policy updated", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateProjectInsuranceEvidenceResponse" + "$ref": "#/components/schemas/UpdateProjectHealthPolicyResponse" } } } @@ -37285,8 +33585,8 @@ } } }, - "500": { - "description": "Internal server error", + "409": { + "description": "Version conflict or selected notification integration unavailable", "content": { "application/json": { "schema": { @@ -37294,19 +33594,19 @@ }, "example": { "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", + "type": "conflict_error", + "code": "state_conflict", + "message": "The request conflicts with the resource state.", "param": null }, "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" + "docs": "https://docs.polymorfa.com/api/errors#state-conflict" } } } }, - "503": { - "description": "Stored, but a connected number did not confirm the change; retry the request", + "500": { + "description": "Internal server error", "content": { "application/json": { "schema": { @@ -37315,12 +33615,12 @@ "example": { "error": { "type": "api_error", - "code": "service_unavailable", - "message": "A required service is unavailable. Check completion before repeating a write.", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", "param": null }, "data": null, - "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" + "docs": "https://docs.polymorfa.com/api/errors#internal-error" } } } @@ -37331,20 +33631,20 @@ } } }, - "/messaging/projects/{projectId}/observation-policy": { + "/messaging/projects/{projectId}/insurance-evidence": { "get": { - "operationId": "getProjectObservationPolicy", + "operationId": "getProjectInsuranceEvidence", "tags": [ - "Presence", - "Labels", - "Business App" + "Ban insurance" ], + "summary": "Get ban insurance evidence setting", + "description": "**Required scope:** `sessions:read`\n\nBan insurance evidence lets Polymorfa measure what the other devices linked to a number do with it: how many messages they send, to how many people, how fast, and in which hours. Only counts are kept, never messages, contacts, or device names. With it off, a claim can still be filed, but Polymorfa cannot say whether another device caused the ban and a person decides the claim.", "security": [ { "BearerAuth": [] } ], - "x-required-scope": "presence:read", + "x-required-scope": "sessions:read", "parameters": [ { "schema": { @@ -37358,11 +33658,11 @@ ], "responses": { "200": { - "description": "Project observation ceiling", + "description": "The project's ban insurance evidence setting", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetProjectObservationPolicyResponse" + "$ref": "#/components/schemas/GetProjectInsuranceEvidenceResponse" } } } @@ -37469,23 +33769,22 @@ } }, "x-mint": { - "content": "presence:read" - }, - "description": "**Required scope:** `presence:read`" + "content": "sessions:read" + } }, "put": { - "operationId": "updateProjectObservationPolicy", + "operationId": "updateProjectInsuranceEvidence", "tags": [ - "Presence", - "Labels", - "Business App" + "Ban insurance" ], + "summary": "Set ban insurance evidence setting", + "description": "**Required scope:** `sessions:manage`\n\nBan insurance evidence lets Polymorfa measure what the other devices linked to a number do with it: how many messages they send, to how many people, how fast, and in which hours. Only counts are kept, never messages, contacts, or device names. With it off, a claim can still be filed, but Polymorfa cannot say whether another device caused the ban and a person decides the claim.", "security": [ { "BearerAuth": [] } ], - "x-required-scope": "presence:observe", + "x-required-scope": "sessions:manage", "parameters": [ { "schema": { @@ -37503,43 +33802,12 @@ "schema": { "type": "object", "properties": { - "presenceMode": { - "type": "string", - "enum": [ - "off", - "events", - "cache" - ] - }, - "typingMode": { - "type": "string", - "enum": [ - "off", - "events", - "cache" - ] - }, - "labelMode": { - "type": "string", - "enum": [ - "off", - "events", - "cache", - "project" - ] - }, - "quickReplyMode": { - "type": "string", - "enum": [ - "off", - "events", - "cache" - ] + "enabled": { + "type": "boolean" } }, "required": [ - "presenceMode", - "typingMode" + "enabled" ], "additionalProperties": false } @@ -37548,11 +33816,11 @@ }, "responses": { "200": { - "description": "Project observation ceiling updated", + "description": "The project's ban insurance evidence setting was updated", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateProjectObservationPolicyResponse" + "$ref": "#/components/schemas/UpdateProjectInsuranceEvidenceResponse" } } } @@ -37637,26 +33905,6 @@ } } }, - "409": { - "description": "Label projection cleanup is active or failed; resolve failed cleanup before re-enabling project retention", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "conflict_error", - "code": "state_conflict", - "message": "The request conflicts with the resource state.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#state-conflict" - } - } - } - }, "500": { "description": "Internal server error", "content": { @@ -37678,7 +33926,7 @@ } }, "503": { - "description": "Policy was stored but a connected runner did not acknowledge it; retry the request", + "description": "Stored, but a connected number did not confirm the change; retry the request", "content": { "application/json": { "schema": { @@ -37699,14 +33947,13 @@ } }, "x-mint": { - "content": "presence:observe" - }, - "description": "**Required scope:** `presence:observe`" + "content": "sessions:manage" + } } }, - "/messaging/{session}/observation-policy": { + "/messaging/projects/{projectId}/observation-policy": { "get": { - "operationId": "getSessionObservationPolicy", + "operationId": "getProjectObservationPolicy", "tags": [ "Presence", "Labels", @@ -37722,20 +33969,20 @@ { "schema": { "type": "string", - "minLength": 1 + "format": "uuid" }, "required": true, - "name": "session", + "name": "projectId", "in": "path" } ], "responses": { "200": { - "description": "Effective session observation policy", + "description": "Project observation ceiling", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetSessionObservationPolicyResponse" + "$ref": "#/components/schemas/GetProjectObservationPolicyResponse" } } } @@ -37842,15 +34089,14 @@ } }, "x-mint": { - "content": "Linked Device API presence:read" + "content": "presence:read" }, - "x-protocols": [ - "Linked Device API" - ], - "description": "**Protocols:** Linked Device API\n\n**Required scope:** `presence:read`" - }, - "put": { - "operationId": "updateSessionObservationPolicy", + "description": "**Required scope:** `presence:read`" + } + }, + "/messaging/{session}/observation-policy": { + "get": { + "operationId": "getSessionObservationPolicy", "tags": [ "Presence", "Labels", @@ -37861,7 +34107,7 @@ "BearerAuth": [] } ], - "x-required-scope": "presence:observe", + "x-required-scope": "presence:read", "parameters": [ { "schema": { @@ -37873,66 +34119,13 @@ "in": "path" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "presenceMode": { - "type": "string", - "enum": [ - "inherit", - "off", - "events", - "cache" - ] - }, - "typingMode": { - "type": "string", - "enum": [ - "inherit", - "off", - "events", - "cache" - ] - }, - "labelMode": { - "type": "string", - "enum": [ - "inherit", - "off", - "events", - "cache", - "project" - ] - }, - "quickReplyMode": { - "type": "string", - "enum": [ - "inherit", - "off", - "events", - "cache" - ] - } - }, - "required": [ - "presenceMode", - "typingMode" - ], - "additionalProperties": false - } - } - } - }, "responses": { "200": { - "description": "Session observation override updated", + "description": "Effective session observation policy", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateSessionObservationPolicyResponse" + "$ref": "#/components/schemas/GetSessionObservationPolicyResponse" } } } @@ -38017,26 +34210,6 @@ } } }, - "409": { - "description": "Label projection cleanup is active or failed; resolve failed cleanup before re-enabling project retention", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "conflict_error", - "code": "state_conflict", - "message": "The request conflicts with the resource state.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#state-conflict" - } - } - } - }, "500": { "description": "Internal server error", "content": { @@ -38056,35 +34229,15 @@ } } } - }, - "503": { - "description": "Policy was stored but a connected runner did not acknowledge it; retry the request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "service_unavailable", - "message": "A required service is unavailable. Check completion before repeating a write.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" - } - } - } } }, "x-mint": { - "content": "Linked Device API presence:observe" + "content": "Linked Device API presence:read" }, "x-protocols": [ "Linked Device API" ], - "description": "**Protocols:** Linked Device API\n\n**Required scope:** `presence:observe`" + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `presence:read`" } }, "/messaging/{session}/profile": { @@ -43975,170 +40128,6 @@ "description": "**Required scope:** `campaigns:manage`" } }, - "/messaging/cloud-api/embedded-signup": { - "post": { - "operationId": "embeddedSignup", - "tags": [ - "Cloud API" - ], - "summary": "Advance Cloud onboarding for a reserved session", - "security": [ - { - "BearerAuth": [] - } - ], - "x-required-scope": "sessions:manage", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmbeddedSignupRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Onboarding progress", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "type": "object", - "properties": { - "stage": { - "type": "string" - } - }, - "required": [ - "stage" - ] - } - }, - "required": [ - "success", - "data" - ] - } - } - } - }, - "400": { - "description": "Bad request — request body or params failed validation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "invalid_parameter", - "message": "Correct the invalid request field.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, - "401": { - "description": "Missing or invalid authentication token", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "authentication_error", - "code": "invalid_credential", - "message": "Supply a valid credential.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" - } - } - } - }, - "403": { - "description": "Authenticated but not permitted (missing scope or rule violation)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "permission_error", - "code": "permission_denied", - "message": "The credential does not have access to this resource.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#permission-denied" - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "invalid_request_error", - "code": "resource_not_found", - "message": "The resource is unavailable to this caller.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicError" - }, - "example": { - "error": { - "type": "api_error", - "code": "internal_error", - "message": "An unexpected service failure occurred. Keep the request ID for support.", - "param": null - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#internal-error" - } - } - } - } - }, - "x-mint": { - "content": "sessions:manage" - }, - "description": "**Required scope:** `sessions:manage`" - } - }, "/messaging/quicklinks": { "post": { "operationId": "createQuickLink", @@ -44146,7 +40135,7 @@ "QuickLink" ], "summary": "Create a QuickLink", - "description": "**Required scope:** `quicklink:manage`\n\nCreates a 15-minute hosted link that connects one WhatsApp number to a new session in the project.", + "description": "**Required scope:** `quicklink:manage`\n\nCreates a persistent hosted link that connects one WhatsApp number to a new session in the project.", "security": [ { "BearerAuth": [] @@ -45160,6 +41149,441 @@ } } }, + "/quicklink/{token}/history": { + "post": { + "operationId": "chooseQuickLinkHistory", + "tags": [ + "QuickLink" + ], + "security": [], + "summary": "Choose history sharing before connecting", + "parameters": [ + { + "schema": { + "type": "string", + "description": "Opaque QuickLink token from the hosted URL." + }, + "required": true, + "name": "token", + "in": "path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "historySync": { + "type": "boolean" + } + }, + "required": [ + "historySync" + ], + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "History choice recorded", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/QuickLinkState" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "403": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "404": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "409": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "422": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "429": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "500": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "503": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + } + } + } + }, + "/quicklink/{token}/testing": { + "patch": { + "operationId": "customizeQuickLinkTesting", + "tags": [ + "QuickLink" + ], + "security": [], + "summary": "Customize a simulated QuickLink account", + "parameters": [ + { + "schema": { + "type": "string", + "description": "Opaque QuickLink token from the hosted URL." + }, + "required": true, + "name": "token", + "in": "path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "status": { + "type": "string", + "maxLength": 139 + } + }, + "additionalProperties": false + }, + "accountType": { + "type": "string", + "enum": [ + "personal", + "business" + ] + }, + "replyBehavior": { + "type": "string", + "enum": [ + "off", + "echo" + ] + }, + "failureScenario": { + "type": "string", + "enum": [ + "none", + "reject-send" + ] + }, + "historyFixtureId": { + "type": "string", + "format": "uuid" + } + }, + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "Test configuration updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/QuickLinkState" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "403": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "404": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "409": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "422": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "429": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "500": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "503": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + } + } + } + }, + "/quicklink/{token}/testing/start": { + "post": { + "operationId": "startQuickLinkTesting", + "tags": [ + "QuickLink" + ], + "security": [], + "summary": "Start the simulated account reserved by a QuickLink", + "parameters": [ + { + "schema": { + "type": "string", + "description": "Opaque QuickLink token from the hosted URL." + }, + "required": true, + "name": "token", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Current test connection state", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/QuickLinkState" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "403": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "404": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "409": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "422": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "429": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "500": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, + "503": { + "description": "QuickLink request failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + } + } + } + }, "/quicklink/{token}": { "get": { "operationId": "getQuickLinkState", @@ -45466,6 +41890,16 @@ } } }, + "402": { + "description": "The team cannot fund pairing", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicQuickLinkError" + } + } + } + }, "403": { "description": "QuickLink request failed", "content": { @@ -45831,6 +42265,374 @@ } } } + }, + "/messaging/cloud-api/embedded-signup": { + "post": { + "operationId": "embeddedSignup", + "tags": [ + "Cloud API" + ], + "summary": "Advance Meta Cloud API setup for an issued QuickLink", + "security": [ + { + "BearerAuth": [] + } + ], + "x-required-scope": "quicklink:manage", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmbeddedSignupRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Onboarding progress", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "type": "object", + "properties": { + "stage": { + "type": "string" + } + }, + "required": [ + "stage" + ] + } + }, + "required": [ + "success", + "data" + ] + } + } + } + }, + "400": { + "description": "Bad request — request body or params failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } + }, + "401": { + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } + }, + "403": { + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "410": { + "description": "Direct session signup was retired; create a QuickLink first", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_gone", + "message": "The requested retained resource has expired or was removed.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-gone" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } + } + }, + "x-mint": { + "content": "quicklink:manage" + }, + "description": "**Required scope:** `quicklink:manage`" + } + }, + "/messaging/testing/{projectId}/history-fixtures": { + "post": { + "operationId": "createTestingHistoryFixture", + "tags": [ + "Testing" + ], + "security": [ + { + "BearerAuth": [] + } + ], + "x-required-scope": "sandbox:write", + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "name": "projectId", + "in": "path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "senderPhone": { + "type": "string", + "pattern": "^\\+[1-9][0-9]{1,14}$" + }, + "text": { + "type": "string", + "maxLength": 4096 + }, + "timestamp": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + }, + "fromMe": { + "type": "boolean" + } + }, + "required": [ + "id", + "senderPhone", + "text", + "timestamp", + "fromMe" + ], + "additionalProperties": false + }, + "maxItems": 100 + } + }, + "required": [ + "messages" + ], + "additionalProperties": false + } + } + } + }, + "responses": { + "201": { + "description": "Project-scoped history fixture", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "fixtureId": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "fixtureId" + ] + } + } + } + }, + "400": { + "description": "Invalid fixture", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } + }, + "403": { + "description": "Test access required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Project not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "502": { + "description": "Test runtime unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } + } + }, + "x-mint": { + "content": "sandbox:write" + }, + "description": "**Required scope:** `sandbox:write`" + } } }, "x-webhooks": { diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index e4de871b..2c5430e6 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -3,7 +3,7 @@ "info": { "title": "Polymorfa Console & Management API", "version": "1.0.0", - "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (138 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (70 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n- `/admin/…` — **staff-only** (54 endpoints): Polymorfa internal staff. EXCLUDED.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." + "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (151 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (74 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." }, "servers": [ { @@ -289,525 +289,933 @@ } } }, - "/platform/sessions": { - "get": { - "operationId": "listPlatformSessions", + "/platform/client-tokens": { + "post": { + "operationId": "mintClientToken", "tags": [ - "sessions" - ], - "summary": "List sessions", - "description": "Lists every session in the authenticated organization or selected project, including disconnected sessions and their latest status reason.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", - "x-merged-endpoints": [ - "listSessionsByOrganization" + "Client Tokens" ], + "summary": "Mint a short-lived browser-safe client token", "security": [ { "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" + "x-required-scopes": [ + "sessions:manage", + "messages:write", + "contacts:read", + "presence:read", + "presence:observe", + "mcp" + ], + "x-required-scope-mode": "all", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessMintClientTokenRequest" + } } } - ], + }, "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { + "description": "Token minted", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "$ref": "#/components/schemas/PlatformAccessMintClientTokenResponse" + } + } + } + }, + "400": { + "description": "Bad request — request body or params failed validation", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PlatformAccessPublicError" }, - "description": "API version applied to this response." + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } } - }, + } + }, + "401": { + "description": "Missing or invalid authentication token", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PlatformSession" - } - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" + "403": { + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, - "401": { - "$ref": "#/components/responses/Unauthorized" + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } }, - "403": { - "$ref": "#/components/responses/Forbidden" + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } } - } + }, + "x-mint": { + "content": "sessions:manage messages:write contacts:read presence:read presence:observe mcp" + }, + "description": "**Required scopes (all):** `sessions:manage` · `messages:write` · `contacts:read` · `presence:read` · `presence:observe` · `mcp`", + "x-audience": "public" } }, - "/platform/sessions/{sessionId}/start": { - "post": { - "operationId": "startPlatformSession", + "/platform/sessions/{session}/client-rules": { + "get": { + "operationId": "getClientRules", "tags": [ - "sessions" + "Client Tokens" ], - "summary": "Start a session", - "description": "Starts a stopped session or retries a session whose runtime failed. The identifier may be the session UUID or stable session slug.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "session", - "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "summary": "Get client-token rules for a session", "security": [ { "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] } ], + "x-required-scope": "sessions:manage", "parameters": [ { - "name": "sessionId", - "in": "path", - "required": true, - "description": "Database session UUID or stable session slug.", "schema": { - "$ref": "#/components/schemas/SessionIdentifier" - } + "type": "string" + }, + "required": true, + "name": "session", + "in": "path" } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SessionProjectContext" - } - } - } - }, "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "description": "Client rules", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "API version applied to this response." + "$ref": "#/components/schemas/PlatformAccessGetClientRulesResponse" + } } - }, + } + }, + "400": { + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/SessionStartResult" - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } }, "500": { - "$ref": "#/components/responses/InternalError" + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } } - } - } - }, - "/platform/sessions/{sessionId}/stop": { - "post": { - "operationId": "stopPlatformSession", + }, + "x-mint": { + "content": "sessions:manage" + }, + "description": "**Required scope:** `sessions:manage`", + "x-audience": "public" + }, + "put": { + "operationId": "setClientRules", "tags": [ - "sessions" + "Client Tokens" ], - "summary": "Stop a session", - "description": "Requests an asynchronous stop for one session. The identifier may be the session UUID or stable session slug.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "session", - "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "summary": "Set client-token rules for a session", "security": [ { "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] } ], + "x-required-scopes": [ + "sessions:manage", + "messages:write", + "contacts:read", + "presence:read", + "presence:observe", + "mcp" + ], + "x-required-scope-mode": "all", "parameters": [ { - "name": "sessionId", - "in": "path", - "required": true, - "description": "Database session UUID or stable session slug.", "schema": { - "$ref": "#/components/schemas/SessionIdentifier" - } + "type": "string" + }, + "required": true, + "name": "session", + "in": "path" } ], "requestBody": { - "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SessionProjectContext" + "$ref": "#/components/schemas/PlatformAccessSetClientRulesRequest" } } } }, "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, + "description": "Rules saved", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/SessionStopResult" - } + "$ref": "#/components/schemas/PlatformAccessSuccessResponse" + } + } + } + }, + "400": { + "description": "Bad request — request body or params failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } }, "500": { - "$ref": "#/components/responses/InternalError" + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } } - } - } - }, - "/platform/sessions/{sessionId}": { + }, + "x-mint": { + "content": "sessions:manage messages:write contacts:read presence:read presence:observe mcp" + }, + "description": "**Required scopes (all):** `sessions:manage` · `messages:write` · `contacts:read` · `presence:read` · `presence:observe` · `mcp`", + "x-audience": "public" + }, "delete": { - "operationId": "deletePlatformSession", + "operationId": "deleteClientRules", "tags": [ - "sessions" + "Client Tokens" ], - "summary": "Remove a session", - "description": "Permanently removes one session and terminates its active connection when needed.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "session", - "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "summary": "Delete client-token rules for a session", "security": [ { "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] } ], + "x-required-scope": "sessions:manage", "parameters": [ { - "name": "sessionId", - "in": "path", - "required": true, - "description": "Database session UUID or stable session slug.", "schema": { - "$ref": "#/components/schemas/SessionIdentifier" - } + "type": "string" + }, + "required": true, + "name": "session", + "in": "path" } ], "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "description": "Rules deleted", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "API version applied to this response." + "$ref": "#/components/schemas/PlatformAccessSuccessResponse" + } } - }, + } + }, + "400": { + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/SessionRemoveResult" - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } } - } - }, - "patch": { - "operationId": "setSessionTierOverride", + }, + "x-mint": { + "content": "sessions:manage" + }, + "description": "**Required scope:** `sessions:manage`", + "x-audience": "public" + } + }, + "/platform/sessions/{sessionId}": { + "get": { + "operationId": "getSession", "tags": [ - "sessions" + "Sessions" ], - "summary": "Set a session tier override", - "description": "Sets an explicit session tier or clears the override with null.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "session", - "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "summary": "Get session details", "security": [ { "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] } ], + "x-required-scope": "sessions:read", "parameters": [ { - "name": "sessionId", - "in": "path", - "required": true, - "description": "Database session UUID or stable session slug.", "schema": { - "$ref": "#/components/schemas/SessionIdentifier" - } + "type": "string" + }, + "required": true, + "name": "sessionId", + "in": "path" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SessionTierOverrideRequest" - } - } - } - }, "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "description": "Session details", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "API version applied to this response." + "$ref": "#/components/schemas/PlatformAccessGetSessionResponse" + } } - }, + } + }, + "400": { + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/ManagedSession" - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } } - } - } - }, - "/platform/sessions/stop": { - "post": { - "operationId": "stopSessions", + }, + "x-mint": { + "content": "sessions:read" + }, + "description": "**Required scope:** `sessions:read`", + "x-audience": "public" + }, + "put": { + "operationId": "updateSession", "tags": [ - "sessions" + "Sessions" ], - "summary": "Stop multiple sessions", - "description": "Requests asynchronous stops for up to 100 session UUIDs or slugs in the authenticated project scope.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "summary": "Update session configuration", "security": [ { "ApiKeyAuth": [] - }, + } + ], + "x-required-scope": "sessions:manage", + "parameters": [ { - "ConsoleSession": [] + "schema": { + "type": "string" + }, + "required": true, + "name": "sessionId", + "in": "path" } ], "requestBody": { - "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SessionBatchRequest" + "$ref": "#/components/schemas/PlatformAccessUpdateSessionRequest" } } } }, "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { + "description": "Desired configuration stored; application reports pending until the runtime acknowledges", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "$ref": "#/components/schemas/PlatformAccessUpdateSessionResponse" + } + } + } + }, + "400": { + "description": "Bad request — request body or params failed validation", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PlatformAccessPublicError" }, - "description": "API version applied to this response." + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } } - }, + } + }, + "401": { + "description": "Missing or invalid authentication token", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/SessionBatchStopResult" - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" + "403": { + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, - "401": { - "$ref": "#/components/responses/Unauthorized" + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } }, - "403": { - "$ref": "#/components/responses/Forbidden" + "409": { + "description": "Configuration revision changed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "conflict_error", + "code": "state_conflict", + "message": "The request conflicts with the resource state.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#state-conflict" + } + } + } }, "500": { - "$ref": "#/components/responses/InternalError" + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } + }, + "503": { + "description": "Session configuration persistence is unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "service_unavailable", + "message": "A required service is unavailable. Check completion before repeating a write.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#service-unavailable" + } + } + } } - } - } - }, - "/platform/sessions/delete": { - "post": { - "operationId": "deleteSessions", + }, + "x-mint": { + "content": "sessions:manage" + }, + "description": "**Required scope:** `sessions:manage`", + "x-audience": "public" + }, + "delete": { + "operationId": "deletePlatformSession", "tags": [ "sessions" ], - "summary": "Remove multiple sessions", - "description": "Permanently removes up to 100 session UUIDs or slugs and terminates any rows that were still active.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "Remove a session", + "description": "Permanently removes one session and terminates its active connection when needed.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "session", "x-excluded-from-public": false, "x-required-scope": "sessions:manage", "security": [ @@ -818,16 +1226,17 @@ "ConsoleSession": [] } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SessionBatchRequest" - } + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "Database session UUID or stable session slug.", + "schema": { + "$ref": "#/components/schemas/SessionIdentifier" } } - }, + ], "responses": { "200": { "description": "Success", @@ -852,7 +1261,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/SessionBatchRemoveResult" + "$ref": "#/components/schemas/SessionRemoveResult" } }, "required": [ @@ -862,28 +1271,26 @@ } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/sessions/testing": { - "post": { - "operationId": "createTestingSession", + }, + "patch": { + "operationId": "setSessionTierOverride", "tags": [ "sessions" ], - "summary": "Create a testing number", - "description": "Creates a disconnected simulated session with testMode enabled. Team API keys require projectId in the body.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "Confirm a number tier change", + "description": "Confirms a previously reviewed tier quote. An upgrade purchases a full fresh 24-hour window without refunding remaining time. A downgrade applies at the paid-window end. Poll the tier quote until applied or rejected; accepting the request does not grant the new tier.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "session", "x-excluded-from-public": false, "x-required-scope": "sessions:manage", "security": [ @@ -894,12 +1301,23 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "Database session UUID or stable session slug.", + "schema": { + "$ref": "#/components/schemas/SessionIdentifier" + } + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateTestingNumberRequest" + "$ref": "#/components/schemas/SessionTierOverrideRequest" } } } @@ -928,7 +1346,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/TestingSessionId" + "$ref": "#/components/schemas/NumberTierChange" } }, "required": [ @@ -950,568 +1368,1041 @@ "404": { "$ref": "#/components/responses/NotFound" }, - "500": { - "$ref": "#/components/responses/InternalError" + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/bans": { - "get": { - "operationId": "listSessionBans", + "/platform/sessions/{sessionId}/restart": { + "post": { + "operationId": "restartSession", "tags": [ - "bans" + "Sessions" ], - "summary": "List session bans", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "summary": "Restart a session", "security": [ { "ApiKeyAuth": [] - }, + } + ], + "x-required-scope": "sessions:manage", + "parameters": [ { - "ConsoleSession": [] + "schema": { + "type": "string" + }, + "required": true, + "name": "sessionId", + "in": "path" } ], "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "202": { + "description": "Testing session reconnected and ready, or production-session restart scheduled", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "API version applied to this response." + "$ref": "#/components/schemas/PlatformAccessOperationAccepted" + } } - }, + } + }, + "400": { + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/platform/bans/active": { - "get": { - "operationId": "listActiveSessionBans", - "tags": [ - "bans" - ], - "summary": "List active session bans", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", - "security": [ - { - "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "404": { + "description": "Resource not found", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PlatformAccessPublicError" }, - "description": "API version applied to this response." + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } } - }, + } + }, + "500": { + "description": "Internal server error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" } } } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" } - } + }, + "x-mint": { + "content": "sessions:manage" + }, + "description": "**Required scope:** `sessions:manage`", + "x-audience": "public" } }, - "/platform/bansafe/health": { - "get": { - "operationId": "listBanSafeHealth", + "/platform/sessions/{sessionId}/logout": { + "post": { + "operationId": "logoutSession", "tags": [ - "bansafe" + "Sessions" ], - "summary": "List number safety", - "description": "Lists present Health, findings counts, and the active Polymorfa restriction for every number in one project. Health is a 0 to 100 estimate from rules v1 or a validated learned model. It remains null until enough fresh evidence is available.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "summary": "Log the session out of WhatsApp", "security": [ { "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] } ], + "x-required-scope": "sessions:manage", "parameters": [ { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "required": false, "schema": { "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } + }, + "required": true, + "name": "sessionId", + "in": "path" } ], "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "202": { + "description": "Session logout scheduled", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "API version applied to this response." + "$ref": "#/components/schemas/PlatformAccessOperationAccepted" + } } - }, + } + }, + "400": { + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeNumber" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null }, - "required": [ - "data", - "page" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } } - } + }, + "x-mint": { + "content": "sessions:manage" + }, + "description": "**Required scope:** `sessions:manage`", + "x-audience": "public" } }, - "/platform/bansafe/health/{session}": { + "/platform/sessions/{sessionId}/me": { "get": { - "operationId": "getBanSafeHealth", + "operationId": "getSessionAccount", "tags": [ - "bansafe" + "Sessions" ], - "summary": "Get number safety", - "description": "Returns one number's present Health estimate and model metadata, retained open and acknowledged findings with their observation times, directly observed account state, the active Polymorfa restriction, and how that restriction lifts. Recorded findings and observed state remain separate from the estimate.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "summary": "Get the connected WhatsApp account info", "security": [ { "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] } ], + "x-required-scope": "sessions:read", "parameters": [ { - "name": "session", - "in": "path", - "required": true, "schema": { "type": "string" - } + }, + "required": true, + "name": "sessionId", + "in": "path" } ], "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "description": "Account info", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "API version applied to this response." + "$ref": "#/components/schemas/PlatformAccessGetSessionAccountResponse" + } } - }, + } + }, + "400": { + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/BanSafeNumberDetail" - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, - "/platform/bansafe/health/{session}/history": { - "get": { - "operationId": "listBanSafeHealthHistory", - "tags": [ - "bansafe" - ], - "summary": "List number health history", - "description": "Returns the newest Health evaluations since the requested date, up to the limit, ordered oldest to newest. Each point carries its estimator source and version, evidence coverage, reliability, and availability reason.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } + } + }, + "x-mint": { + "content": "sessions:read" + }, + "description": "**Required scope:** `sessions:read`", + "x-audience": "public" + } + }, + "/platform/sessions/{sessionId}/handoff-keys": { + "post": { + "operationId": "sessionHandoffKeys", + "tags": [ + "Sessions" + ], + "summary": "Initiate M↔P toggle: fetch the destination Curve25519 pubkey", "security": [ { "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] } ], + "x-required-scope": "sessions:manage", "parameters": [ { - "name": "session", - "in": "path", - "required": true, "schema": { "type": "string" - } - }, - { - "name": "since", - "in": "query", - "required": false, - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 720, - "default": 720 - } + }, + "required": true, + "name": "sessionId", + "in": "path" } ], "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "description": "Destination pubkey + correlation id for the handoff", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "API version applied to this response." + "$ref": "#/components/schemas/PlatformAccessHandoffKeysSuccess" + } } - }, + } + }, + "400": { + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/BanSafeHealthHistory" - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } } - } + }, + "x-mint": { + "content": "sessions:manage" + }, + "description": "**Required scope:** `sessions:manage`", + "x-audience": "public" } }, - "/platform/bansafe/signals": { - "get": { - "operationId": "listBanSafeSignals", + "/platform/sessions/{sessionId}/handoff-import": { + "post": { + "operationId": "sessionHandoffImport", "tags": [ - "bansafe" + "Sessions" ], - "summary": "List BanSafe telemetry signals", - "description": "Lists the bounded numeric, boolean, enum, histogram, and error-code aggregate signals that BanSafe can return. The catalogue contains no message content, recipient identifiers, or raw telemetry records.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "summary": "Ship a sealed SignalStoreSnapshot from customer Baileys to the runner", "security": [ { "ApiKeyAuth": [] - }, + } + ], + "x-required-scope": "sessions:manage", + "parameters": [ { - "ConsoleSession": [] + "schema": { + "type": "string" + }, + "required": true, + "name": "sessionId", + "in": "path" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessHandoffImportRequest" + } + } + } + }, "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "description": "Import accepted; bartenderMode will flip after the runner acks", + "content": { + "application/json": { "schema": { - "type": "string" - }, - "description": "API version applied to this response." + "$ref": "#/components/schemas/PlatformAccessHandoffImportSuccess" + } } - }, + } + }, + "400": { + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeSignalDefinition" - } - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } } - } + }, + "x-mint": { + "content": "sessions:manage" + }, + "description": "**Required scope:** `sessions:manage`", + "x-audience": "public" } }, - "/platform/bansafe/telemetry/{session}": { - "get": { - "operationId": "getBanSafeTelemetry", + "/platform/sessions/{sessionId}/upload-appstate-keys": { + "post": { + "operationId": "sessionUploadAppStateKeys", "tags": [ - "bansafe" + "Sessions" ], - "summary": "Get latest BanSafe telemetry", - "description": "Returns collection availability and the latest supported telemetry snapshot for one number. Empty collection is reported as not collected, not as a disabled collector.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "summary": "Upload selected AppStateSyncKeys (Track P+ tier)", "security": [ { "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] } ], + "x-required-scope": "sessions:manage", "parameters": [ { - "name": "session", - "in": "path", - "required": true, "schema": { "type": "string" - } + }, + "required": true, + "name": "sessionId", + "in": "path" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessUploadAppStateKeysRequest" + } + } + } + }, "responses": { "200": { - "description": "Success", - "headers": { - "X-Request-Id": { + "description": "Keys persisted; mode may have been flipped per upgradeTo", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PlatformAccessUploadAppStateKeysSuccess" + } + } + } + }, + "400": { + "description": "Bad request — request body or params failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } + }, + "401": { + "description": "Missing or invalid authentication token", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PlatformAccessPublicError" }, - "description": "API version applied to this response." + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } } - }, + } + }, + "403": { + "description": "Authenticated but not permitted (missing scope or rule violation)", "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/BanSafeTelemetryDetail" - } + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null }, - "required": [ - "data" - ] + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } + } + }, + "x-mint": { + "content": "sessions:manage" + }, + "description": "**Required scope:** `sessions:manage`", + "x-audience": "public" + } + }, + "/platform/sessions/{sessionId}/handoff-ack": { + "post": { + "operationId": "sessionHandoffAck", + "tags": [ + "Sessions" + ], + "summary": "Acknowledge receipt of an M→P export; API flips bartenderMode after hash verify", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-required-scope": "sessions:manage", + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "sessionId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessHandoffAckRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Ack accepted; bartenderMode flipped from magic → passthrough", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessHandoffAckSuccess" } } } }, "400": { - "$ref": "#/components/responses/BadRequest" + "description": "Bad request — request body or params failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } }, "401": { - "$ref": "#/components/responses/Unauthorized" + "description": "Missing or invalid authentication token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "authentication_error", + "code": "invalid_credential", + "message": "Supply a valid credential.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-credential" + } + } + } }, "403": { - "$ref": "#/components/responses/Forbidden" + "description": "Authenticated but not permitted (missing scope or rule violation)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformAccessPublicError" + }, + "example": { + "error": { + "type": "api_error", + "code": "internal_error", + "message": "An unexpected service failure occurred. Keep the request ID for support.", + "param": null + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#internal-error" + } + } + } } - } + }, + "x-mint": { + "content": "sessions:manage" + }, + "description": "**Required scope:** `sessions:manage`", + "x-audience": "public" } }, - "/platform/bansafe/telemetry/{session}/history": { + "/platform/session-configuration": { "get": { - "operationId": "listBanSafeTelemetryHistory", + "operationId": "getSessionConfigurationDefaults", "tags": [ - "bansafe" + "session-configuration" ], - "summary": "List BanSafe telemetry history", - "description": "Returns the newest supported telemetry snapshots for one number. Signals are fixed-schema safe aggregates with explicit measured state; raw records and identifiers inside them are not returned.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "Read session configuration defaults", + "description": "Returns effective team or project defaults, explicit overrides, and their source.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, @@ -1526,49 +2417,12 @@ ], "parameters": [ { - "name": "session", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "since", - "in": "query", - "required": false, - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "until", - "in": "query", - "required": false, - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "cursor", + "name": "projectId", "in": "query", "required": false, "schema": { "type": "string" } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 50, - "default": 10 - } } ], "responses": { @@ -1595,26 +2449,16 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeTelemetrySnapshot" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/SessionConfigurationView" } }, "required": [ - "data", - "page" + "data" ] } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, @@ -1625,20 +2469,18 @@ "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/bansafe/collection": { - "get": { - "operationId": "listBanSafeCollection", + }, + "put": { + "operationId": "updateSessionConfigurationDefaults", "tags": [ - "bansafe" + "session-configuration" ], - "summary": "List BanSafe collection status", - "description": "Lists telemetry collection freshness for the numbers in one project. Status is derived from ingested records and does not guess whether a collector flag is configured.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "Update session configuration defaults", + "description": "Updates explicit defaults using a revision check. Existing sessions inherit fields they have not overridden. Runtime application is asynchronous.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -1647,35 +2489,16 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionConfigurationUpdateRequest" + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -1700,18 +2523,11 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeCollectionSession" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/SessionConfigurationView" } }, "required": [ - "data", - "page" + "data" ] } } @@ -1728,82 +2544,47 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/bansafe/health-actions": { + "/console/session-configuration/{sessionId}": { "get": { - "operationId": "listBanSafeHealthActions", + "operationId": "getSessionConfiguration", "tags": [ - "bansafe" + "session-configuration" ], - "summary": "List Health rule actions", - "description": "Lists the durable customer-visible outcomes of project Health rule actions, newest first. Internal leases, retry bookkeeping, runtime identifiers, and free-form outcomes are omitted.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "summary": "Read session configuration", + "description": "Returns effective configuration, overrides, and runtime application status for an owned session.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-exclusion-reason": "Private Console session configuration; requires live dashboard membership.", "security": [ - { - "ApiKeyAuth": [] - }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "session", - "in": "query", - "required": false, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 256 - } - }, - { - "name": "status", - "in": "query", - "required": false, + "name": "sessionId", + "in": "path", + "required": true, + "description": "Database session UUID or stable session slug.", "schema": { - "type": "string", - "enum": [ - "pending", - "running", - "succeeded", - "failed", - "cancelled" - ] + "$ref": "#/components/schemas/SessionIdentifier" } }, { - "name": "cursor", + "name": "projectId", "in": "query", "required": false, "schema": { "type": "string" } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } } ], "responses": { @@ -1830,26 +2611,16 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeHealthAction" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/SessionConfigurationView" } }, "required": [ - "data", - "page" + "data" ] } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, @@ -1860,93 +2631,44 @@ "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/bansafe/findings": { - "get": { - "operationId": "listBanSafeFindings", + }, + "put": { + "operationId": "updateSessionConfiguration", "tags": [ - "bansafe" + "session-configuration" ], - "summary": "List safety findings", - "description": "Lists safety findings across the organization, filterable by project, number, status, and severity. Evidence is numbers only and never contains message content or a recipient identifier.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "summary": "Update session configuration", + "description": "Sets or resets explicit configuration for an owned session using a revision check.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-exclusion-reason": "Private Console session configuration; requires live dashboard membership.", "security": [ - { - "ApiKeyAuth": [] - }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "session", - "in": "query", - "required": false, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 256 - } - }, - { - "name": "status", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "open", - "acknowledged", - "resolved" - ] - } - }, - { - "name": "severity", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "info", - "warning", - "critical" - ] - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, + "name": "sessionId", + "in": "path", + "required": true, + "description": "Database session UUID or stable session slug.", "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 + "$ref": "#/components/schemas/SessionIdentifier" } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionConfigurationUpdateRequest" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -1971,18 +2693,11 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeFinding" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/SessionConfigurationView" } }, "required": [ - "data", - "page" + "data" ] } } @@ -1999,48 +2714,46 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/bansafe/findings/{findingId}/acknowledge": { - "post": { - "operationId": "acknowledgeBanSafeFinding", + "/platform/sessions": { + "get": { + "operationId": "listPlatformSessions", "tags": [ - "bansafe" + "sessions" ], - "summary": "Acknowledge a safety finding", - "description": "Records that a named person on the team has seen this finding, optionally silences its notifications for up to 30 days, and unlocks the appeal form. It does not change the Health estimate, the restriction, or any campaign limit. Requires a signed-in organization owner, admin, or developer; an API key or token is refused with `bansafe_ack_requires_dashboard_identity`.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "summary": "List sessions", + "description": "Lists every session in the authenticated team or selected project, including disconnected sessions and their latest status reason.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-dashboard-only": true, + "x-required-scope": "sessions:read", + "x-merged-endpoints": [ + "listSessionsByOrganization" + ], "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "findingId", - "in": "path", - "required": true, + "name": "projectId", + "in": "query", + "required": false, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AcknowledgeBanSafeFindingInput" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -2065,7 +2778,10 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/BanSafeFindingAcknowledgement" + "type": "array", + "items": { + "$ref": "#/components/schemas/PlatformSession" + } } }, "required": [ @@ -2083,25 +2799,22 @@ }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" } } } }, - "/platform/bansafe/enforcement": { - "get": { - "operationId": "listBanSafeEnforcement", + "/platform/sessions/{sessionId}/start": { + "post": { + "operationId": "startPlatformSession", "tags": [ - "bansafe" + "sessions" ], - "summary": "List number restrictions", - "description": "Lists every number Polymorfa is currently restricting, what the restriction blocks, and the exact condition that lifts it.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "Start a session", + "description": "Starts a stopped session or retries a session whose runtime failed. A hosted paid number must reserve credit before connecting; insufficient available credit returns 402. A new paid window is charged only after the messaging connection succeeds. The identifier may be the session UUID or stable session slug.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "session", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -2112,48 +2825,25 @@ ], "parameters": [ { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "rung", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "none", - "notify", - "throttle", - "block_cold", - "suspend" - ] - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, + "name": "sessionId", + "in": "path", + "required": true, + "description": "Database session UUID or stable session slug.", "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 + "$ref": "#/components/schemas/SessionIdentifier" } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionProjectContext" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -2178,71 +2868,74 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeEnforcementSummary" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/SessionStartResult" } }, "required": [ - "data", - "page" + "data" ] } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, + "402": { + "$ref": "#/components/responses/BillingCreditRequired" + }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/InternalError" } } } }, - "/platform/bansafe/enforcement/{session}/appeal": { + "/platform/sessions/{sessionId}/stop": { "post": { - "operationId": "appealBanSafeEnforcement", + "operationId": "stopPlatformSession", "tags": [ - "bansafe" + "sessions" ], - "summary": "Appeal a number restriction", - "description": "Files one appeal against a number's restriction. Every open finding on the number must be acknowledged first, otherwise the request is refused with `findings_unacknowledged`. Filing an appeal does not change the restriction. Requires a signed-in organization owner, admin, or developer.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "summary": "Stop a session", + "description": "Requests an asynchronous stop for one session. The identifier may be the session UUID or stable session slug.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "session", "x-excluded-from-public": false, - "x-dashboard-only": true, + "x-required-scope": "sessions:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "session", + "name": "sessionId", "in": "path", "required": true, + "description": "Database session UUID or stable session slug.", "schema": { - "type": "string" + "$ref": "#/components/schemas/SessionIdentifier" } } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AppealBanSafeEnforcementInput" + "$ref": "#/components/schemas/SessionProjectContext" } } } @@ -2271,7 +2964,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/BanSafeAppealReceipt" + "$ref": "#/components/schemas/SessionStopResult" } }, "required": [ @@ -2281,9 +2974,6 @@ } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, @@ -2293,24 +2983,24 @@ "404": { "$ref": "#/components/responses/NotFound" }, - "409": { - "$ref": "#/components/responses/Conflict" + "500": { + "$ref": "#/components/responses/InternalError" } } } }, - "/platform/bansafe/incidents": { - "get": { - "operationId": "listBanSafeIncidents", + "/platform/sessions/{sessionId}/tier-quotes": { + "post": { + "operationId": "quoteSessionTierChange", "tags": [ - "bansafe" + "sessions" ], - "summary": "List safety incidents", - "description": "Lists the WhatsApp restrictions and customer reports recorded for the organization's numbers.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "Preview a number tier change", + "description": "Returns the full credit charge and effective time before confirmation. Null tierOverride restores project inheritance. Creating a quote does not change the number or consume credit. Quotes expire after ten minutes and are invalidated when the number or price changes.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "session", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -2321,43 +3011,25 @@ ], "parameters": [ { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "session", - "in": "query", - "required": false, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 256 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, + "name": "sessionId", + "in": "path", + "required": true, + "description": "Database session UUID or stable session slug.", "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 + "$ref": "#/components/schemas/SessionIdentifier" } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NumberTierQuoteRequest" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -2382,18 +3054,11 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeIncident" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/NumberTierChange" } }, "required": [ - "data", - "page" + "data" ] } } @@ -2410,20 +3075,25 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } - }, - "post": { - "operationId": "createBanSafeIncident", + } + }, + "/platform/sessions/{sessionId}/tier-quotes/{quoteId}": { + "get": { + "operationId": "getSessionTierChange", "tags": [ - "bansafe" + "sessions" ], - "summary": "Report a ban", - "description": "Records that WhatsApp restricted or banned one of your numbers. Send the same `Idempotency-Key` to retry safely for the same number; a repeat returns the original report. Reusing the key for a different number returns `409 bansafe_incident_idempotency_scope_conflict`.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "Read a number tier change", + "description": "Reads the quote and durable result for a number in the authenticated project scope.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "session", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -2434,26 +3104,23 @@ ], "parameters": [ { - "name": "Idempotency-Key", - "in": "header", + "name": "sessionId", + "in": "path", "required": true, + "description": "Database session UUID or stable session slug.", "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 + "$ref": "#/components/schemas/SessionIdentifier" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ReportBanSafeIncidentInput" - } + }, + { + "name": "quoteId", + "in": "path", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -2478,7 +3145,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/BanSafeIncidentReceipt" + "$ref": "#/components/schemas/NumberTierChange" } }, "required": [ @@ -2499,23 +3166,20 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } } }, - "/platform/bansafe/incidents/{incidentId}/retract": { + "/platform/sessions/stop": { "post": { - "operationId": "retractBanSafeIncident", + "operationId": "stopSessions", "tags": [ - "bansafe" + "sessions" ], - "summary": "Withdraw a ban report", - "description": "Withdraws a customer report and returns its incident record. Repeating the request returns the closed record. An incident observed from WhatsApp cannot be withdrawn and returns `409 bansafe_incident_not_a_report`. An unknown incident returns 404.\n\nRequires a team API key with `sessions:manage`. Project tokens are not accepted.", + "summary": "Stop multiple sessions", + "description": "Requests asynchronous stops for up to 100 session UUIDs or slugs in the authenticated project scope.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, "x-required-scope": "sessions:manage", "security": [ @@ -2526,19 +3190,19 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "incidentId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionBatchRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Success", "headers": { "X-Request-Id": { "schema": { @@ -2560,7 +3224,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/BanSafeIncident" + "$ref": "#/components/schemas/SessionBatchStopResult" } }, "required": [ @@ -2579,27 +3243,24 @@ "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "500": { + "$ref": "#/components/responses/InternalError" } } } }, - "/platform/bansafe/claims": { - "get": { - "operationId": "listBanSafeClaims", + "/platform/sessions/delete": { + "post": { + "operationId": "deleteSessions", "tags": [ - "bansafe" + "sessions" ], - "summary": "List Ban Insurance claims", - "description": "Lists the Ban Insurance claims filed on your numbers, newest first. A claim is filed automatically when a ban is recorded on a number whose plan includes insurance; there is no request to file one.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "Remove multiple sessions", + "description": "Permanently removes up to 100 session UUIDs or slugs and terminates any rows that were still active.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -2608,61 +3269,16 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "session", - "in": "query", - "required": false, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 256 - } - }, - { - "name": "status", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "filed", - "under_review", - "approved", - "denied", - "paid", - "reversed" - ] - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionBatchRequest" + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -2687,18 +3303,11 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeClaim" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/SessionBatchRemoveResult" } }, "required": [ - "data", - "page" + "data" ] } } @@ -2712,23 +3321,20 @@ }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" } } } }, - "/platform/bansafe/claims/{claimId}": { + "/platform/bans": { "get": { - "operationId": "getBanSafeClaim", + "operationId": "listSessionBans", "tags": [ - "bansafe" + "bans" ], - "summary": "Get a Ban Insurance claim", - "description": "Returns one claim: the 30 days it covers, the credits that number consumed in them, the most the plan returns, what the claim returns, what the ban was attributed to, and the evidence counts behind that.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "List session bans", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, "x-required-scope": "sessions:read", "security": [ @@ -2739,16 +3345,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "claimId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "responses": { "200": { "description": "Success", @@ -2769,45 +3365,33 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/BanSafeClaim" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/projects/{projectId}/safe-mode": { + "/platform/bans/active": { "get": { - "operationId": "getProjectSafeModeCeiling", + "operationId": "listActiveSessionBans", "tags": [ - "bansafe" + "bans" ], - "summary": "Get a project's Safe Mode ceiling", - "description": "Returns the most visible sending behaviour any number in this project may use. Every setting is off by default. `entitled` reports whether the plan includes BanSafe Lite, which Safe Mode is part of, and `entitlementReason` gives the reason a raise would be refused.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "List active session bans", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, "x-required-scope": "sessions:read", "security": [ @@ -2818,17 +3402,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], "responses": { "200": { "description": "Success", @@ -2849,45 +3422,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/ProjectSafeMode" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "put": { - "operationId": "updateProjectSafeModeCeiling", + } + }, + "/platform/bansafe/health": { + "get": { + "operationId": "listBanSafeHealth", "tags": [ "bansafe" ], - "summary": "Set a project's Safe Mode ceiling", - "description": "Sets the most visible behaviour any number in this project may use. Send only the settings you want to change. Presence and read receipts are visible to the people these numbers message: turning them on makes a number appear online, exposes last seen subject to the account's privacy settings, and puts blue ticks on incoming messages. Raising a setting requires a plan that includes BanSafe Lite, and lowering one is always allowed. A signed-in caller must be an organization owner, admin, or developer.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "List number safety", + "description": "Lists present Health, findings counts, and the active Polymorfa restriction for every number in one project. Health is a 0 to 100 estimate from rules v1 or a validated learned model. It remains null until enough fresh evidence is available.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -2899,24 +3462,32 @@ "parameters": [ { "name": "projectId", - "in": "path", - "required": true, + "in": "query", + "required": false, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateProjectSafeModeInput" - } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 } } - }, + ], "responses": { "200": { "description": "Success", @@ -2941,11 +3512,18 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectSafeMode" + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeNumber" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" } }, "required": [ - "data" + "data", + "page" ] } } @@ -2957,31 +3535,25 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, - "402": { - "$ref": "#/components/responses/PaymentRequired" - }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" - }, - "503": { - "$ref": "#/components/responses/PropagationPending" } } } }, - "/platform/sessions/{sessionId}/safe-mode": { + "/platform/bansafe/health/{session}": { "get": { - "operationId": "getNumberSafeMode", + "operationId": "getBanSafeHealth", "tags": [ "bansafe" ], - "summary": "Get a number's Safe Mode", - "description": "Returns the project ceiling, this number's override, the policy in force, and what the number last reported it is running. `mismatch` is true when the two differ. `entitled` reports whether the plan includes BanSafe Lite, which Safe Mode is part of, and `entitlementReason` gives the reason a raise would be refused.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "Get number safety", + "description": "Returns one number's present Health estimate and model metadata, retained open and acknowledged findings with their observation times, directly observed account state, the active Polymorfa restriction, and how that restriction lifts. Recorded findings and observed state remain separate from the estimate.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "session", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, "x-required-scope": "sessions:read", "security": [ @@ -2994,12 +3566,11 @@ ], "parameters": [ { - "name": "sessionId", + "name": "session", "in": "path", "required": true, - "description": "Database session UUID or stable session slug.", "schema": { - "$ref": "#/components/schemas/SessionIdentifier" + "type": "string" } } ], @@ -3027,7 +3598,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/SessionSafeMode" + "$ref": "#/components/schemas/BanSafeNumberDetail" } }, "required": [ @@ -3050,18 +3621,20 @@ "$ref": "#/components/responses/NotFound" } } - }, - "put": { - "operationId": "updateNumberSafeMode", + } + }, + "/platform/bansafe/health/{session}/history": { + "get": { + "operationId": "listBanSafeHealthHistory", "tags": [ "bansafe" ], - "summary": "Set a number's Safe Mode", - "description": "Sets one number's Safe Mode. Each setting accepts `inherit` to follow the project ceiling. An override may only be equally or more conservative than the ceiling; a more visible value is refused with `safe_mode_exceeds_project_ceiling`. Raising a setting requires a plan that includes BanSafe Lite, and lowering one is always allowed. A signed-in caller must be an organization owner, admin, or developer.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "List number health history", + "description": "Returns the newest Health evaluations since the requested date, up to the limit, ordered oldest to newest. Each point carries its estimator source and version, evidence coverage, reliability, and availability reason.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "session", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -3072,25 +3645,34 @@ ], "parameters": [ { - "name": "sessionId", + "name": "session", "in": "path", "required": true, - "description": "Database session UUID or stable session slug.", "schema": { - "$ref": "#/components/schemas/SessionIdentifier" + "type": "string" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateSessionSafeModeInput" - } + }, + { + "name": "since", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 720, + "default": 720 } } - }, + ], "responses": { "200": { "description": "Success", @@ -3115,7 +3697,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/SessionSafeMode" + "$ref": "#/components/schemas/BanSafeHealthHistory" } }, "required": [ @@ -3131,29 +3713,23 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, - "402": { - "$ref": "#/components/responses/PaymentRequired" - }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" - }, - "503": { - "$ref": "#/components/responses/PropagationPending" } } } }, - "/platform/projects/{projectId}/warmup-plan": { + "/platform/bansafe/signals": { "get": { - "operationId": "getProjectWarmupPlanSettings", + "operationId": "listBanSafeSignals", "tags": [ "bansafe" ], - "summary": "Get a project's warm-up plan", - "description": "Returns whether numbers in this project have a daily allowance, the two settings that shape the ramp, the per-number daily cap it climbs to, and the projected allowance day by day. `entitled` reports whether the plan includes BanSafe Lite, which the warm-up plan is part of.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "List BanSafe telemetry signals", + "description": "Lists the bounded numeric, boolean, enum, histogram, and error-code aggregate signals that BanSafe can return. The catalogue contains no message content, recipient identifiers, or raw telemetry records.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, @@ -3166,17 +3742,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], "responses": { "200": { "description": "Success", @@ -3201,7 +3766,10 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectWarmupPlan" + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeSignalDefinition" + } } }, "required": [ @@ -3224,18 +3792,20 @@ "$ref": "#/components/responses/NotFound" } } - }, - "put": { - "operationId": "updateProjectWarmupPlanSettings", + } + }, + "/platform/bansafe/telemetry/{session}": { + "get": { + "operationId": "getBanSafeTelemetry", "tags": [ "bansafe" ], - "summary": "Set a project's warm-up plan", - "description": "Turns the warm-up plan on or off for a project and sets how long the ramp takes and how many messages a number may send on its first day. Send only the settings you want to change. While the plan is on, a send past a number's allowance is refused with `bansafe_daily_allowance_reached` and campaign recipients are deferred to the next reset. Turning the plan on requires a plan that includes BanSafe Lite; turning it off is always allowed. A signed-in caller must be an organization owner, admin, or developer.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "Get latest BanSafe telemetry", + "description": "Returns collection availability and the latest supported telemetry snapshot for one number. Empty collection is reported as not collected, not as a disabled collector.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -3246,25 +3816,14 @@ ], "parameters": [ { - "name": "projectId", + "name": "session", "in": "path", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateProjectWarmupPlanInput" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -3289,7 +3848,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectWarmupPlan" + "$ref": "#/components/schemas/BanSafeTelemetryDetail" } }, "required": [ @@ -3305,29 +3864,23 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, - "402": { - "$ref": "#/components/responses/PaymentRequired" - }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" - }, - "503": { - "$ref": "#/components/responses/PropagationPending" } } } }, - "/platform/projects/{projectId}/health-policy": { + "/platform/bansafe/telemetry/{session}/history": { "get": { - "operationId": "getProjectHealthPolicySettings", + "operationId": "listBanSafeTelemetryHistory", "tags": [ "bansafe" ], - "summary": "Get a project's health policy", - "description": "Returns the health threshold and the actions configured for numbers in this project. Integration flags report whether email and webhook delivery are ready to use.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "List BanSafe telemetry history", + "description": "Returns the newest supported telemetry snapshots for one number. Signals are fixed-schema safe aggregates with explicit measured state; raw records and identifiers inside them are not returned.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, @@ -3342,12 +3895,48 @@ ], "parameters": [ { - "name": "projectId", + "name": "session", "in": "path", "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "since", + "in": "query", + "required": false, "schema": { "type": "string", - "format": "uuid" + "format": "date-time" + } + }, + { + "name": "until", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 10 } } ], @@ -3375,11 +3964,18 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectHealthPolicy" + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeTelemetrySnapshot" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" } }, "required": [ - "data" + "data", + "page" ] } } @@ -3398,18 +3994,20 @@ "$ref": "#/components/responses/NotFound" } } - }, - "put": { - "operationId": "updateProjectHealthPolicySettings", + } + }, + "/platform/bansafe/collection": { + "get": { + "operationId": "listBanSafeCollection", "tags": [ "bansafe" ], - "summary": "Set a project's health policy", - "description": "Replaces the project policy when the supplied version matches the stored version. The rule applies only when an available health estimate is below the threshold; an unavailable estimate triggers no action. Stopping disconnects a session and retains its linked credentials. Logging out requires relinking.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "List BanSafe collection status", + "description": "Lists telemetry collection freshness for the numbers in one project. Status is derived from ingested records and does not guess whether a collector flag is configured.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -3421,24 +4019,32 @@ "parameters": [ { "name": "projectId", - "in": "path", - "required": true, + "in": "query", + "required": false, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateProjectHealthPolicyInput" - } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 } } - }, + ], "responses": { "200": { "description": "Success", @@ -3463,12 +4069,19 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectHealthPolicy" + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeCollectionSession" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" } }, "required": [ - "data" - ] + "data", + "page" + ] } } } @@ -3484,21 +4097,18 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } } }, - "/platform/projects/{projectId}/insurance-evidence": { + "/platform/bansafe/health-actions": { "get": { - "operationId": "getInsuranceEvidenceSettings", + "operationId": "listBanSafeHealthActions", "tags": [ "bansafe" ], - "summary": "Get a project's insurance evidence setting", - "description": "Returns whether evidence collection about other linked devices is enabled and whether any number in the project has Ban Insurance included in its plan. Collection is off until explicitly enabled.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "List Health rule actions", + "description": "Lists the durable customer-visible outcomes of project Health rule actions, newest first. Internal leases, retry bookkeeping, runtime identifiers, and free-form outcomes are omitted.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, @@ -3514,11 +4124,54 @@ "parameters": [ { "name": "projectId", - "in": "path", - "required": true, + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "session", + "in": "query", + "required": false, "schema": { "type": "string", - "format": "uuid" + "minLength": 1, + "maxLength": 256 + } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "pending", + "running", + "succeeded", + "failed", + "cancelled" + ] + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 } } ], @@ -3546,11 +4199,18 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectInsuranceEvidence" + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeHealthAction" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" } }, "required": [ - "data" + "data", + "page" ] } } @@ -3569,18 +4229,20 @@ "$ref": "#/components/responses/NotFound" } } - }, - "put": { - "operationId": "updateInsuranceEvidenceSettings", + } + }, + "/platform/bansafe/findings": { + "get": { + "operationId": "listBanSafeFindings", "tags": [ "bansafe" ], - "summary": "Set a project's insurance evidence setting", - "description": "Enables or disables collection of counts about other linked devices. Collection never includes messages, recipient identities, or device names. Disabling collection discards stored device evidence. A 503 `propagation_pending` means the setting was stored but a connected number has not confirmed it; retry the request. A signed-in caller must be an organization owner, admin, or developer.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "List safety findings", + "description": "Lists safety findings across the team, filterable by project, number, status, and severity. Evidence is numbers only and never contains message content or a recipient identifier.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -3592,24 +4254,68 @@ "parameters": [ { "name": "projectId", - "in": "path", - "required": true, + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "session", + "in": "query", + "required": false, "schema": { "type": "string", - "format": "uuid" + "minLength": 1, + "maxLength": 256 } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateProjectInsuranceEvidenceInput" - } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "open", + "acknowledged", + "resolved" + ] + } + }, + { + "name": "severity", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "info", + "warning", + "critical" + ] + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 } } - }, + ], "responses": { "200": { "description": "Success", @@ -3634,11 +4340,18 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectInsuranceEvidence" + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeFinding" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" } }, "required": [ - "data" + "data", + "page" ] } } @@ -3655,51 +4368,48 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "503": { - "$ref": "#/components/responses/PropagationPending" } } } }, - "/platform/campaigns": { - "get": { - "operationId": "listPlatformCampaigns", + "/console/bansafe/findings/{findingId}/acknowledge": { + "post": { + "operationId": "acknowledgeBanSafeFinding", "tags": [ - "campaigns" + "bansafe" ], - "summary": "List platform campaigns", - "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "campaigns:read", + "summary": "Acknowledge a safety finding", + "description": "Records that a named person on the team has seen this finding, optionally silences its notifications for up to 30 days, and unlocks the appeal form. It does not change the Health estimate, the restriction, or any campaign limit. Requires a signed-in team owner, admin, or developer; an API key or token is refused with `bansafe_ack_requires_dashboard_identity`.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": true, + "x-dashboard-only": true, "security": [ - { - "ApiKeyAuth": [] - }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "projectId", - "in": "query", + "name": "findingId", + "in": "path", "required": true, "schema": { - "type": "string" - } - }, - { - "name": "projectSlug", - "in": "query", - "required": false, - "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AcknowledgeBanSafeFindingInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -3720,33 +4430,47 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/BanSafeFindingAcknowledgement" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - }, - "post": { - "operationId": "createPlatformCampaign", + } + }, + "/platform/bansafe/enforcement": { + "get": { + "operationId": "listBanSafeEnforcement", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Create campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "List number restrictions", + "description": "Lists every number Polymorfa is currently restricting, what the restriction blocks, and the exact condition that lifts it.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -3755,17 +4479,50 @@ "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "projectId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "rung", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "none", + "notify", + "throttle", + "block_cold", + "suspend" + ] + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 } } - }, + ], "responses": { "200": { "description": "Success", @@ -3786,46 +4543,62 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeEnforcementSummary" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" + } + }, + "required": [ + "data", + "page" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/platform/campaigns/{campaignId}": { - "get": { - "operationId": "getPlatformCampaign", + "/console/bansafe/enforcement/{session}/appeal": { + "post": { + "operationId": "appealBanSafeEnforcement", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Get campaign", - "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "campaigns:read", + "summary": "Appeal a number restriction", + "description": "Files one appeal against a number's restriction. Every open finding on the number must be acknowledged first, otherwise the request is refused with `findings_unacknowledged`. Filing an appeal does not change the restriction. Requires a signed-in team owner, admin, or developer.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": true, + "x-dashboard-only": true, "security": [ - { - "ApiKeyAuth": [] - }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "campaignId", + "name": "session", "in": "path", "required": true, "schema": { @@ -3833,6 +4606,16 @@ } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppealBanSafeEnforcementInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -3853,33 +4636,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/BanSafeAppealReceipt" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } - }, - "patch": { - "operationId": "updateCampaign", + } + }, + "/platform/bansafe/incidents": { + "get": { + "operationId": "listBanSafeIncidents", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Update campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "List safety incidents", + "description": "Lists the WhatsApp restrictions and customer reports recorded for the team's numbers.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -3890,25 +4690,43 @@ ], "parameters": [ { - "name": "campaignId", - "in": "path", - "required": true, + "name": "projectId", + "in": "query", + "required": false, "schema": { "type": "string" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + }, + { + "name": "session", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 } } - }, + ], "responses": { "200": { "description": "Success", @@ -3929,33 +4747,52 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeIncident" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" + } + }, + "required": [ + "data", + "page" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } }, - "delete": { - "operationId": "deleteCampaign", + "post": { + "operationId": "createBanSafeIncident", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Delete campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "Report a ban", + "description": "Records that WhatsApp restricted or banned one of your numbers. Send the same `Idempotency-Key` to retry safely for the same number; a repeat returns the original report. Reusing the key for a different number returns `409 bansafe_incident_idempotency_scope_conflict`.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -3966,14 +4803,26 @@ ], "parameters": [ { - "name": "campaignId", - "in": "path", + "name": "Idempotency-Key", + "in": "header", "required": true, "schema": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportBanSafeIncidentInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -3994,35 +4843,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/BanSafeIncidentReceipt" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/campaigns/{campaignId}/launch": { + "/platform/bansafe/incidents/{incidentId}/retract": { "post": { - "operationId": "launchPlatformCampaign", + "operationId": "retractBanSafeIncident", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Launch campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "Withdraw a ban report", + "description": "Withdraws a customer report and returns its incident record. Repeating the request returns the closed record. An incident observed from WhatsApp cannot be withdrawn and returns `409 bansafe_incident_not_a_report`. An unknown incident returns 404.\n\nRequires a team API key with `sessions:manage`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -4033,7 +4897,7 @@ ], "parameters": [ { - "name": "campaignId", + "name": "incidentId", "in": "path", "required": true, "schema": { @@ -4041,17 +4905,6 @@ } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -4072,35 +4925,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/BanSafeIncident" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/campaigns/{campaignId}/pause": { - "post": { - "operationId": "pausePlatformCampaign", + "/platform/bansafe/claims": { + "get": { + "operationId": "listBanSafeClaims", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Pause campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "List Ban Insurance claims", + "description": "Lists the Ban Insurance claims filed on your numbers, newest first. A claim is filed automatically when a ban is recorded on a number whose plan includes insurance; there is no request to file one.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -4111,25 +4979,59 @@ ], "parameters": [ { - "name": "campaignId", - "in": "path", - "required": true, + "name": "projectId", + "in": "query", + "required": false, "schema": { "type": "string" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + }, + { + "name": "session", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "filed", + "under_review", + "approved", + "denied", + "paid", + "reversed" + ] + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 } } - }, + ], "responses": { "200": { "description": "Success", @@ -4150,35 +5052,54 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeClaim" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" + } + }, + "required": [ + "data", + "page" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/platform/campaigns/{campaignId}/resume": { - "post": { - "operationId": "resumePlatformCampaign", + "/platform/bansafe/claims/{claimId}": { + "get": { + "operationId": "getBanSafeClaim", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Resume campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "Get a Ban Insurance claim", + "description": "Returns one claim: the 30 days it covers, the credits that number consumed in them, the most the plan returns, what the claim returns, what the ban was attributed to, and the evidence counts behind that.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -4189,7 +5110,7 @@ ], "parameters": [ { - "name": "campaignId", + "name": "claimId", "in": "path", "required": true, "schema": { @@ -4197,17 +5118,6 @@ } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -4228,35 +5138,47 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/BanSafeClaim" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/platform/campaigns/{campaignId}/stop": { - "post": { - "operationId": "stopPlatformCampaign", + "/platform/projects/{projectId}/safe-mode": { + "get": { + "operationId": "getProjectSafeModeCeiling", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Stop campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "Get a project's Safe Mode ceiling", + "description": "Returns the most visible sending behaviour any number in this project may use. Every setting is off by default. `entitled` reports whether the plan includes BanSafe Lite, which Safe Mode is part of, and `entitlementReason` gives the reason a raise would be refused.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -4267,25 +5189,15 @@ ], "parameters": [ { - "name": "campaignId", + "name": "projectId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -4306,35 +5218,45 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectSafeMode" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/campaigns/{campaignId}/archive": { - "post": { - "operationId": "archiveCampaign", + }, + "put": { + "operationId": "updateProjectSafeModeCeiling", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Archive campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "Set a project's Safe Mode ceiling", + "description": "Sets the most visible behaviour any number in this project may use. Send only the settings you want to change. Presence and read receipts are visible to the people these numbers message: turning them on makes a number appear online, exposes last seen subject to the account's privacy settings, and puts blue ticks on incoming messages. Raising a setting requires a plan that includes BanSafe Lite, and lowering one is always allowed. A signed-in caller must be an team owner, admin, or developer.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -4345,11 +5267,12 @@ ], "parameters": [ { - "name": "campaignId", + "name": "projectId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], @@ -4358,8 +5281,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/UpdateProjectSafeModeInput" } } } @@ -4384,35 +5306,53 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectSafeMode" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, + "402": { + "$ref": "#/components/responses/PaymentRequired" + }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" } } } }, - "/platform/campaigns/{campaignId}/duplicate": { - "post": { - "operationId": "duplicateCampaign", + "/platform/sessions/{sessionId}/safe-mode": { + "get": { + "operationId": "getNumberSafeMode", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Duplicate campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "Get a number's Safe Mode", + "description": "Returns the project ceiling, this number's override, the policy in force, and what the number last reported it is running. `mismatch` is true when the two differ. `entitled` reports whether the plan includes BanSafe Lite, which Safe Mode is part of, and `entitlementReason` gives the reason a raise would be refused.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "session", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -4423,25 +5363,15 @@ ], "parameters": [ { - "name": "campaignId", + "name": "sessionId", "in": "path", "required": true, + "description": "Database session UUID or stable session slug.", "schema": { - "type": "string" + "$ref": "#/components/schemas/SessionIdentifier" } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -4462,35 +5392,45 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/SessionSafeMode" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/campaigns/{campaignId}/requeue": { - "post": { - "operationId": "requeueFailedCampaign", + }, + "put": { + "operationId": "updateNumberSafeMode", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Requeue failed campaign", - "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", + "summary": "Set a number's Safe Mode", + "description": "Sets one number's Safe Mode. Each setting accepts `inherit` to follow the project ceiling. An override may only be equally or more conservative than the ceiling; a more visible value is refused with `safe_mode_exceeds_project_ceiling`. Raising a setting requires a plan that includes BanSafe Lite, and lowering one is always allowed. A signed-in caller must be an team owner, admin, or developer.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "session", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -4501,11 +5441,12 @@ ], "parameters": [ { - "name": "campaignId", + "name": "sessionId", "in": "path", "required": true, + "description": "Database session UUID or stable session slug.", "schema": { - "type": "string" + "$ref": "#/components/schemas/SessionIdentifier" } } ], @@ -4514,8 +5455,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/UpdateSessionSafeModeInput" } } } @@ -4540,35 +5480,53 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/SessionSafeMode" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, + "402": { + "$ref": "#/components/responses/PaymentRequired" + }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" } } } }, - "/platform/campaigns/{campaignId}/analytics": { + "/platform/projects/{projectId}/warmup-plan": { "get": { - "operationId": "getPlatformCampaignAnalytics", + "operationId": "getProjectWarmupPlanSettings", "tags": [ - "campaigns" + "bansafe" ], - "summary": "Get campaign analytics", - "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", + "summary": "Get a project's warm-up plan", + "description": "Returns whether numbers in this project have a daily allowance, the two settings that shape the ramp, the per-number daily cap it climbs to, and the projected allowance day by day. `entitled` reports whether the plan includes BanSafe Lite, which the warm-up plan is part of.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:read", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -4579,11 +5537,12 @@ ], "parameters": [ { - "name": "campaignId", + "name": "projectId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], @@ -4607,35 +5566,45 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectWarmupPlan" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/campaigns/{campaignId}/events": { - "get": { - "operationId": "listCampaignEvents", + }, + "put": { + "operationId": "updateProjectWarmupPlanSettings", "tags": [ - "campaigns" + "bansafe" ], - "summary": "List campaign events", - "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", + "summary": "Set a project's warm-up plan", + "description": "Turns the warm-up plan on or off for a project and sets how long the ramp takes and how many messages a number may send on its first day. Send only the settings you want to change. While the plan is on, a send past a number's allowance is refused with `bansafe_daily_allowance_reached` and campaign recipients are deferred to the next reset. Turning the plan on requires a plan that includes BanSafe Lite; turning it off is always allowed. A signed-in caller must be an team owner, admin, or developer.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:read", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -4646,14 +5615,25 @@ ], "parameters": [ { - "name": "campaignId", + "name": "projectId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectWarmupPlanInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -4674,35 +5654,53 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectWarmupPlan" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, + "402": { + "$ref": "#/components/responses/PaymentRequired" + }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" } } } }, - "/platform/campaigns/{campaignId}/recipients": { + "/platform/projects/{projectId}/health-policy": { "get": { - "operationId": "listCampaignRecipients", + "operationId": "getProjectHealthPolicySettings", "tags": [ - "campaigns" + "bansafe" ], - "summary": "List campaign recipients", - "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", + "summary": "Get a project's health policy", + "description": "Returns the health threshold and the actions configured for numbers in this project. Integration flags report whether email and webhook delivery are ready to use.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:read", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -4713,11 +5711,12 @@ ], "parameters": [ { - "name": "campaignId", + "name": "projectId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], @@ -4741,35 +5740,45 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectHealthPolicy" + } + }, + "required": [ + "data" + ] } } } }, - "401": { - "$ref": "#/components/responses/Unauthorized" + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/audiences": { - "get": { - "operationId": "listAudiences", + }, + "put": { + "operationId": "updateProjectHealthPolicySettings", "tags": [ - "audiences" + "bansafe" ], - "summary": "List audiences", - "description": "Public Management API. Requires a team API key with `campaigns:read`. Project tokens are not accepted.", + "summary": "Set a project's health policy", + "description": "Replaces the project policy when the supplied version matches the stored version. The rule applies only when an available health estimate is below the threshold; an unavailable estimate triggers no action. Stopping disconnects a session and retains its linked credentials. Logging out requires relinking.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:read", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -4778,6 +5787,27 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectHealthPolicyInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -4798,33 +5828,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectHealthPolicy" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } - }, - "post": { - "operationId": "createAudience", + } + }, + "/platform/projects/{projectId}/insurance-evidence": { + "get": { + "operationId": "getInsuranceEvidenceSettings", "tags": [ - "audiences" + "bansafe" ], - "summary": "Create audience", - "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", + "summary": "Get a project's insurance evidence setting", + "description": "Returns whether evidence collection about other linked devices is enabled and whether any number in the project has Ban Insurance included in its plan. Collection is off until explicitly enabled.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -4833,17 +5880,17 @@ "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } } - }, + ], "responses": { "200": { "description": "Success", @@ -4864,35 +5911,45 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectInsuranceEvidence" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/audiences/{listId}": { - "get": { - "operationId": "getAudience", + }, + "put": { + "operationId": "updateInsuranceEvidenceSettings", "tags": [ - "audiences" + "bansafe" ], - "summary": "Get audience", - "description": "Public Management API. Requires a team API key with `campaigns:read`. Project tokens are not accepted.", + "summary": "Set a project's insurance evidence setting", + "description": "Enables or disables collection of counts about other linked devices. Collection never includes messages, recipient identities, or device names. Disabling collection discards stored device evidence. A 503 `propagation_pending` means the setting was stored but a connected number has not confirmed it; retry the request. A signed-in caller must be an team owner, admin, or developer.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:read", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -4903,14 +5960,25 @@ ], "parameters": [ { - "name": "listId", + "name": "projectId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectInsuranceEvidenceInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -4931,33 +5999,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectInsuranceEvidence" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" } } - }, - "delete": { - "operationId": "deleteAudience", + } + }, + "/platform/campaigns": { + "get": { + "operationId": "listPlatformCampaigns", "tags": [ - "audiences" + "campaigns" ], - "summary": "Delete audience", - "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", + "summary": "List platform campaigns", + "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "campaigns:read", "security": [ { "ApiKeyAuth": [] @@ -4968,12 +6053,20 @@ ], "parameters": [ { - "name": "listId", - "in": "path", + "name": "projectId", + "in": "query", "required": true, "schema": { "type": "string" } + }, + { + "name": "projectSlug", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -5011,18 +6104,16 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/platform/audiences/uploads": { + }, "post": { - "operationId": "createAudienceUploadUrl", + "operationId": "createPlatformCampaign", "tags": [ - "audiences" + "campaigns" ], - "summary": "Create audience upload url", - "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", + "summary": "Create campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, "x-required-scope": "campaigns:manage", "security": [ @@ -5081,18 +6172,18 @@ } } }, - "/platform/templates": { + "/platform/campaigns/{campaignId}": { "get": { - "operationId": "listMessageTemplates", + "operationId": "getPlatformCampaign", "tags": [ - "templates" + "campaigns" ], - "summary": "List message templates", - "description": "Lists the message-template drafts in one project.\n\nRequires `templates:read`. Project tokens only access resources in their own project.", + "summary": "Get campaign", + "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "templates:read", + "x-required-scope": "campaigns:read", "security": [ { "ApiKeyAuth": [] @@ -5103,8 +6194,8 @@ ], "parameters": [ { - "name": "projectId", - "in": "query", + "name": "campaignId", + "in": "path", "required": true, "schema": { "type": "string" @@ -5131,49 +6222,33 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "maxItems": 500, - "items": { - "$ref": "#/components/schemas/ManagementTemplate" - } - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } }, - "post": { - "operationId": "createMessageTemplate", + "patch": { + "operationId": "updateCampaign", "tags": [ - "templates" + "campaigns" ], - "summary": "Create a message-template draft", - "description": "Creates an incomplete or complete message-template draft in one project.\n\nRequires `templates:manage`. Project tokens only access resources in their own project.", + "summary": "Update campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "templates:manage", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -5182,12 +6257,23 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "campaignId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateManagementTemplateRequest" + "type": "object", + "additionalProperties": true } } } @@ -5212,47 +6298,33 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/TemplateId" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/platform/templates/{templateId}": { - "get": { - "operationId": "getMessageTemplate", + }, + "delete": { + "operationId": "deleteCampaign", "tags": [ - "templates" + "campaigns" ], - "summary": "Get a message-template draft", - "description": "Gets one message-template draft from the specified project.\n\nRequires `templates:read`. Project tokens only access resources in their own project.", + "summary": "Delete campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "templates:read", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -5263,20 +6335,12 @@ ], "parameters": [ { - "name": "templateId", + "name": "campaignId", "in": "path", "required": true, "schema": { "type": "string" } - }, - { - "name": "projectId", - "in": "query", - "required": true, - "schema": { - "type": "string" - } } ], "responses": { @@ -5299,51 +6363,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/ManagementTemplate" - } - ], - "nullable": true - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "patch": { - "operationId": "updateMessageTemplate", + } + }, + "/platform/campaigns/{campaignId}/launch": { + "post": { + "operationId": "launchPlatformCampaign", "tags": [ - "templates" + "campaigns" ], - "summary": "Update a message-template draft", - "description": "Updates one message-template draft in the project supplied in the request body.\n\nRequires `templates:manage`. Project tokens only access resources in their own project.", + "summary": "Launch campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "templates:manage", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -5354,7 +6402,7 @@ ], "parameters": [ { - "name": "templateId", + "name": "campaignId", "in": "path", "required": true, "schema": { @@ -5363,11 +6411,12 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateManagementTemplateRequest" + "type": "object", + "additionalProperties": true } } } @@ -5392,45 +6441,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/ManagementTemplate" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "delete": { - "operationId": "deleteMessageTemplate", + } + }, + "/platform/campaigns/{campaignId}/pause": { + "post": { + "operationId": "pausePlatformCampaign", "tags": [ - "templates" + "campaigns" ], - "summary": "Delete a message-template draft", - "description": "Deletes one message-template draft from the specified project.\n\nRequires `templates:manage`. Project tokens only access resources in their own project.", + "summary": "Pause campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "templates:manage", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -5441,22 +6480,25 @@ ], "parameters": [ { - "name": "templateId", + "name": "campaignId", "in": "path", "required": true, "schema": { "type": "string" } - }, - { - "name": "projectId", - "in": "query", - "required": true, - "schema": { - "type": "string" - } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -5477,47 +6519,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/DeleteManagementTemplateResult" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/flows": { - "get": { - "operationId": "listFlows", + "/platform/campaigns/{campaignId}/resume": { + "post": { + "operationId": "resumePlatformCampaign", "tags": [ - "flows" + "campaigns" ], - "summary": "List Flow drafts", - "description": "Lists compact WhatsApp Flow draft summaries in one project.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "Resume campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -5528,14 +6558,25 @@ ], "parameters": [ { - "name": "projectId", - "in": "query", + "name": "campaignId", + "in": "path", "required": true, "schema": { "type": "string" } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -5556,49 +6597,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "maxItems": 250, - "items": { - "$ref": "#/components/schemas/FlowSummary" - } - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, + } + }, + "/platform/campaigns/{campaignId}/stop": { "post": { - "operationId": "createFlow", + "operationId": "stopPlatformCampaign", "tags": [ - "flows" + "campaigns" ], - "summary": "Create a Flow draft", - "description": "Creates a project-scoped WhatsApp Flow draft. A supplied draftId makes retries idempotent.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "Stop campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -5607,12 +6634,23 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "campaignId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateFlowRequest" + "type": "object", + "additionalProperties": true } } } @@ -5637,47 +6675,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/FlowDraft" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/flows/{flowId}": { - "get": { - "operationId": "getFlow", + "/platform/campaigns/{campaignId}/archive": { + "post": { + "operationId": "archiveCampaign", "tags": [ - "flows" + "campaigns" ], - "summary": "Get a Flow draft", - "description": "Gets one project-scoped WhatsApp Flow draft.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "Archive campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -5688,22 +6714,25 @@ ], "parameters": [ { - "name": "flowId", + "name": "campaignId", "in": "path", "required": true, "schema": { "type": "string" } - }, - { - "name": "projectId", - "in": "query", - "required": true, - "schema": { - "type": "string" - } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -5724,51 +6753,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/FlowDraft" - } - ], - "nullable": true - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "patch": { - "operationId": "updateFlow", + } + }, + "/platform/campaigns/{campaignId}/duplicate": { + "post": { + "operationId": "duplicateCampaign", "tags": [ - "flows" + "campaigns" ], - "summary": "Update a Flow draft", - "description": "Updates a Flow draft when expectedUpdatedAt matches the stored revision.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "Duplicate campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -5779,7 +6792,7 @@ ], "parameters": [ { - "name": "flowId", + "name": "campaignId", "in": "path", "required": true, "schema": { @@ -5788,11 +6801,12 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateFlowRequest" + "type": "object", + "additionalProperties": true } } } @@ -5817,48 +6831,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/FlowDraft" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "delete": { - "operationId": "deleteFlow", + } + }, + "/platform/campaigns/{campaignId}/requeue": { + "post": { + "operationId": "requeueFailedCampaign", "tags": [ - "flows" + "campaigns" ], - "summary": "Delete a Flow draft", - "description": "Deletes a local Flow draft without deleting linked Meta Flow assets.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "Requeue failed campaign", + "description": "Public Management API. Requires `campaigns:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -5869,22 +6870,25 @@ ], "parameters": [ { - "name": "flowId", + "name": "campaignId", "in": "path", "required": true, "schema": { "type": "string" } - }, - { - "name": "projectId", - "in": "query", - "required": true, - "schema": { - "type": "string" - } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -5905,45 +6909,33 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/DeleteFlowResult" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/optouts": { + "/platform/campaigns/{campaignId}/analytics": { "get": { - "operationId": "listOptOuts", + "operationId": "getPlatformCampaignAnalytics", "tags": [ - "optouts" + "campaigns" ], - "summary": "List opt outs", - "description": "Public Management API. Requires a team API key with `campaigns:read`. Project tokens are not accepted.", + "summary": "Get campaign analytics", + "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, "x-required-scope": "campaigns:read", "security": [ @@ -5954,6 +6946,16 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "campaignId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -5989,18 +6991,20 @@ "$ref": "#/components/responses/NotImplemented" } } - }, - "post": { - "operationId": "createOptOut", + } + }, + "/platform/campaigns/{campaignId}/events": { + "get": { + "operationId": "listCampaignEvents", "tags": [ - "optouts" + "campaigns" ], - "summary": "Create opt out", - "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", + "summary": "List campaign events", + "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "campaigns:read", "security": [ { "ApiKeyAuth": [] @@ -6009,17 +7013,16 @@ "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "campaignId", + "in": "path", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -6057,86 +7060,18 @@ } } }, - "/platform/optouts/batch": { - "post": { - "operationId": "createOptOuts", + "/platform/campaigns/{campaignId}/recipients": { + "get": { + "operationId": "listCampaignRecipients", "tags": [ - "optouts" - ], - "summary": "Create opt outs", - "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", - "security": [ - { - "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/platform/optouts/{phone}": { - "delete": { - "operationId": "deleteOptOut", - "tags": [ - "optouts" + "campaigns" ], - "summary": "Delete opt out", - "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", + "summary": "List campaign recipients", + "description": "Public Management API. Requires `campaigns:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "campaigns:manage", + "x-required-scope": "campaigns:read", "security": [ { "ApiKeyAuth": [] @@ -6147,7 +7082,7 @@ ], "parameters": [ { - "name": "phone", + "name": "campaignId", "in": "path", "required": true, "schema": { @@ -6192,18 +7127,18 @@ } } }, - "/platform/billing": { + "/platform/audiences": { "get": { - "operationId": "getBilling", + "operationId": "listAudiences", "tags": [ - "billing" + "audiences" ], - "summary": "Get billing", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "List audiences", + "description": "Public Management API. Requires a team API key with `campaigns:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "campaigns:read", "security": [ { "ApiKeyAuth": [] @@ -6247,20 +7182,18 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/platform/billing/usage": { - "get": { - "operationId": "listUsageMeters", + }, + "post": { + "operationId": "createAudience", "tags": [ - "billing" + "audiences" ], - "summary": "List usage meters", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "Create audience", + "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -6269,6 +7202,17 @@ "ConsoleSession": [] } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -6306,18 +7250,18 @@ } } }, - "/platform/billing/transactions": { + "/platform/audiences/{listId}": { "get": { - "operationId": "listBillingTransactions", + "operationId": "getAudience", "tags": [ - "billing" + "audiences" ], - "summary": "List billing transactions", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "Get audience", + "description": "Public Management API. Requires a team API key with `campaigns:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "campaigns:read", "security": [ { "ApiKeyAuth": [] @@ -6326,6 +7270,16 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "listId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -6361,20 +7315,18 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/platform/billing/pricing": { - "get": { - "operationId": "listTierPricing", + }, + "delete": { + "operationId": "deleteAudience", "tags": [ - "billing" + "audiences" ], - "summary": "List tier pricing", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "Delete audience", + "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -6383,6 +7335,16 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "listId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -6420,18 +7382,18 @@ } } }, - "/platform/billing/reminders": { - "patch": { - "operationId": "updateBillingReminderSettings", + "/platform/audiences/uploads": { + "post": { + "operationId": "createAudienceUploadUrl", "tags": [ - "billing" + "audiences" ], - "summary": "Update billing reminder settings", - "description": "Public Management API. Requires a team API key with `sessions:manage`. Project tokens are not accepted.", + "summary": "Create audience upload url", + "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -6488,18 +7450,18 @@ } } }, - "/platform/team": { + "/platform/templates": { "get": { - "operationId": "getOrganization", + "operationId": "listMessageTemplates", "tags": [ - "team" + "templates" ], - "summary": "Get organization", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "List message templates", + "description": "Lists the message-template drafts in one project.\n\nRequires `templates:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "templates:read", "security": [ { "ApiKeyAuth": [] @@ -6508,6 +7470,16 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "projectId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -6528,49 +7500,63 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "maxItems": 500, + "items": { + "$ref": "#/components/schemas/ManagementTemplate" + } + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/console/organization": { - "patch": { - "operationId": "updateOrganization", + }, + "post": { + "operationId": "createMessageTemplate", "tags": [ - "organization" + "templates" ], - "summary": "Update organization", - "description": "**EXCLUDED from the public API** (`console`). Organization changes require an authenticated console member.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Organization changes require an authenticated console member.", - "x-dashboard-only": true, - "x-required-organization-role": "owner_or_admin", + "summary": "Create a message-template draft", + "description": "Creates an incomplete or complete message-template draft in one project.\n\nRequires `templates:manage`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "templates:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/CreateManagementTemplateRequest" } } } @@ -6595,35 +7581,47 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/TemplateId" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/platform/projects": { + "/platform/templates/{templateId}": { "get": { - "operationId": "listProjects", + "operationId": "getMessageTemplate", "tags": [ - "projects" + "templates" ], - "summary": "List projects", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "Get a message-template draft", + "description": "Gets one message-template draft from the specified project.\n\nRequires `templates:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "templates:read", "security": [ { "ApiKeyAuth": [] @@ -6632,6 +7630,24 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "templateId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "projectId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -6652,33 +7668,51 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/ManagementTemplate" + } + ], + "nullable": true + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } }, - "post": { - "operationId": "createProject", + "patch": { + "operationId": "updateMessageTemplate", "tags": [ - "projects" + "templates" ], - "summary": "Create project", - "description": "Public Management API. Requires a team API key with `sessions:manage`. Project tokens are not accepted.", + "summary": "Update a message-template draft", + "description": "Updates one message-template draft in the project supplied in the request body.\n\nRequires `templates:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "templates:manage", "security": [ { "ApiKeyAuth": [] @@ -6687,13 +7721,22 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "templateId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/UpdateManagementTemplateRequest" } } } @@ -6718,110 +7761,71 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ManagementTemplate" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/console/projects/{projectId}": { - "patch": { - "operationId": "updateProject", + }, + "delete": { + "operationId": "deleteMessageTemplate", "tags": [ - "projects" + "templates" ], - "summary": "Update project settings", - "description": "Updates the display name, icon, or default number plan without changing the project ID, URL, stage, or individual number overrides.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard project name, icon, and default number plan settings. Requires a current owner or admin.", - "x-required-organization-role": "owner_or_admin", + "summary": "Delete a message-template draft", + "description": "Deletes one message-template draft from the specified project.\n\nRequires `templates:manage`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "templates:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "projectId", + "name": "templateId", "in": "path", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "minProperties": 1, - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 200 - }, - "defaultTier": { - "type": "string", - "enum": [ - "free", - "standard", - "pro" - ] - }, - "icon": { - "type": "object", - "additionalProperties": false, - "required": [ - "type", - "value" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "emoji", - "icon", - "image" - ] - }, - "value": { - "type": "string", - "minLength": 1, - "maxLength": 512 - }, - "color": { - "type": "string", - "maxLength": 64 - }, - "storageId": { - "type": "string", - "maxLength": 256 - } - } - } - } - } + }, + { + "name": "projectId", + "in": "query", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -6846,19 +7850,7 @@ "additionalProperties": false, "properties": { "data": { - "type": "object", - "additionalProperties": false, - "required": [ - "saved" - ], - "properties": { - "saved": { - "type": "boolean", - "enum": [ - true - ] - } - } + "$ref": "#/components/schemas/DeleteManagementTemplateResult" } }, "required": [ @@ -6883,18 +7875,18 @@ } } }, - "/platform/projects/{projectId}/promote": { - "post": { - "operationId": "requestProductionEnrollment", + "/platform/flows": { + "get": { + "operationId": "listFlows", "tags": [ - "projects" + "flows" ], - "summary": "Request production enrollment", - "description": "Creates a durable production-enrollment operation. The project stays in development until provisioning succeeds. Poll the returned operationId for progress.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "List Flow drafts", + "description": "Lists compact WhatsApp Flow draft summaries in one project.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -6906,24 +7898,13 @@ "parameters": [ { "name": "projectId", - "in": "path", + "in": "query", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductionEnrollmentRequest" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -6948,7 +7929,11 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProductionEnrollmentResult" + "type": "array", + "maxItems": 250, + "items": { + "$ref": "#/components/schemas/FlowSummary" + } } }, "required": [ @@ -6969,25 +7954,20 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } - } - }, - "/platform/projects/{projectId}/customers/status": { - "get": { - "operationId": "getCustomersStatus", + }, + "post": { + "operationId": "createFlow", "tags": [ - "customers" + "flows" ], - "summary": "Get Customers status", - "description": "Returns whether Customers is enabled for the exact project and its protected default Customer when enabled.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", + "summary": "Create a Flow draft", + "description": "Creates a project-scoped WhatsApp Flow draft. A supplied draftId makes retries idempotent.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "customers:read", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -6996,17 +7976,16 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateFlowRequest" + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -7031,7 +8010,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/CustomersStatus" + "$ref": "#/components/schemas/FlowDraft" } }, "required": [ @@ -7056,18 +8035,18 @@ } } }, - "/platform/projects/{projectId}/customers/enable": { - "post": { - "operationId": "enableCustomers", + "/platform/flows/{flowId}": { + "get": { + "operationId": "getFlow", "tags": [ - "customers" + "flows" ], - "summary": "Enable Customers", - "description": "Enables Customers once, creates the default Customer, and assigns every existing Number in the project to it atomically.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", + "summary": "Get a Flow draft", + "description": "Gets one project-scoped WhatsApp Flow draft.\n\nRequires `sessions:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "customers:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -7078,22 +8057,19 @@ ], "parameters": [ { - "name": "projectId", + "name": "flowId", "in": "path", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } }, { - "name": "Idempotency-Key", - "in": "header", + "name": "projectId", + "in": "query", "required": true, "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 + "type": "string" } } ], @@ -7121,7 +8097,13 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/CustomersEnablement" + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/FlowDraft" + } + ], + "nullable": true } }, "required": [ @@ -7142,21 +8124,16 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } - } - }, - "/platform/projects/{projectId}/production-enrollments/{operationId}/approve": { - "post": { - "operationId": "approveProductionEnrollment", + }, + "patch": { + "operationId": "updateFlow", "tags": [ - "projects" + "flows" ], - "summary": "Approve production enrollment", - "description": "Resumes a production-enrollment operation that requires approval.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "Update a Flow draft", + "description": "Updates a Flow draft when expectedUpdatedAt matches the stored revision.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, @@ -7171,31 +8148,20 @@ ], "parameters": [ { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "operationId", + "name": "flowId", "in": "path", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } } ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/UpdateFlowRequest" } } } @@ -7224,7 +8190,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProductionEnrollmentCommandResult" + "$ref": "#/components/schemas/FlowDraft" } }, "required": [ @@ -7243,20 +8209,21 @@ "403": { "$ref": "#/components/responses/Forbidden" }, + "404": { + "$ref": "#/components/responses/NotFound" + }, "409": { "$ref": "#/components/responses/Conflict" } } - } - }, - "/platform/projects/{projectId}/production-enrollments/{operationId}/cancel": { - "post": { - "operationId": "cancelProductionEnrollment", + }, + "delete": { + "operationId": "deleteFlow", "tags": [ - "projects" + "flows" ], - "summary": "Cancel production enrollment", - "description": "Requests cancellation of an active production-enrollment operation. Provisioned resources are compensated before cancellation completes.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "summary": "Delete a Flow draft", + "description": "Deletes a local Flow draft without deleting linked Meta Flow assets.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, @@ -7271,35 +8238,22 @@ ], "parameters": [ { - "name": "projectId", + "name": "flowId", "in": "path", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } }, { - "name": "operationId", - "in": "path", + "name": "projectId", + "in": "query", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -7324,7 +8278,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProductionEnrollmentCommandResult" + "$ref": "#/components/schemas/DeleteFlowResult" } }, "required": [ @@ -7343,24 +8297,24 @@ "403": { "$ref": "#/components/responses/Forbidden" }, - "409": { - "$ref": "#/components/responses/Conflict" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/platform/webhooks": { + "/platform/optouts": { "get": { - "operationId": "listOrganizationWebhooks", + "operationId": "listOptOuts", "tags": [ - "webhooks" + "optouts" ], - "summary": "List organization webhooks", - "description": "Lists only organization-owned webhook endpoints. Project-owned endpoints are excluded.\n\nRequires a team API key with `webhooks:read`. Project tokens are not accepted.", + "summary": "List opt outs", + "description": "Public Management API. Requires a team API key with `campaigns:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:read", + "x-required-scope": "campaigns:read", "security": [ { "ApiKeyAuth": [] @@ -7369,45 +8323,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "eventType", - "in": "query", - "required": false, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 128 - } - }, - { - "name": "enabled", - "in": "query", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], "responses": { "200": { "description": "Success", @@ -7428,49 +8343,33 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrganizationWebhook" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" - } - }, - "required": [ - "data", - "page" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" + }, + "501": { + "$ref": "#/components/responses/NotImplemented" } } }, "post": { - "operationId": "createOrganizationWebhook", + "operationId": "createOptOut", "tags": [ - "webhooks" + "optouts" ], - "summary": "Create an organization webhook", - "description": "Creates an organization-owned webhook and returns its generated signing secret once.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", + "summary": "Create opt out", + "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -7479,24 +8378,13 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } - } - ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateOrganizationWebhookInput" + "type": "object", + "additionalProperties": true } } } @@ -7521,47 +8409,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/OrganizationWebhookCreationReceipt" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "409": { - "$ref": "#/components/responses/Conflict" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/webhooks/{webhookId}": { - "get": { - "operationId": "getOrganizationWebhook", + "/platform/optouts/batch": { + "post": { + "operationId": "createOptOuts", "tags": [ - "webhooks" + "optouts" ], - "summary": "Get an organization webhook", - "description": "Returns one organization-owned webhook without secret values.\n\nRequires a team API key with `webhooks:read`. Project tokens are not accepted.", + "summary": "Create opt outs", + "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:read", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -7570,17 +8446,17 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -7601,45 +8477,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/OrganizationWebhook" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "patch": { - "operationId": "updateOrganizationWebhook", + } + }, + "/platform/optouts/{phone}": { + "delete": { + "operationId": "deleteOptOut", "tags": [ - "webhooks" + "optouts" ], - "summary": "Update an organization webhook", - "description": "Updates settings without rotating the signing secret.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", + "summary": "Delete opt out", + "description": "Public Management API. Requires a team API key with `campaigns:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "x-required-scope": "campaigns:manage", "security": [ { "ApiKeyAuth": [] @@ -7650,35 +8516,14 @@ ], "parameters": [ { - "name": "webhookId", + "name": "phone", "in": "path", "required": true, "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 + "type": "string" } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateOrganizationWebhookInput" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -7699,48 +8544,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/OrganizationWebhookMutationReceipt" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "delete": { - "operationId": "deleteOrganizationWebhook", + } + }, + "/platform/billing": { + "get": { + "operationId": "getBilling", "tags": [ - "webhooks" + "billing" ], - "summary": "Delete an organization webhook", - "description": "Deletes one organization-owned webhook.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", + "summary": "Get billing", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -7749,27 +8581,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } - } - ], "responses": { "200": { "description": "Success", @@ -7790,50 +8601,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/OrganizationWebhookDeletionReceipt" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/webhooks/{webhookId}/secret-rotations": { - "post": { - "operationId": "rotateOrganizationWebhookSecret", + "/platform/billing/usage": { + "get": { + "operationId": "listUsageMeters", "tags": [ - "webhooks" + "billing" ], - "summary": "Rotate an organization webhook secret", - "description": "Rotates the signing secret and returns the new secret once.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", + "summary": "List usage meters", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -7842,37 +8638,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RotateWebhookSecretInput" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -7893,50 +8658,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/OrganizationWebhookSecretRotationReceipt" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/webhooks/{webhookId}/tests": { - "post": { - "operationId": "testOrganizationWebhook", + "/platform/billing/transactions": { + "get": { + "operationId": "listBillingTransactions", "tags": [ - "webhooks" + "billing" ], - "summary": "Send an organization webhook test", - "description": "Creates a synthetic organization event and a traceable webhook delivery.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", + "summary": "List billing transactions", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -7945,37 +8695,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestWebhookInput" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -7996,50 +8715,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/OrganizationWebhookTestReceipt" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/projects/{projectId}/webhooks": { + "/platform/billing/pricing": { "get": { - "operationId": "listProjectWebhooks", + "operationId": "listTierPricing", "tags": [ - "webhooks" + "billing" ], - "summary": "List project webhooks", - "description": "Lists webhooks owned by the exact project in the route.\n\nRequires `webhooks:read`. Project tokens only access resources in their own project.", + "summary": "List tier pricing", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:read", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -8048,54 +8752,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "eventType", - "in": "query", - "required": false, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 128 - } - }, - { - "name": "enabled", - "in": "query", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], "responses": { "200": { "description": "Success", @@ -8116,52 +8772,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ProjectWebhook" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" - } - }, - "required": [ - "data", - "page" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "post": { - "operationId": "createProjectWebhook", + } + }, + "/platform/team": { + "get": { + "operationId": "getOrganization", "tags": [ - "webhooks" + "team" ], - "summary": "Create a project webhook", - "description": "Creates a project-owned webhook and returns its generated signing secret once.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", + "summary": "Get organization", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -8170,37 +8809,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateProjectWebhookInput" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -8221,78 +8829,53 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/ProjectWebhookCreationReceipt" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/projects/{projectId}/webhooks/{webhookId}": { - "get": { - "operationId": "getProjectWebhook", + "/console/organization": { + "patch": { + "operationId": "updateOrganization", "tags": [ - "webhooks" + "organization" ], - "summary": "Get a project webhook", - "description": "Returns one webhook owned by the exact project without secret values.\n\nRequires `webhooks:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "webhooks:read", + "summary": "Update organization", + "description": "**EXCLUDED from the public API** (`console`). Organization changes require an authenticated console member.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-exclusion-reason": "Organization changes require an authenticated console member.", + "x-dashboard-only": true, + "x-required-organization-role": "owner_or_admin", "security": [ - { - "ApiKeyAuth": [] - }, { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -8313,45 +8896,35 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/ProjectWebhook" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "patch": { - "operationId": "updateProjectWebhook", + } + }, + "/platform/projects": { + "get": { + "operationId": "listProjects", "tags": [ - "webhooks" + "projects" ], - "summary": "Update a project webhook", - "description": "Updates settings without rotating the signing secret.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", + "summary": "List projects", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "x-required-scope": "sessions:read", "security": [ { "ApiKeyAuth": [] @@ -8360,46 +8933,6 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateProjectWebhookInput" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -8420,48 +8953,33 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/ProjectWebhookMutationReceipt" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "501": { + "$ref": "#/components/responses/NotImplemented" } } }, - "delete": { - "operationId": "deleteProjectWebhook", + "post": { + "operationId": "createProject", "tags": [ - "webhooks" + "projects" ], - "summary": "Delete a project webhook", - "description": "Deletes one webhook owned by the exact project.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", + "summary": "Create project", + "description": "Public Management API. Requires a team API key with `sessions:manage`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -8470,36 +8988,17 @@ "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -8520,54 +9019,37 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/ProjectWebhookDeletionReceipt" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/platform/projects/{projectId}/webhooks/{webhookId}/secret-rotations": { - "post": { - "operationId": "rotateProjectWebhookSecret", + "/console/projects/{projectId}": { + "patch": { + "operationId": "updateProject", "tags": [ - "webhooks" + "projects" ], - "summary": "Rotate a project webhook secret", - "description": "Rotates the signing secret and returns the new secret once.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "summary": "Update project settings", + "description": "Updates the display name, icon, or default number plan without changing the project ID, URL, stage, or individual number overrides.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard project name, icon, and default number plan settings. Requires a current owner or admin.", + "x-required-organization-role": "owner_or_admin", "security": [ - { - "ApiKeyAuth": [] - }, { "ConsoleSession": [] } @@ -8581,62 +9063,103 @@ "type": "string", "format": "uuid" } - }, - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } } ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RotateWebhookSecretInput" - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "defaultTier": { + "type": "string", + "enum": [ + "free", + "standard", + "pro" + ] + }, + "icon": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "emoji", + "icon", + "image" + ] + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "color": { + "type": "string", + "maxLength": 64 + }, + "storageId": { + "type": "string", + "maxLength": 256 + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { "application/json": { "schema": { "type": "object", "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectWebhookSecretRotationReceipt" + "type": "object", + "additionalProperties": false, + "required": [ + "saved" + ], + "properties": { + "saved": { + "type": "boolean", + "enum": [ + true + ] + } + } } }, "required": [ @@ -8657,25 +9180,22 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } } }, - "/platform/projects/{projectId}/webhooks/{webhookId}/tests": { + "/platform/projects/{projectId}/promote": { "post": { - "operationId": "testProjectWebhook", + "operationId": "requestProductionEnrollment", "tags": [ - "webhooks" + "projects" ], - "summary": "Send a project webhook test", - "description": "Creates a synthetic project event and a traceable webhook delivery. An optional complete native envelope is accepted only with an exact HMS-enabled project session.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", + "summary": "Request production enrollment", + "description": "Creates a durable production-enrollment operation. The project stays in development until provisioning succeeds. Poll the returned operationId for progress.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "webhooks:manage", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -8693,33 +9213,14 @@ "type": "string", "format": "uuid" } - }, - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } } ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestWebhookInput" + "$ref": "#/components/schemas/ProductionEnrollmentRequest" } } } @@ -8748,7 +9249,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectWebhookTestReceipt" + "$ref": "#/components/schemas/ProductionEnrollmentResult" } }, "required": [ @@ -8772,25 +9273,22 @@ }, "409": { "$ref": "#/components/responses/Conflict" - }, - "422": { - "$ref": "#/components/responses/Unprocessable" } } } }, - "/platform/events": { + "/platform/projects/{projectId}/customers/status": { "get": { - "operationId": "listOrganizationEvents", + "operationId": "getCustomersStatus", "tags": [ - "events" + "customers" ], - "summary": "List organization events", - "description": "Lists organization-owned event metadata. Project-owned events are excluded.\n\nRequires a team API key with `events:read`. Project tokens are not accepted.", + "summary": "Get Customers status", + "description": "Returns whether Customers is enabled for the exact project and its protected default Customer when enabled.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "events:read", + "x-required-scope": "customers:read", "security": [ { "ApiKeyAuth": [] @@ -8801,48 +9299,12 @@ ], "parameters": [ { - "name": "type", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "since", - "in": "query", - "required": false, - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "until", - "in": "query", - "required": false, + "name": "projectId", + "in": "path", + "required": true, "schema": { "type": "string", - "format": "date-time" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" + "format": "uuid" } } ], @@ -8870,18 +9332,11 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrganizationEvent" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/CustomersStatus" } }, "required": [ - "data", - "page" + "data" ] } } @@ -8895,22 +9350,25 @@ }, "403": { "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/platform/events/{eventId}": { - "get": { - "operationId": "getOrganizationEvent", + "/platform/projects/{projectId}/customers/enable": { + "post": { + "operationId": "enableCustomers", "tags": [ - "events" + "customers" ], - "summary": "Get an organization event", - "description": "Returns one organization-owned event and includes exact payload bytes only when requested and available.\n\nRequires a team API key with `events:read`. Project tokens are not accepted.", + "summary": "Enable Customers", + "description": "Enables Customers once, creates the default Customer, and assigns every existing Number in the project to it atomically.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "events:read", + "x-required-scope": "customers:manage", "security": [ { "ApiKeyAuth": [] @@ -8921,7 +9379,7 @@ ], "parameters": [ { - "name": "eventId", + "name": "projectId", "in": "path", "required": true, "schema": { @@ -8930,12 +9388,13 @@ } }, { - "name": "includePayload", - "in": "query", - "required": false, + "name": "Idempotency-Key", + "in": "header", + "required": true, "schema": { - "type": "boolean", - "default": false + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], @@ -8963,7 +9422,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/OrganizationEvent" + "$ref": "#/components/schemas/CustomersEnablement" } }, "required": [ @@ -8984,22 +9443,25 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/events/{eventId}/replays": { + "/platform/projects/{projectId}/production-enrollments/{operationId}/approve": { "post": { - "operationId": "replayOrganizationEvent", + "operationId": "approveProductionEnrollment", "tags": [ - "events" + "projects" ], - "summary": "Replay an organization event", - "description": "Creates a new logical delivery for a retained organization event.\n\nRequires a team API key with `events:replay`. Project tokens are not accepted.", + "summary": "Approve production enrollment", + "description": "Resumes a production-enrollment operation that requires approval.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "events:replay", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -9010,7 +9472,7 @@ ], "parameters": [ { - "name": "eventId", + "name": "projectId", "in": "path", "required": true, "schema": { @@ -9019,22 +9481,22 @@ } }, { - "name": "Idempotency-Key", - "in": "header", + "name": "operationId", + "in": "path", "required": true, "schema": { "type": "string", - "minLength": 1, - "maxLength": 255 + "format": "uuid" } } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReplayEventInput" + "type": "object", + "additionalProperties": true } } } @@ -9063,7 +9525,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/OrganizationEventReplayReceipt" + "$ref": "#/components/schemas/ProductionEnrollmentCommandResult" } }, "required": [ @@ -9082,30 +9544,24 @@ "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" - }, "409": { "$ref": "#/components/responses/Conflict" - }, - "422": { - "$ref": "#/components/responses/Unprocessable" } } } }, - "/platform/projects/{projectId}/events": { - "get": { - "operationId": "listProjectEvents", + "/platform/projects/{projectId}/production-enrollments/{operationId}/cancel": { + "post": { + "operationId": "cancelProductionEnrollment", "tags": [ - "events" + "projects" ], - "summary": "List project events", - "description": "Lists event metadata owned by the exact project in the route.\n\nRequires `events:read`. Project tokens only access resources in their own project.", + "summary": "Cancel production enrollment", + "description": "Requests cancellation of an active production-enrollment operation. Provisioned resources are compensated before cancellation completes.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "events:read", + "x-required-scope": "sessions:manage", "security": [ { "ApiKeyAuth": [] @@ -9125,29 +9581,112 @@ } }, { - "name": "type", - "in": "query", - "required": false, + "name": "operationId", + "in": "path", + "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProductionEnrollmentCommandResult" + } + }, + "required": [ + "data" + ] + } + } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/platform/webhooks": { + "get": { + "operationId": "listOrganizationWebhooks", + "tags": [ + "webhooks" + ], + "summary": "List team webhooks", + "description": "Lists only team-owned webhook endpoints. Project-owned endpoints are excluded.\n\nRequires a team API key with `webhooks:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "webhooks:read", + "security": [ + { + "ApiKeyAuth": [] + }, { - "name": "since", + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "eventType", "in": "query", "required": false, "schema": { "type": "string", - "format": "date-time" + "minLength": 1, + "maxLength": 128 } }, { - "name": "until", + "name": "enabled", "in": "query", "required": false, "schema": { - "type": "string", - "format": "date-time" + "type": "boolean" } }, { @@ -9196,7 +9735,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/ProjectEvent" + "$ref": "#/components/schemas/OrganizationWebhook" } }, "page": { @@ -9219,25 +9758,20 @@ }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/projects/{projectId}/events/{eventId}": { - "get": { - "operationId": "getProjectEvent", + }, + "post": { + "operationId": "createOrganizationWebhook", "tags": [ - "events" + "webhooks" ], - "summary": "Get a project event", - "description": "Returns one event owned by the exact project and includes exact payload bytes only when requested and available.\n\nRequires `events:read`. Project tokens only access resources in their own project.", + "summary": "Create an team webhook", + "description": "Creates an team-owned webhook and returns its generated signing secret once.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "events:read", + "x-required-scope": "webhooks:manage", "security": [ { "ApiKeyAuth": [] @@ -9248,33 +9782,26 @@ ], "parameters": [ { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "eventId", - "in": "path", + "name": "Idempotency-Key", + "in": "header", "required": true, "schema": { "type": "string", - "format": "uuid" - } - }, - { - "name": "includePayload", - "in": "query", - "required": false, - "schema": { - "type": "boolean", - "default": false + "minLength": 1, + "maxLength": 255 } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrganizationWebhookInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -9299,7 +9826,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectEvent" + "$ref": "#/components/schemas/OrganizationWebhookCreationReceipt" } }, "required": [ @@ -9318,24 +9845,24 @@ "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/projects/{projectId}/events/{eventId}/replays": { - "post": { - "operationId": "replayProjectEvent", + "/platform/webhooks/{webhookId}": { + "get": { + "operationId": "getOrganizationWebhook", "tags": [ - "events" + "webhooks" ], - "summary": "Replay a project event", - "description": "Creates a new logical delivery for a retained event owned by the exact project.\n\nRequires `events:replay`. Project tokens only access resources in their own project.", + "summary": "Get an team webhook", + "description": "Returns one team-owned webhook without secret values.\n\nRequires a team API key with `webhooks:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "events:replay", + "x-required-scope": "webhooks:read", "security": [ { "ApiKeyAuth": [] @@ -9346,44 +9873,15 @@ ], "parameters": [ { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "eventId", + "name": "webhookId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ReplayEventInput" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -9408,7 +9906,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectEventReplayReceipt" + "$ref": "#/components/schemas/OrganizationWebhook" } }, "required": [ @@ -9429,28 +9927,20 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" - }, - "422": { - "$ref": "#/components/responses/Unprocessable" } } - } - }, - "/platform/webhook-deliveries": { - "get": { - "operationId": "listOrganizationWebhookDeliveries", + }, + "patch": { + "operationId": "updateOrganizationWebhook", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "List organization webhook deliveries", - "description": "Lists logical deliveries for organization-owned webhooks. Project-owned deliveries are excluded.\n\nRequires a team API key with `webhook-deliveries:read`. Project tokens are not accepted.", + "summary": "Update an team webhook", + "description": "Updates settings without rotating the signing secret.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:read", + "x-required-scope": "webhooks:manage", "security": [ { "ApiKeyAuth": [] @@ -9462,75 +9952,34 @@ "parameters": [ { "name": "webhookId", - "in": "query", - "required": false, + "in": "path", + "required": true, "schema": { "type": "string", "format": "uuid" } }, { - "name": "eventId", - "in": "query", - "required": false, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "status", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "pending", - "delivering", - "retrying", - "succeeded", - "failed" - ] - } - }, - { - "name": "since", - "in": "query", - "required": false, - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "until", - "in": "query", - "required": false, + "name": "Idempotency-Key", + "in": "header", + "required": true, "schema": { "type": "string", - "format": "date-time" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" + "minLength": 1, + "maxLength": 255 } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateOrganizationWebhookInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -9555,18 +10004,11 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrganizationWebhookDelivery" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/OrganizationWebhookMutationReceipt" } }, "required": [ - "data", - "page" + "data" ] } } @@ -9580,22 +10022,26 @@ }, "403": { "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } - } - }, - "/platform/webhook-deliveries/{deliveryId}": { - "get": { - "operationId": "getOrganizationWebhookDelivery", + }, + "delete": { + "operationId": "deleteOrganizationWebhook", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "Get an organization webhook delivery", - "description": "Returns one logical organization webhook delivery.\n\nRequires a team API key with `webhook-deliveries:read`. Project tokens are not accepted.", + "summary": "Delete an team webhook", + "description": "Deletes one team-owned webhook.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:read", + "x-required-scope": "webhooks:manage", "security": [ { "ApiKeyAuth": [] @@ -9606,13 +10052,23 @@ ], "parameters": [ { - "name": "deliveryId", + "name": "webhookId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + } } ], "responses": { @@ -9639,7 +10095,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/OrganizationWebhookDelivery" + "$ref": "#/components/schemas/OrganizationWebhookDeletionReceipt" } }, "required": [ @@ -9660,22 +10116,25 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/webhook-deliveries/{deliveryId}/retry": { + "/platform/webhooks/{webhookId}/secret-rotations": { "post": { - "operationId": "retryOrganizationWebhookDelivery", + "operationId": "rotateOrganizationWebhookSecret", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "Retry an organization webhook delivery", - "description": "Appends a new immutable attempt to the same logical delivery.\n\nRequires a team API key with `webhook-deliveries:retry`. Project tokens are not accepted.", + "summary": "Rotate an team webhook secret", + "description": "Rotates the signing secret and returns the new secret once.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:retry", + "x-required-scope": "webhooks:manage", "security": [ { "ApiKeyAuth": [] @@ -9686,7 +10145,7 @@ ], "parameters": [ { - "name": "deliveryId", + "name": "webhookId", "in": "path", "required": true, "schema": { @@ -9705,6 +10164,16 @@ } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RotateWebhookSecretInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -9729,7 +10198,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/OrganizationWebhookDeliveryRetryReceipt" + "$ref": "#/components/schemas/OrganizationWebhookSecretRotationReceipt" } }, "required": [ @@ -9753,25 +10222,22 @@ }, "409": { "$ref": "#/components/responses/Conflict" - }, - "422": { - "$ref": "#/components/responses/Unprocessable" } } } }, - "/platform/webhook-deliveries/{deliveryId}/attempts": { - "get": { - "operationId": "listOrganizationWebhookDeliveryAttempts", + "/platform/webhooks/{webhookId}/tests": { + "post": { + "operationId": "testOrganizationWebhook", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "List organization delivery attempts", - "description": "Lists immutable network attempts for one organization webhook delivery.\n\nRequires a team API key with `webhook-deliveries:read`. Project tokens are not accepted.", + "summary": "Send an team webhook test", + "description": "Creates a synthetic team event and a traceable webhook delivery.\n\nRequires a team API key with `webhooks:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:read", + "x-required-scope": "webhooks:manage", "security": [ { "ApiKeyAuth": [] @@ -9782,7 +10248,7 @@ ], "parameters": [ { - "name": "deliveryId", + "name": "webhookId", "in": "path", "required": true, "schema": { @@ -9791,25 +10257,26 @@ } }, { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, + "name": "Idempotency-Key", + "in": "header", + "required": true, "schema": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestWebhookInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -9834,18 +10301,11 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrganizationWebhookDeliveryAttempt" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/OrganizationWebhookTestReceipt" } }, "required": [ - "data", - "page" + "data" ] } } @@ -9862,22 +10322,25 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/webhook-deliveries/{deliveryId}/attempts/{attemptId}": { + "/platform/projects/{projectId}/webhooks": { "get": { - "operationId": "getOrganizationWebhookDeliveryAttempt", + "operationId": "listProjectWebhooks", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "Get organization delivery attempt", - "description": "Returns one immutable network attempt.\n\nRequires a team API key with `webhook-deliveries:read`. Project tokens are not accepted.", + "summary": "List project webhooks", + "description": "Lists webhooks owned by the exact project in the route.\n\nRequires `webhooks:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:read", + "x-required-scope": "webhooks:read", "security": [ { "ApiKeyAuth": [] @@ -9888,7 +10351,7 @@ ], "parameters": [ { - "name": "deliveryId", + "name": "projectId", "in": "path", "required": true, "schema": { @@ -9897,12 +10360,40 @@ } }, { - "name": "attemptId", - "in": "path", - "required": true, + "name": "eventType", + "in": "query", + "required": false, "schema": { "type": "string", - "format": "uuid" + "minLength": 1, + "maxLength": 128 + } + }, + { + "name": "enabled", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" } } ], @@ -9930,13 +10421,20 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/OrganizationWebhookDeliveryAttempt" - } - }, - "required": [ - "data" - ] - } + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectWebhook" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" + } + }, + "required": [ + "data", + "page" + ] + } } } }, @@ -9953,20 +10451,18 @@ "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/projects/{projectId}/webhook-deliveries": { - "get": { - "operationId": "listProjectWebhookDeliveries", + }, + "post": { + "operationId": "createProjectWebhook", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "List project webhook deliveries", - "description": "Lists logical webhook deliveries owned by the exact project in the route.\n\nRequires `webhook-deliveries:read`. Project tokens only access resources in their own project.", + "summary": "Create a project webhook", + "description": "Creates a project-owned webhook and returns its generated signing secret once.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:read", + "x-required-scope": "webhooks:manage", "security": [ { "ApiKeyAuth": [] @@ -9986,76 +10482,26 @@ } }, { - "name": "webhookId", - "in": "query", - "required": false, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "eventId", - "in": "query", - "required": false, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "status", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "pending", - "delivering", - "retrying", - "succeeded", - "failed" - ] - } - }, - { - "name": "since", - "in": "query", - "required": false, - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "until", - "in": "query", - "required": false, + "name": "Idempotency-Key", + "in": "header", + "required": true, "schema": { "type": "string", - "format": "date-time" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" + "minLength": 1, + "maxLength": 255 } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProjectWebhookInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -10080,18 +10526,11 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ProjectWebhookDelivery" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/ProjectWebhookCreationReceipt" } }, "required": [ - "data", - "page" + "data" ] } } @@ -10108,22 +10547,25 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/projects/{projectId}/webhook-deliveries/{deliveryId}": { + "/platform/projects/{projectId}/webhooks/{webhookId}": { "get": { - "operationId": "getProjectWebhookDelivery", + "operationId": "getProjectWebhook", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "Get a project webhook delivery", - "description": "Returns one logical webhook delivery owned by the exact project.\n\nRequires `webhook-deliveries:read`. Project tokens only access resources in their own project.", + "summary": "Get a project webhook", + "description": "Returns one webhook owned by the exact project without secret values.\n\nRequires `webhooks:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:read", + "x-required-scope": "webhooks:read", "security": [ { "ApiKeyAuth": [] @@ -10143,7 +10585,7 @@ } }, { - "name": "deliveryId", + "name": "webhookId", "in": "path", "required": true, "schema": { @@ -10176,7 +10618,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectWebhookDelivery" + "$ref": "#/components/schemas/ProjectWebhook" } }, "required": [ @@ -10199,20 +10641,18 @@ "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/projects/{projectId}/webhook-deliveries/{deliveryId}/retry": { - "post": { - "operationId": "retryProjectWebhookDelivery", + }, + "patch": { + "operationId": "updateProjectWebhook", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "Retry a project webhook delivery", - "description": "Appends a new immutable attempt to the same logical delivery owned by the exact project.\n\nRequires `webhook-deliveries:retry`. Project tokens only access resources in their own project.", + "summary": "Update a project webhook", + "description": "Updates settings without rotating the signing secret.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:retry", + "x-required-scope": "webhooks:manage", "security": [ { "ApiKeyAuth": [] @@ -10232,7 +10672,7 @@ } }, { - "name": "deliveryId", + "name": "webhookId", "in": "path", "required": true, "schema": { @@ -10251,6 +10691,16 @@ } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectWebhookInput" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -10275,7 +10725,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectWebhookDeliveryRetryReceipt" + "$ref": "#/components/schemas/ProjectWebhookMutationReceipt" } }, "required": [ @@ -10299,25 +10749,20 @@ }, "409": { "$ref": "#/components/responses/Conflict" - }, - "422": { - "$ref": "#/components/responses/Unprocessable" } } - } - }, - "/platform/projects/{projectId}/webhook-deliveries/{deliveryId}/attempts": { - "get": { - "operationId": "listProjectWebhookDeliveryAttempts", + }, + "delete": { + "operationId": "deleteProjectWebhook", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "List project webhook delivery attempts", - "description": "Lists immutable network attempts for one project webhook delivery.\n\nRequires `webhook-deliveries:read`. Project tokens only access resources in their own project.", + "summary": "Delete a project webhook", + "description": "Deletes one webhook owned by the exact project.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:read", + "x-required-scope": "webhooks:manage", "security": [ { "ApiKeyAuth": [] @@ -10337,7 +10782,7 @@ } }, { - "name": "deliveryId", + "name": "webhookId", "in": "path", "required": true, "schema": { @@ -10346,22 +10791,13 @@ } }, { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, + "name": "Idempotency-Key", + "in": "header", + "required": true, "schema": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], @@ -10389,18 +10825,11 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ProjectWebhookDeliveryAttempt" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" + "$ref": "#/components/schemas/ProjectWebhookDeletionReceipt" } }, "required": [ - "data", - "page" + "data" ] } } @@ -10417,22 +10846,25 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/projects/{projectId}/webhook-deliveries/{deliveryId}/attempts/{attemptId}": { - "get": { - "operationId": "getProjectWebhookDeliveryAttempt", + "/platform/projects/{projectId}/webhooks/{webhookId}/secret-rotations": { + "post": { + "operationId": "rotateProjectWebhookSecret", "tags": [ - "webhook deliveries" + "webhooks" ], - "summary": "Get a project webhook delivery attempt", - "description": "Returns one immutable network attempt owned by the exact project.\n\nRequires `webhook-deliveries:read`. Project tokens only access resources in their own project.", + "summary": "Rotate a project webhook secret", + "description": "Rotates the signing secret and returns the new secret once.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "webhook-deliveries:read", + "x-required-scope": "webhooks:manage", "security": [ { "ApiKeyAuth": [] @@ -10452,7 +10884,7 @@ } }, { - "name": "deliveryId", + "name": "webhookId", "in": "path", "required": true, "schema": { @@ -10461,22 +10893,33 @@ } }, { - "name": "attemptId", - "in": "path", + "name": "Idempotency-Key", + "in": "header", "required": true, "schema": { "type": "string", - "format": "uuid" + "minLength": 1, + "maxLength": 255 } } ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RotateWebhookSecretInput" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" }, "description": "Stable request correlation identifier." }, @@ -10494,7 +10937,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectWebhookDeliveryAttempt" + "$ref": "#/components/schemas/ProjectWebhookSecretRotationReceipt" } }, "required": [ @@ -10515,75 +10958,151 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/console/operations": { - "get": { - "operationId": "listOrganizationOperations", + "/platform/projects/{projectId}/webhooks/{webhookId}/tests": { + "post": { + "operationId": "testProjectWebhook", "tags": [ - "operations" + "webhooks" ], - "summary": "List organization operations", - "description": "Lists only organization-owned asynchronous operations. Project-owned operations are excluded.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", + "summary": "Send a project webhook test", + "description": "Creates a synthetic project event and a traceable webhook delivery. An optional complete native envelope is accepted only with an exact HMS-enabled project session.\n\nRequires `webhooks:manage`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "webhooks:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "status", - "in": "query", - "required": false, + "name": "projectId", + "in": "path", + "required": true, "schema": { "type": "string", - "enum": [ - "pending", - "running", - "action_required", - "cancelling", - "succeeded", - "failed", - "cancelled" - ] + "format": "uuid" } }, { - "name": "kind", - "in": "query", - "required": false, + "name": "webhookId", + "in": "path", + "required": true, "schema": { "type": "string", - "enum": [ - "auth_projection_repair", - "session_lifecycle", - "auth_session_purge", - "label_projection_purge", - "billing_reconciliation", - "auto_top_up", - "campaign", - "production_enrollment", - "retention_sweep", - "webhook_redrive" - ] + "format": "uuid" } }, { - "name": "resourceType", - "in": "query", - "required": false, + "name": "Idempotency-Key", + "in": "header", + "required": true, "schema": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 255 + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestWebhookInput" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectWebhookTestReceipt" + } + }, + "required": [ + "data" + ] + } + } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "422": { + "$ref": "#/components/responses/Unprocessable" + } + } + } + }, + "/platform/events": { + "get": { + "operationId": "listOrganizationEvents", + "tags": [ + "events" + ], + "summary": "List team events", + "description": "Lists team-owned event metadata. Project-owned events are excluded.\n\nRequires a team API key with `events:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "events:read", + "security": [ + { + "ApiKeyAuth": [] + }, { - "name": "resourceId", + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "type", "in": "query", "required": false, "schema": { @@ -10654,7 +11173,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/OrganizationOperation" + "$ref": "#/components/schemas/OrganizationEvent" } }, "page": { @@ -10681,32 +11200,44 @@ } } }, - "/console/operations/{operationId}": { + "/platform/events/{eventId}": { "get": { - "operationId": "getOrganizationOperation", + "operationId": "getOrganizationEvent", "tags": [ - "operations" + "events" ], - "summary": "Get an organization operation", - "description": "Returns one organization-owned asynchronous operation.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", + "summary": "Get an team event", + "description": "Returns one team-owned event and includes exact payload bytes only when requested and available.\n\nRequires a team API key with `events:read`. Project tokens are not accepted.", + "x-audience": "public", "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", + "x-excluded-from-public": false, + "x-required-scope": "events:read", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "operationId", + "name": "eventId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } + }, + { + "name": "includePayload", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false + } } ], "responses": { @@ -10733,7 +11264,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/OrganizationOperation" + "$ref": "#/components/schemas/OrganizationEvent" } }, "required": [ @@ -10758,26 +11289,29 @@ } } }, - "/console/operations/{operationId}/transitions": { - "get": { - "operationId": "listOrganizationOperationTransitions", + "/platform/events/{eventId}/replays": { + "post": { + "operationId": "replayOrganizationEvent", "tags": [ - "operations" + "events" ], - "summary": "List organization operation transitions", - "description": "Lists immutable transitions for one organization-owned operation in ascending sequence.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", + "summary": "Replay an team event", + "description": "Creates a new logical delivery for a retained team event.\n\nRequires a team API key with `events:replay`. Project tokens are not accepted.", + "x-audience": "public", "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", + "x-excluded-from-public": false, + "x-required-scope": "events:replay", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "operationId", + "name": "eventId", "in": "path", "required": true, "schema": { @@ -10786,129 +11320,26 @@ } }, { - "name": "afterSequence", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "format": "int64", - "minimum": 0 - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, + "name": "Idempotency-Key", + "in": "header", + "required": true, "schema": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OperationTransition" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" - } - }, - "required": [ - "data", - "page" - ] - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplayEventInput" } } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, - "/console/operations/{operationId}/cancel": { - "post": { - "operationId": "cancelOrganizationOperation", - "tags": [ - "operations" - ], - "summary": "Cancel an organization operation", - "description": "Requests cancellation after transactionally rechecking that the operation is cancellable.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "operationId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } } - ], + }, "responses": { "200": { "description": "Success", @@ -10933,7 +11364,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/OrganizationOperationCancellationReceipt" + "$ref": "#/components/schemas/OrganizationEventReplayReceipt" } }, "required": [ @@ -10956,24 +11387,30 @@ "$ref": "#/components/responses/NotFound" }, "409": { - "$ref": "#/components/responses/OperationCancellationConflict" + "$ref": "#/components/responses/Conflict" + }, + "422": { + "$ref": "#/components/responses/Unprocessable" } } } }, - "/console/projects/{projectId}/operations": { + "/platform/projects/{projectId}/events": { "get": { - "operationId": "listProjectOperations", + "operationId": "listProjectEvents", "tags": [ - "operations" + "events" ], - "summary": "List project operations", - "description": "Lists asynchronous operations owned by the exact project in the route.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", + "summary": "List project events", + "description": "Lists event metadata owned by the exact project in the route.\n\nRequires `events:read`. Project tokens only access resources in their own project.", + "x-audience": "public", "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", + "x-excluded-from-public": false, + "x-required-scope": "events:read", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } @@ -10989,52 +11426,7 @@ } }, { - "name": "status", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "pending", - "running", - "action_required", - "cancelling", - "succeeded", - "failed", - "cancelled" - ] - } - }, - { - "name": "kind", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "auth_projection_repair", - "session_lifecycle", - "auth_session_purge", - "label_projection_purge", - "billing_reconciliation", - "auto_top_up", - "campaign", - "production_enrollment", - "retention_sweep", - "webhook_redrive" - ] - } - }, - { - "name": "resourceType", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "resourceId", + "name": "type", "in": "query", "required": false, "schema": { @@ -11105,7 +11497,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/ProjectOperation" + "$ref": "#/components/schemas/ProjectEvent" } }, "page": { @@ -11135,19 +11527,22 @@ } } }, - "/console/projects/{projectId}/operations/{operationId}": { + "/platform/projects/{projectId}/events/{eventId}": { "get": { - "operationId": "getProjectOperation", + "operationId": "getProjectEvent", "tags": [ - "operations" + "events" ], - "summary": "Get a project operation", - "description": "Returns one asynchronous operation owned by the exact project in the route.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", + "summary": "Get a project event", + "description": "Returns one event owned by the exact project and includes exact payload bytes only when requested and available.\n\nRequires `events:read`. Project tokens only access resources in their own project.", + "x-audience": "public", "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", + "x-excluded-from-public": false, + "x-required-scope": "events:read", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } @@ -11163,13 +11558,22 @@ } }, { - "name": "operationId", + "name": "eventId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } + }, + { + "name": "includePayload", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false + } } ], "responses": { @@ -11196,7 +11600,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectOperation" + "$ref": "#/components/schemas/ProjectEvent" } }, "required": [ @@ -11221,19 +11625,22 @@ } } }, - "/console/projects/{projectId}/operations/{operationId}/transitions": { - "get": { - "operationId": "listProjectOperationTransitions", + "/platform/projects/{projectId}/events/{eventId}/replays": { + "post": { + "operationId": "replayProjectEvent", "tags": [ - "operations" + "events" ], - "summary": "List project operation transitions", - "description": "Lists immutable transitions for one project-owned operation in ascending sequence.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", + "summary": "Replay a project event", + "description": "Creates a new logical delivery for a retained event owned by the exact project.\n\nRequires `events:replay`. Project tokens only access resources in their own project.", + "x-audience": "public", "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", + "x-excluded-from-public": false, + "x-required-scope": "events:replay", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } @@ -11249,7 +11656,7 @@ } }, { - "name": "operationId", + "name": "eventId", "in": "path", "required": true, "schema": { @@ -11258,138 +11665,26 @@ } }, { - "name": "afterSequence", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "format": "int64", - "minimum": 0 - } - }, - { - "name": "limit", - "in": "query", - "required": false, + "name": "Idempotency-Key", + "in": "header", + "required": true, "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OperationTransition" - } - }, - "page": { - "$ref": "#/components/schemas/CursorPageMetadata" - } - }, - "required": [ - "data", - "page" - ] - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplayEventInput" } } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, - "/console/projects/{projectId}/operations/{operationId}/cancel": { - "post": { - "operationId": "cancelProjectOperation", - "tags": [ - "operations" - ], - "summary": "Cancel a project operation", - "description": "Requests cancellation for an operation in the exact project after transactionally rechecking that it is cancellable.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "projectId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "operationId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } } - ], + }, "responses": { "200": { "description": "Success", @@ -11414,7 +11709,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/ProjectOperationCancellationReceipt" + "$ref": "#/components/schemas/ProjectEventReplayReceipt" } }, "required": [ @@ -11437,23 +11732,26 @@ "$ref": "#/components/responses/NotFound" }, "409": { - "$ref": "#/components/responses/OperationCancellationConflict" + "$ref": "#/components/responses/Conflict" + }, + "422": { + "$ref": "#/components/responses/Unprocessable" } } } }, - "/platform/customers": { + "/platform/webhook-deliveries": { "get": { - "operationId": "listCustomers", + "operationId": "listOrganizationWebhookDeliveries", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "List Customers", - "description": "Lists masked Customer summaries in the exact authenticated project with cursor pagination and filters.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", + "summary": "List team webhook deliveries", + "description": "Lists logical deliveries for team-owned webhooks. Project-owned deliveries are excluded.\n\nRequires a team API key with `webhook-deliveries:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "customers:read", + "x-required-scope": "webhook-deliveries:read", "security": [ { "ApiKeyAuth": [] @@ -11464,76 +11762,73 @@ ], "parameters": [ { - "name": "projectId", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "cursor", + "name": "webhookId", "in": "query", "required": false, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } }, { - "name": "limit", + "name": "eventId", "in": "query", "required": false, "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 + "type": "string", + "format": "uuid" } }, { - "name": "search", + "name": "status", "in": "query", "required": false, "schema": { - "type": "string" + "type": "string", + "enum": [ + "pending", + "delivering", + "retrying", + "succeeded", + "failed" + ] } }, { - "name": "status", + "name": "since", "in": "query", "required": false, "schema": { "type": "string", - "enum": [ - "active", - "archiving", - "archived", - "all" - ] + "format": "date-time" } }, { - "name": "isDefault", + "name": "until", "in": "query", "required": false, "schema": { - "type": "boolean" + "type": "string", + "format": "date-time" } }, { - "name": "hasNumbers", + "name": "limit", "in": "query", "required": false, "schema": { - "type": "boolean" + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 } }, { - "name": "needsAttention", + "name": "cursor", "in": "query", "required": false, "schema": { - "type": "boolean" + "type": "string" } } ], @@ -11563,7 +11858,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/CustomerSummary" + "$ref": "#/components/schemas/OrganizationWebhookDelivery" } }, "page": { @@ -11586,23 +11881,22 @@ }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" } } - }, - "post": { - "operationId": "createCustomer", + } + }, + "/platform/webhook-deliveries/{deliveryId}": { + "get": { + "operationId": "getOrganizationWebhookDelivery", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "Create a Customer", - "description": "Creates a Customer with optional name and caller-defined external identifier.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", + "summary": "Get an team webhook delivery", + "description": "Returns one logical team webhook delivery.\n\nRequires a team API key with `webhook-deliveries:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "customers:manage", + "x-required-scope": "webhook-deliveries:read", "security": [ { "ApiKeyAuth": [] @@ -11613,26 +11907,15 @@ ], "parameters": [ { - "name": "Idempotency-Key", - "in": "header", + "name": "deliveryId", + "in": "path", "required": true, "schema": { "type": "string", - "minLength": 1, - "maxLength": 255 + "format": "uuid" } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateCustomerRequest" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -11657,7 +11940,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/Customer" + "$ref": "#/components/schemas/OrganizationWebhookDelivery" } }, "required": [ @@ -11678,25 +11961,22 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } } }, - "/platform/customers/{customerId}": { - "get": { - "operationId": "getCustomer", + "/platform/webhook-deliveries/{deliveryId}/retry": { + "post": { + "operationId": "retryOrganizationWebhookDelivery", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "Get a Customer", - "description": "Returns one Customer from the exact authenticated project.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", + "summary": "Retry an team webhook delivery", + "description": "Appends a new immutable attempt to the same logical delivery.\n\nRequires a team API key with `webhook-deliveries:retry`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "customers:read", + "x-required-scope": "webhook-deliveries:retry", "security": [ { "ApiKeyAuth": [] @@ -11707,7 +11987,7 @@ ], "parameters": [ { - "name": "customerId", + "name": "deliveryId", "in": "path", "required": true, "schema": { @@ -11716,11 +11996,13 @@ } }, { - "name": "projectId", - "in": "query", + "name": "Idempotency-Key", + "in": "header", "required": true, "schema": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], @@ -11748,7 +12030,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/Customer" + "$ref": "#/components/schemas/OrganizationWebhookDeliveryRetryReceipt" } }, "required": [ @@ -11769,20 +12051,28 @@ }, "404": { "$ref": "#/components/responses/NotFound" - } - } - }, - "patch": { - "operationId": "updateCustomer", + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "422": { + "$ref": "#/components/responses/Unprocessable" + } + } + } + }, + "/platform/webhook-deliveries/{deliveryId}/attempts": { + "get": { + "operationId": "listOrganizationWebhookDeliveryAttempts", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "Update a Customer", - "description": "Updates supplied Customer profile fields. Null clears an optional field.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", + "summary": "List team delivery attempts", + "description": "Lists immutable network attempts for one team webhook delivery.\n\nRequires a team API key with `webhook-deliveries:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "customers:manage", + "x-required-scope": "webhook-deliveries:read", "security": [ { "ApiKeyAuth": [] @@ -11793,25 +12083,34 @@ ], "parameters": [ { - "name": "customerId", + "name": "deliveryId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCustomerRequest" - } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -11836,11 +12135,18 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/Customer" + "type": "array", + "items": { + "$ref": "#/components/schemas/OrganizationWebhookDeliveryAttempt" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" } }, "required": [ - "data" + "data", + "page" ] } } @@ -11857,25 +12163,22 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } } }, - "/platform/customers/{customerId}/archive": { - "post": { - "operationId": "archiveCustomer", + "/platform/webhook-deliveries/{deliveryId}/attempts/{attemptId}": { + "get": { + "operationId": "getOrganizationWebhookDeliveryAttempt", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "Archive a Customer", - "description": "Archives a non-default Customer. The default Customer cannot be archived.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", + "summary": "Get team delivery attempt", + "description": "Returns one immutable network attempt.\n\nRequires a team API key with `webhook-deliveries:read`. Project tokens are not accepted.", "x-audience": "public", - "x-polymorfa-resource-boundary": "project", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "customers:manage", + "x-required-scope": "webhook-deliveries:read", "security": [ { "ApiKeyAuth": [] @@ -11886,7 +12189,7 @@ ], "parameters": [ { - "name": "customerId", + "name": "deliveryId", "in": "path", "required": true, "schema": { @@ -11895,26 +12198,15 @@ } }, { - "name": "Idempotency-Key", - "in": "header", + "name": "attemptId", + "in": "path", "required": true, "schema": { "type": "string", - "minLength": 1, - "maxLength": 255 + "format": "uuid" } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CustomerProjectRequest" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -11939,7 +12231,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/Customer" + "$ref": "#/components/schemas/OrganizationWebhookDeliveryAttempt" } }, "required": [ @@ -11960,25 +12252,22 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } } }, - "/platform/customers/{customerId}/restore": { - "post": { - "operationId": "restoreCustomer", + "/platform/projects/{projectId}/webhook-deliveries": { + "get": { + "operationId": "listProjectWebhookDeliveries", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "Restore a Customer", - "description": "Restores an archived Customer.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", + "summary": "List project webhook deliveries", + "description": "Lists logical webhook deliveries owned by the exact project in the route.\n\nRequires `webhook-deliveries:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "customers:manage", + "x-required-scope": "webhook-deliveries:read", "security": [ { "ApiKeyAuth": [] @@ -11989,7 +12278,7 @@ ], "parameters": [ { - "name": "customerId", + "name": "projectId", "in": "path", "required": true, "schema": { @@ -11998,26 +12287,76 @@ } }, { - "name": "Idempotency-Key", - "in": "header", - "required": true, + "name": "webhookId", + "in": "query", + "required": false, "schema": { "type": "string", - "minLength": 1, - "maxLength": 255 + "format": "uuid" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CustomerProjectRequest" - } + }, + { + "name": "eventId", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "pending", + "delivering", + "retrying", + "succeeded", + "failed" + ] + } + }, + { + "name": "since", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "until", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -12042,11 +12381,18 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/Customer" + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectWebhookDelivery" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" } }, "required": [ - "data" + "data", + "page" ] } } @@ -12063,25 +12409,22 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } } }, - "/platform/customers/{customerId}/numbers": { + "/platform/projects/{projectId}/webhook-deliveries/{deliveryId}": { "get": { - "operationId": "listCustomerNumbers", + "operationId": "getProjectWebhookDelivery", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "List Customer Numbers", - "description": "Lists Numbers assigned to one Customer in the exact project.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", + "summary": "Get a project webhook delivery", + "description": "Returns one logical webhook delivery owned by the exact project.\n\nRequires `webhook-deliveries:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "customers:read", + "x-required-scope": "webhook-deliveries:read", "security": [ { "ApiKeyAuth": [] @@ -12092,7 +12435,7 @@ ], "parameters": [ { - "name": "customerId", + "name": "projectId", "in": "path", "required": true, "schema": { @@ -12101,11 +12444,12 @@ } }, { - "name": "projectId", - "in": "query", + "name": "deliveryId", + "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], @@ -12133,10 +12477,7 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CustomerNumber" - } + "$ref": "#/components/schemas/ProjectWebhookDelivery" } }, "required": [ @@ -12161,18 +12502,18 @@ } } }, - "/platform/customers/{customerId}/events": { - "get": { - "operationId": "listCustomerEvents", + "/platform/projects/{projectId}/webhook-deliveries/{deliveryId}/retry": { + "post": { + "operationId": "retryProjectWebhookDelivery", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "List recent Customer events", - "description": "Lists the newest Customer lifecycle events with safe metadata. Actor identities, message content, phone values, invitation tokens, and URLs are excluded.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", + "summary": "Retry a project webhook delivery", + "description": "Appends a new immutable attempt to the same logical delivery owned by the exact project.\n\nRequires `webhook-deliveries:retry`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "customers:read", + "x-required-scope": "webhook-deliveries:retry", "security": [ { "ApiKeyAuth": [] @@ -12183,7 +12524,7 @@ ], "parameters": [ { - "name": "customerId", + "name": "projectId", "in": "path", "required": true, "schema": { @@ -12192,22 +12533,22 @@ } }, { - "name": "projectId", - "in": "query", + "name": "deliveryId", + "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } }, { - "name": "limit", - "in": "query", - "required": false, + "name": "Idempotency-Key", + "in": "header", + "required": true, "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], @@ -12235,10 +12576,7 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CustomerEvent" - } + "$ref": "#/components/schemas/ProjectWebhookDeliveryRetryReceipt" } }, "required": [ @@ -12259,22 +12597,28 @@ }, "404": { "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "422": { + "$ref": "#/components/responses/Unprocessable" } } } }, - "/platform/customers/{customerId}/pairing-links": { - "post": { - "operationId": "createCustomerPairingLink", + "/platform/projects/{projectId}/webhook-deliveries/{deliveryId}/attempts": { + "get": { + "operationId": "listProjectWebhookDeliveryAttempts", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "Create a Customer pairing link", - "description": "Creates a revocable Customer invitation. The raw URL is returned only in this response.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", + "summary": "List project webhook delivery attempts", + "description": "Lists immutable network attempts for one project webhook delivery.\n\nRequires `webhook-deliveries:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "customers:manage", + "x-required-scope": "webhook-deliveries:read", "security": [ { "ApiKeyAuth": [] @@ -12285,7 +12629,7 @@ ], "parameters": [ { - "name": "customerId", + "name": "projectId", "in": "path", "required": true, "schema": { @@ -12294,26 +12638,34 @@ } }, { - "name": "Idempotency-Key", - "in": "header", + "name": "deliveryId", + "in": "path", "required": true, "schema": { "type": "string", - "minLength": 1, - "maxLength": 255 + "format": "uuid" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateCustomerPairingLinkRequest" - } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -12338,11 +12690,18 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/CreatedCustomerPairingLink" + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectWebhookDeliveryAttempt" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" } }, "required": [ - "data" + "data", + "page" ] } } @@ -12359,23 +12718,22 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } - }, + } + }, + "/platform/projects/{projectId}/webhook-deliveries/{deliveryId}/attempts/{attemptId}": { "get": { - "operationId": "listCustomerPairingLinks", + "operationId": "getProjectWebhookDeliveryAttempt", "tags": [ - "customers" + "webhook deliveries" ], - "summary": "List Customer pairing links", - "description": "Lists safe pairing-link metadata. Raw invitation tokens and URLs are never returned.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", + "summary": "Get a project webhook delivery attempt", + "description": "Returns one immutable network attempt owned by the exact project.\n\nRequires `webhook-deliveries:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "customers:read", + "x-required-scope": "webhook-deliveries:read", "security": [ { "ApiKeyAuth": [] @@ -12386,7 +12744,7 @@ ], "parameters": [ { - "name": "customerId", + "name": "projectId", "in": "path", "required": true, "schema": { @@ -12395,11 +12753,21 @@ } }, { - "name": "projectId", - "in": "query", + "name": "deliveryId", + "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" + } + }, + { + "name": "attemptId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } } ], @@ -12427,10 +12795,7 @@ "additionalProperties": false, "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CustomerPairingLink" - } + "$ref": "#/components/schemas/ProjectWebhookDeliveryAttempt" } }, "required": [ @@ -12455,48 +12820,110 @@ } } }, - "/platform/customers/{customerId}/pairing-links/{pairingLinkId}": { - "delete": { - "operationId": "revokeCustomerPairingLink", + "/console/operations": { + "get": { + "operationId": "listOrganizationOperations", "tags": [ - "customers" + "operations" ], - "summary": "Revoke a Customer pairing link", - "description": "Revokes an open pairing link.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "customers:manage", + "summary": "List team operations", + "description": "Lists only team-owned asynchronous operations. Project-owned operations are excluded.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", "security": [ - { - "ApiKeyAuth": [] - }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "customerId", - "in": "path", - "required": true, + "name": "status", + "in": "query", + "required": false, "schema": { "type": "string", - "format": "uuid" + "enum": [ + "pending", + "running", + "action_required", + "cancelling", + "succeeded", + "failed", + "cancelled" + ] } }, { - "name": "pairingLinkId", - "in": "path", - "required": true, + "name": "kind", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "auth_projection_repair", + "session_lifecycle", + "auth_session_purge", + "label_projection_purge", + "billing_reconciliation", + "auto_top_up", + "campaign", + "production_enrollment", + "retention_sweep", + "webhook_redrive" + ] + } + }, + { + "name": "resourceType", + "in": "query", + "required": false, "schema": { "type": "string" } }, { - "name": "projectId", + "name": "resourceId", "in": "query", - "required": true, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "since", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "until", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, "schema": { "type": "string" } @@ -12526,11 +12953,18 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/CustomerPairingLink" + "type": "array", + "items": { + "$ref": "#/components/schemas/OrganizationOperation" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" } }, "required": [ - "data" + "data", + "page" ] } } @@ -12544,76 +12978,38 @@ }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } } }, - "/platform/customers/{customerId}/numbers/{sessionId}/transfer": { - "post": { - "operationId": "transferCustomerNumber", + "/console/operations/{operationId}": { + "get": { + "operationId": "getOrganizationOperation", "tags": [ - "customers" + "operations" ], - "summary": "Transfer a Customer Number", - "description": "Atomically transfers one eligible Number from the explicitly confirmed source Customer to the destination Customer.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "customers:manage", + "summary": "Get an team operation", + "description": "Returns one team-owned asynchronous operation.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", "security": [ - { - "ApiKeyAuth": [] - }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "customerId", + "name": "operationId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } - }, - { - "name": "sessionId", - "in": "path", - "required": true, - "description": "Database session UUID or stable session slug.", - "schema": { - "$ref": "#/components/schemas/SessionIdentifier" - } - }, - { - "name": "Idempotency-Key", - "in": "header", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 255 - } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TransferCustomerNumberRequest" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -12638,7 +13034,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/CustomerNumber" + "$ref": "#/components/schemas/OrganizationOperation" } }, "required": [ @@ -12659,31 +13055,65 @@ }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" } } } }, - "/platform/members": { + "/console/operations/{operationId}/transitions": { "get": { - "operationId": "listMembers", + "operationId": "listOrganizationOperationTransitions", "tags": [ - "members" + "operations" ], - "summary": "List members", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", - "x-audience": "public", + "summary": "List team operation transitions", + "description": "Lists immutable transitions for one team-owned operation in ascending sequence.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", "security": [ { - "ApiKeyAuth": [] + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "operationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } }, { - "ConsoleSession": [] + "name": "afterSequence", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -12706,53 +13136,80 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OperationTransition" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" + } + }, + "required": [ + "data", + "page" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/console/members/invites": { + "/console/operations/{operationId}/cancel": { "post": { - "operationId": "inviteMember", + "operationId": "cancelOrganizationOperation", "tags": [ - "members" + "operations" ], - "summary": "Invite member", - "description": "**EXCLUDED from the public API** (`console`). Membership administration requires an authenticated console member.", + "summary": "Cancel an team operation", + "description": "Requests cancellation after transactionally rechecking that the operation is cancellable.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", + "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": true, - "x-exclusion-reason": "Membership administration requires an authenticated console member.", - "x-dashboard-only": true, - "x-required-organization-role": "owner_or_admin", + "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", "security": [ { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "operationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 } } - }, + ], "responses": { "200": { "description": "Success", @@ -12773,37 +13230,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/OrganizationOperationCancellationReceipt" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/OperationCancellationConflict" } } } }, - "/console/members/{memberId}": { - "patch": { - "operationId": "updateMemberRole", + "/console/projects/{projectId}/operations": { + "get": { + "operationId": "listProjectOperations", "tags": [ - "members" + "operations" ], - "summary": "Update member role", - "description": "**EXCLUDED from the public API** (`console`). Membership administration requires an authenticated console member.", + "summary": "List project operations", + "description": "Lists asynchronous operations owned by the exact project in the route.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": true, - "x-exclusion-reason": "Membership administration requires an authenticated console member.", - "x-dashboard-only": true, - "x-required-organization-role": "owner_or_admin", + "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", "security": [ { "ConsoleSession": [] @@ -12811,26 +13281,106 @@ ], "parameters": [ { - "name": "memberId", + "name": "projectId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "pending", + "running", + "action_required", + "cancelling", + "succeeded", + "failed", + "cancelled" + ] } - } - }, - "responses": { + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "auth_projection_repair", + "session_lifecycle", + "auth_session_purge", + "label_projection_purge", + "billing_reconciliation", + "auto_top_up", + "campaign", + "production_enrollment", + "retention_sweep", + "webhook_redrive" + ] + } + }, + { + "name": "resourceType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "resourceId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "since", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "until", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { "200": { "description": "Success", "headers": { @@ -12850,35 +13400,54 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectOperation" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" + } + }, + "required": [ + "data", + "page" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - }, - "delete": { - "operationId": "deleteMember", + } + }, + "/console/projects/{projectId}/operations/{operationId}": { + "get": { + "operationId": "getProjectOperation", "tags": [ - "members" + "operations" ], - "summary": "Delete member", - "description": "**EXCLUDED from the public API** (`console`). Membership administration requires an authenticated console member.", + "summary": "Get a project operation", + "description": "Returns one asynchronous operation owned by the exact project in the route.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": true, - "x-exclusion-reason": "Membership administration requires an authenticated console member.", - "x-dashboard-only": true, - "x-required-organization-role": "owner_or_admin", + "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", "security": [ { "ConsoleSession": [] @@ -12886,11 +13455,21 @@ ], "parameters": [ { - "name": "memberId", + "name": "projectId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" + } + }, + { + "name": "operationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } } ], @@ -12914,41 +13493,99 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectOperation" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/platform/keys": { + "/console/projects/{projectId}/operations/{operationId}/transitions": { "get": { - "operationId": "listApiKeys", + "operationId": "listProjectOperationTransitions", "tags": [ - "keys" + "operations" ], - "summary": "List api keys", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "summary": "List project operation transitions", + "description": "Lists immutable transitions for one project-owned operation in ascending sequence.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", "security": [ { - "ApiKeyAuth": [] + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } }, { - "ConsoleSession": [] + "name": "operationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "afterSequence", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -12971,50 +13608,86 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OperationTransition" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" + } + }, + "required": [ + "data", + "page" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/platform/keys/{keyId}": { - "delete": { - "operationId": "deactivateApiKey", + "/console/projects/{projectId}/operations/{operationId}/cancel": { + "post": { + "operationId": "cancelProjectOperation", "tags": [ - "keys" + "operations" ], - "summary": "Deactivate api key", - "description": "Public Management API. Requires a team API key with `sessions:manage`. Project tokens are not accepted.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "summary": "Cancel a project operation", + "description": "Requests cancellation for an operation in the exact project after transactionally rechecking that it is cancellable.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard operation bookkeeping; unavailable to machine credentials.", "security": [ - { - "ApiKeyAuth": [] - }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "keyId", + "name": "projectId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" + } + }, + { + "name": "operationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], @@ -13038,35 +13711,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ProjectOperationCancellationReceipt" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/OperationCancellationConflict" } } } }, - "/platform/tokens": { + "/platform/customers": { "get": { - "operationId": "listPolymorfaTokens", + "operationId": "listCustomers", "tags": [ - "tokens" + "customers" ], - "summary": "List polymorfa tokens", - "description": "Public Management API. Requires `sessions:read`. Project tokens only access resources in their own project.", + "summary": "List Customers", + "description": "Lists masked Customer summaries in the exact authenticated project with cursor pagination and filters.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "customers:read", "security": [ { "ApiKeyAuth": [] @@ -13079,10 +13767,75 @@ { "name": "projectId", "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + }, + { + "name": "search", + "in": "query", "required": false, "schema": { "type": "string" } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "active", + "archiving", + "archived", + "all" + ] + } + }, + { + "name": "isDefault", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "hasNumbers", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "needsAttention", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } } ], "responses": { @@ -13105,35 +13858,52 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerSummary" + } + }, + "page": { + "$ref": "#/components/schemas/CursorPageMetadata" + } + }, + "required": [ + "data", + "page" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/audit": { - "get": { - "operationId": "listAuditLogs", + }, + "post": { + "operationId": "createCustomer", "tags": [ - "audit" + "customers" ], - "summary": "List audit logs", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "Create a Customer", + "description": "Creates a Customer with optional name and caller-defined external identifier.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "customers:manage", "security": [ { "ApiKeyAuth": [] @@ -13142,6 +13912,28 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCustomerRequest" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -13162,35 +13954,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/Customer" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/incidents": { + "/platform/customers/{customerId}": { "get": { - "operationId": "listSecurityIncidents", + "operationId": "getCustomer", "tags": [ - "incidents" + "customers" ], - "summary": "List security incidents", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "Get a Customer", + "description": "Returns one Customer from the exact authenticated project.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "customers:read", "security": [ { "ApiKeyAuth": [] @@ -13199,6 +14006,25 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "customerId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "projectId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -13219,35 +14045,45 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/Customer" + } + }, + "required": [ + "data" + ] + } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/platform/incidents/{incidentId}/acknowledge": { - "post": { - "operationId": "acknowledgeSecurityIncident", + }, + "patch": { + "operationId": "updateCustomer", "tags": [ - "incidents" + "customers" ], - "summary": "Acknowledge security incident", - "description": "Public Management API. Requires a team API key with `sessions:manage`. Project tokens are not accepted.", + "summary": "Update a Customer", + "description": "Updates supplied Customer profile fields. Null clears an optional field.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", + "x-required-scope": "customers:manage", "security": [ { "ApiKeyAuth": [] @@ -13258,21 +14094,21 @@ ], "parameters": [ { - "name": "incidentId", + "name": "customerId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/UpdateCustomerRequest" } } } @@ -13297,35 +14133,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/Customer" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/media/{mediaId}": { - "get": { - "operationId": "getMediaUrl", + "/platform/customers/{customerId}/archive": { + "post": { + "operationId": "archiveCustomer", "tags": [ - "media" + "customers" ], - "summary": "Get media url", - "description": "Public Management API. Requires a team API key with `media:read`. Project tokens are not accepted.", + "summary": "Archive a Customer", + "description": "Archives a non-default Customer. The default Customer cannot be archived.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "media:read", + "x-required-scope": "customers:manage", "security": [ { "ApiKeyAuth": [] @@ -13336,14 +14187,35 @@ ], "parameters": [ { - "name": "mediaId", + "name": "customerId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerProjectRequest" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -13364,33 +14236,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/Customer" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } - }, - "delete": { - "operationId": "deleteMedia", + } + }, + "/platform/customers/{customerId}/restore": { + "post": { + "operationId": "restoreCustomer", "tags": [ - "media" + "customers" ], - "summary": "Delete media", - "description": "Public Management API. Requires a team API key with `media:manage`. Project tokens are not accepted.", + "summary": "Restore a Customer", + "description": "Restores an archived Customer.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "media:manage", + "x-required-scope": "customers:manage", "security": [ { "ApiKeyAuth": [] @@ -13401,14 +14290,35 @@ ], "parameters": [ { - "name": "mediaId", + "name": "customerId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerProjectRequest" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -13429,35 +14339,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/Customer" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/platform/media/uploads": { - "post": { - "operationId": "createMediaUploadUrl", + "/platform/customers/{customerId}/numbers": { + "get": { + "operationId": "listCustomerNumbers", "tags": [ - "media" + "customers" ], - "summary": "Create media upload url", - "description": "Public Management API. Requires a team API key with `media:manage`. Project tokens are not accepted.", + "summary": "List Customer Numbers", + "description": "Lists Numbers assigned to one Customer in the exact project.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", + "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "media:manage", + "x-required-scope": "customers:read", "security": [ { "ApiKeyAuth": [] @@ -13466,17 +14391,25 @@ "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "customerId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "projectId", + "in": "query", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -13497,35 +14430,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerNumber" + } + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/platform/quicklink": { + "/platform/customers/{customerId}/events": { "get": { - "operationId": "getQuickLinkSettings", + "operationId": "listCustomerEvents", "tags": [ - "quicklink" + "customers" ], - "summary": "Read QuickLink settings", - "description": "Returns the saved QuickLink settings for the authenticated organization or one project, or null when nothing has been saved for that scope. There is no separate read permission: reading and updating both require `quicklink:manage`, because the settings include the redirect allowlist and branding that only a QuickLink manager should see.\n\nRequires `quicklink:manage`. Project tokens only access resources in their own project.", + "summary": "List recent Customer events", + "description": "Lists the newest Customer lifecycle events with safe metadata. Actor identities, message content, phone values, invitation tokens, and URLs are excluded.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "quicklink:manage", + "x-required-scope": "customers:read", "security": [ { "ApiKeyAuth": [] @@ -13535,13 +14483,33 @@ } ], "parameters": [ + { + "name": "customerId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, { "name": "projectId", "in": "query", - "required": false, + "required": true, "schema": { "type": "string" } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } } ], "responses": { @@ -13568,12 +14536,10 @@ "additionalProperties": false, "properties": { "data": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/QuickLinkSettings" - } - ] + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerEvent" + } } }, "required": [ @@ -13583,25 +14549,33 @@ } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" } } - }, - "put": { - "operationId": "updateQuickLinkSettings", + } + }, + "/platform/customers/{customerId}/pairing-links": { + "post": { + "operationId": "createCustomerPairingLink", "tags": [ - "quicklink" + "customers" ], - "summary": "Update QuickLink settings", - "description": "Updates the saved QuickLink settings for the authenticated organization or project and returns the saved record.\n\nRequires `quicklink:manage`. Project tokens only access resources in their own project.", + "summary": "Create a Customer pairing link", + "description": "Creates a revocable Customer invitation. The raw URL is returned only in this response.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "project", "x-excluded-from-public": false, - "x-required-scope": "quicklink:manage", + "x-required-scope": "customers:manage", "security": [ { "ApiKeyAuth": [] @@ -13610,12 +14584,33 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "customerId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + } + } + ], "requestBody": { "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateQuickLinkSettingsRequest" + "$ref": "#/components/schemas/CreateCustomerPairingLinkRequest" } } } @@ -13644,7 +14639,7 @@ "additionalProperties": false, "properties": { "data": { - "$ref": "#/components/schemas/QuickLinkSettings" + "$ref": "#/components/schemas/CreatedCustomerPairingLink" } }, "required": [ @@ -13662,38 +14657,53 @@ }, "403": { "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } - } - }, - "/console/keys": { - "post": { - "operationId": "mintForConsoleApiKey", + }, + "get": { + "operationId": "listCustomerPairingLinks", "tags": [ - "keys" + "customers" ], - "summary": "Mint for console api key", - "description": "**EXCLUDED from the public API** (`console`). Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only — keeps revocation meaningful.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only — keeps revocation meaningful.", + "summary": "List Customer pairing links", + "description": "Lists safe pairing-link metadata. Raw invitation tokens and URLs are never returned.\n\nRequires `customers:read`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "customers:read", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "customerId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "projectId", + "in": "query", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -13714,51 +14724,85 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerPairingLink" + } + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/console/billing/topup": { - "post": { - "operationId": "topUpBilling", + "/platform/customers/{customerId}/pairing-links/{pairingLinkId}": { + "delete": { + "operationId": "revokeCustomerPairingLink", "tags": [ - "billing" + "customers" ], - "summary": "Top up billing", - "description": "**EXCLUDED from the public API** (`console`). Starts a Stripe Checkout browser redirect; human session only. API clients use auto-top-up.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Starts a Stripe Checkout browser redirect; human session only. API clients use auto-top-up.", + "summary": "Revoke a Customer pairing link", + "description": "Revokes an open pairing link.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "customers:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "customerId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "pairingLinkId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "projectId", + "in": "query", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -13779,48 +14823,168 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/CustomerPairingLink" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" } } } }, - "/console/organizations/{slug}": { - "get": { - "operationId": "getOrganizationBySlug", + "/platform/customers/{customerId}/numbers/{sessionId}/transfer": { + "post": { + "operationId": "transferCustomerNumber", "tags": [ - "organizations" + "customers" ], - "summary": "Get organization by slug", - "description": "**EXCLUDED from the public API** (`console`). Console org-switcher lookup; a key is already bound to one org.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Console org-switcher lookup; a key is already bound to one org.", + "summary": "Transfer a Customer Number", + "description": "Atomically transfers one eligible Number from the explicitly confirmed source Customer to the destination Customer.\n\nRequires `customers:manage`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "customers:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "slug", + "name": "customerId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" + } + }, + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "Database session UUID or stable session slug.", + "schema": { + "$ref": "#/components/schemas/SessionIdentifier" + } + }, + { + "name": "Idempotency-Key", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransferCustomerNumberRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/CustomerNumber" + } + }, + "required": [ + "data" + ] + } + } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/platform/members": { + "get": { + "operationId": "listMembers", + "tags": [ + "members" + ], + "summary": "List members", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "sessions:read", + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ConsoleSession": [] } ], "responses": { @@ -13860,18 +15024,20 @@ } } }, - "/console/organization/avatar/uploads": { + "/console/members/invites": { "post": { - "operationId": "generateOrganizationAvatarUploadUrl", + "operationId": "inviteMember", "tags": [ - "organization" + "members" ], - "summary": "Generate organization avatar upload url", - "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", + "summary": "Invite member", + "description": "**EXCLUDED from the public API** (`console`). Membership administration requires an authenticated console member.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Browser logo-upload flow.", + "x-exclusion-reason": "Membership administration requires an authenticated console member.", + "x-dashboard-only": true, + "x-required-organization-role": "owner_or_admin", "security": [ { "ConsoleSession": [] @@ -13925,23 +15091,35 @@ } } }, - "/console/organization/avatar": { - "put": { - "operationId": "saveAvatarOrganization", + "/console/members/{memberId}": { + "patch": { + "operationId": "updateMemberRole", "tags": [ - "organization" + "members" ], - "summary": "Save avatar organization", - "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", + "summary": "Update member role", + "description": "**EXCLUDED from the public API** (`console`). Membership administration requires an authenticated console member.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Browser logo-upload flow.", + "x-exclusion-reason": "Membership administration requires an authenticated console member.", + "x-dashboard-only": true, + "x-required-organization-role": "owner_or_admin", "security": [ { "ConsoleSession": [] } ], + "parameters": [ + { + "name": "memberId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "requestBody": { "required": false, "content": { @@ -13990,21 +15168,33 @@ } }, "delete": { - "operationId": "removeAvatarOrganization", + "operationId": "deleteMember", "tags": [ - "organization" + "members" ], - "summary": "Remove avatar organization", - "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", + "summary": "Delete member", + "description": "**EXCLUDED from the public API** (`console`). Membership administration requires an authenticated console member.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Browser logo-upload flow.", + "x-exclusion-reason": "Membership administration requires an authenticated console member.", + "x-dashboard-only": true, + "x-required-organization-role": "owner_or_admin", "security": [ { "ConsoleSession": [] } ], + "parameters": [ + { + "name": "memberId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -14042,34 +15232,26 @@ } } }, - "/console/organizations/ensure": { - "post": { - "operationId": "ensureForCurrentUserOrganization", + "/platform/keys": { + "get": { + "operationId": "listApiKeys", "tags": [ - "organizations" + "keys" ], - "summary": "Ensure for current user organization", - "description": "**EXCLUDED from the public API** (`console`). First-sign-in bootstrap for the session user.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "First-sign-in bootstrap for the session user.", + "summary": "List api keys", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "sessions:read", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -14107,45 +15289,36 @@ } } }, - "/console/projects/{projectId}/icon": { - "post": { - "operationId": "generateProjectUploadUrl", + "/platform/keys/{keyId}": { + "delete": { + "operationId": "deactivateApiKey", "tags": [ - "projects" + "keys" ], - "summary": "Generate project upload url", - "description": "**EXCLUDED from the public API** (`console`). Browser icon-upload flow.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Browser icon-upload flow.", + "summary": "Deactivate api key", + "description": "Public Management API. Requires a team API key with `sessions:manage`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "sessions:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], "parameters": [ { - "name": "projectId", + "name": "keyId", "in": "path", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -14183,23 +15356,36 @@ } } }, - "/console/profile": { + "/platform/tokens": { "get": { - "operationId": "getUserProfile", + "operationId": "listPolymorfaTokens", "tags": [ - "profile" + "tokens" ], - "summary": "Get user profile", - "description": "**EXCLUDED from the public API** (`console`). 'Current user' only exists for a session; keys are org-scoped, not user-scoped.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "'Current user' only exists for a session; keys are org-scoped, not user-scoped.", + "summary": "List polymorfa tokens", + "description": "Public Management API. Requires `sessions:read`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "sessions:read", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], + "parameters": [ + { + "name": "projectId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -14235,34 +15421,85 @@ "$ref": "#/components/responses/NotImplemented" } } - }, - "patch": { - "operationId": "updateUserProfileField", + } + }, + "/platform/audit": { + "get": { + "operationId": "listAuditLogs", "tags": [ - "profile" + "audit" ], - "summary": "Update user profile field", - "description": "**EXCLUDED from the public API** (`console`). Personal profile edit for the signed-in user.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Personal profile edit for the signed-in user.", + "summary": "List audit logs", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "sessions:read", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataEnvelope" + } } } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "501": { + "$ref": "#/components/responses/NotImplemented" } - }, + } + } + }, + "/platform/incidents": { + "get": { + "operationId": "listSecurityIncidents", + "tags": [ + "incidents" + ], + "summary": "List security incidents", + "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "sessions:read", + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ConsoleSession": [] + } + ], "responses": { "200": { "description": "Success", @@ -14300,23 +15537,36 @@ } } }, - "/console/profile/ensure": { + "/platform/incidents/{incidentId}/acknowledge": { "post": { - "operationId": "ensureProfileUserProfile", + "operationId": "acknowledgeSecurityIncident", "tags": [ - "profile" + "incidents" ], - "summary": "Ensure profile user profile", - "description": "**EXCLUDED from the public API** (`console`). Sign-in bootstrap.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Sign-in bootstrap.", + "summary": "Acknowledge security incident", + "description": "Public Management API. Requires a team API key with `sessions:manage`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "sessions:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], + "parameters": [ + { + "name": "incidentId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "requestBody": { "required": false, "content": { @@ -14365,23 +15615,36 @@ } } }, - "/console/teams": { + "/platform/media/{mediaId}": { "get": { - "operationId": "listUserProfileTeams", + "operationId": "getMediaUrl", "tags": [ - "teams" + "media" ], - "summary": "List user profile teams", - "description": "**EXCLUDED from the public API** (`console`). Session user's team switcher.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Session user's team switcher.", + "summary": "Get media url", + "description": "Public Management API. Requires a team API key with `media:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "media:read", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -14417,36 +15680,36 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/console/profile/avatar/uploads": { - "post": { - "operationId": "generateUserProfileAvatarUploadUrl", + }, + "delete": { + "operationId": "deleteMedia", "tags": [ - "profile" + "media" ], - "summary": "Generate user profile avatar upload url", - "description": "**EXCLUDED from the public API** (`console`). Browser avatar-upload flow.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Browser avatar-upload flow.", + "summary": "Delete media", + "description": "Public Management API. Requires a team API key with `media:manage`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "media:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -14484,19 +15747,22 @@ } } }, - "/console/profile/avatar": { - "put": { - "operationId": "saveAvatarUserProfile", + "/platform/media/uploads": { + "post": { + "operationId": "createMediaUploadUrl", "tags": [ - "profile" + "media" ], - "summary": "Save avatar user profile", - "description": "**EXCLUDED from the public API** (`console`). Browser avatar-upload flow.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Browser avatar-upload flow.", + "summary": "Create media upload url", + "description": "Public Management API. Requires a team API key with `media:manage`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "media:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } @@ -14547,23 +15813,38 @@ "$ref": "#/components/responses/NotImplemented" } } - }, - "delete": { - "operationId": "removeAvatarUserProfile", + } + }, + "/platform/quicklink": { + "get": { + "operationId": "getQuickLinkSettings", "tags": [ - "profile" + "quicklink" ], - "summary": "Remove avatar user profile", - "description": "**EXCLUDED from the public API** (`console`). Personal profile edit.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Personal profile edit.", + "summary": "Read QuickLink settings", + "description": "Returns the saved QuickLink settings for the authenticated team or one project, or null when nothing has been saved for that scope. There is no separate read permission: reading and updating both require `quicklink:manage`, because the settings include the redirect allowlist and branding that only a QuickLink manager should see.\n\nRequires `quicklink:manage`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "quicklink:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], + "parameters": [ + { + "name": "projectId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "Success", @@ -14584,7 +15865,21 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/QuickLinkSettings" + } + ] + } + }, + "required": [ + "data" + ] } } } @@ -14594,30 +15889,38 @@ }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/console/auth/methods": { - "get": { - "operationId": "getUserProfileAuthMethods", + }, + "put": { + "operationId": "updateQuickLinkSettings", "tags": [ - "auth" + "quicklink" ], - "summary": "Get user profile auth methods", - "description": "**EXCLUDED from the public API** (`console`). Account-security introspection for the session user.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Account-security introspection for the session user.", + "summary": "Update QuickLink settings", + "description": "Updates the saved QuickLink settings for the authenticated team or project and returns the saved record.\n\nRequires `quicklink:manage`. Project tokens only access resources in their own project.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "project", + "x-excluded-from-public": false, + "x-required-scope": "quicklink:manage", "security": [ + { + "ApiKeyAuth": [] + }, { "ConsoleSession": [] } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateQuickLinkSettingsRequest" + } + } + } + }, "responses": { "200": { "description": "Success", @@ -14638,35 +15941,44 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/QuickLinkSettings" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" } } } }, - "/console/auth/totp": { + "/console/keys": { "post": { - "operationId": "enrollTotpUserProfile", + "operationId": "mintForConsoleApiKey", "tags": [ - "auth" + "keys" ], - "summary": "Enroll totp user profile", - "description": "**EXCLUDED from the public API** (`console`). MFA enrollment via API key = account-takeover vector.", + "summary": "Mint for console api key", + "description": "**EXCLUDED from the public API** (`console`). Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only — keeps revocation meaningful.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "MFA enrollment via API key = account-takeover vector.", + "x-exclusion-reason": "Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only — keeps revocation meaningful.", "security": [ { "ConsoleSession": [] @@ -14720,18 +16032,18 @@ } } }, - "/console/auth/totp/verify": { + "/console/billing/topup": { "post": { - "operationId": "verifyTotpUserProfile", + "operationId": "topUpBilling", "tags": [ - "auth" + "billing" ], - "summary": "Verify totp user profile", - "description": "**EXCLUDED from the public API** (`console`). Part of the human MFA step-up flow.", + "summary": "Top up billing", + "description": "**EXCLUDED from the public API** (`console`). Starts a native billing purchase in the console; human session only. API clients use auto-top-up.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Part of the human MFA step-up flow.", + "x-exclusion-reason": "Starts a native billing purchase in the console; human session only. API clients use auto-top-up.", "security": [ { "ConsoleSession": [] @@ -14785,18 +16097,18 @@ } } }, - "/console/auth/factors/{factorId}": { - "delete": { - "operationId": "deleteUserProfileAuthFactor", + "/console/organizations/{slug}": { + "get": { + "operationId": "getOrganizationBySlug", "tags": [ - "auth" + "organizations" ], - "summary": "Delete user profile auth factor", - "description": "**EXCLUDED from the public API** (`console`). Removing MFA via API key = account-takeover vector.", + "summary": "Get organization by slug", + "description": "**EXCLUDED from the public API** (`console`). Console org-switcher lookup; a key is already bound to one org.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Removing MFA via API key = account-takeover vector.", + "x-exclusion-reason": "Console org-switcher lookup; a key is already bound to one org.", "security": [ { "ConsoleSession": [] @@ -14804,7 +16116,7 @@ ], "parameters": [ { - "name": "factorId", + "name": "slug", "in": "path", "required": true, "schema": { @@ -14849,23 +16161,34 @@ } } }, - "/console/auth/providers": { - "get": { - "operationId": "getUserProfileProviderConnectionUrl", + "/console/organization/avatar/uploads": { + "post": { + "operationId": "generateOrganizationAvatarUploadUrl", "tags": [ - "auth" + "organization" ], - "summary": "Get user profile provider connection url", - "description": "**EXCLUDED from the public API** (`console`). Deferred Better Auth provider-link flow; dashboard identity only.", + "summary": "Generate organization avatar upload url", + "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Deferred Better Auth provider-link flow; dashboard identity only.", + "x-exclusion-reason": "Browser logo-upload flow.", "security": [ { "ConsoleSession": [] } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -14903,33 +16226,34 @@ } } }, - "/console/auth/providers/{provider}": { - "delete": { - "operationId": "disconnectProviderUserProfile", + "/console/organization/avatar": { + "put": { + "operationId": "saveAvatarOrganization", "tags": [ - "auth" + "organization" ], - "summary": "Disconnect provider user profile", - "description": "**EXCLUDED from the public API** (`console`). Deferred Better Auth provider-unlink flow; dashboard identity only.", + "summary": "Save avatar organization", + "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Deferred Better Auth provider-unlink flow; dashboard identity only.", + "x-exclusion-reason": "Browser logo-upload flow.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "provider", - "in": "path", - "required": true, - "schema": { - "type": "string" + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -14965,36 +16289,23 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/console/teams/leave": { - "post": { - "operationId": "leaveTeamUserProfile", + }, + "delete": { + "operationId": "removeAvatarOrganization", "tags": [ - "teams" + "organization" ], - "summary": "Leave team user profile", - "description": "**EXCLUDED from the public API** (`console`). Acts on the session user's own membership.", + "summary": "Remove avatar organization", + "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Acts on the session user's own membership.", + "x-exclusion-reason": "Browser logo-upload flow.", "security": [ { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -15032,33 +16343,23 @@ } } }, - "/console/teams/{teamId}/delete": { + "/console/organizations/ensure": { "post": { - "operationId": "deleteUserProfileTeam", + "operationId": "ensureForCurrentUserOrganization", "tags": [ - "teams" + "organizations" ], - "summary": "Delete user profile team", - "description": "**EXCLUDED from the public API** (`console`). Destructive, human-confirmed flow.", + "summary": "Ensure for current user organization", + "description": "**EXCLUDED from the public API** (`console`). First-sign-in bootstrap for the session user.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Destructive, human-confirmed flow.", + "x-exclusion-reason": "First-sign-in bootstrap for the session user.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "requestBody": { "required": false, "content": { @@ -15107,34 +16408,56 @@ } } }, - "/console/notifications": { - "get": { - "operationId": "listNotificationInbox", + "/console/projects/{projectId}/icon": { + "post": { + "operationId": "generateProjectUploadUrl", "tags": [ - "notifications" + "projects" ], - "summary": "List notification inbox", - "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", + "summary": "Generate project upload url", + "description": "**EXCLUDED from the public API** (`console`). Browser icon-upload flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "User-scoped console inbox.", + "x-exclusion-reason": "Browser icon-upload flow.", "security": [ { "ConsoleSession": [] } ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { "schema": { "type": "string" }, @@ -15161,18 +16484,18 @@ } } }, - "/console/notifications/archived": { + "/console/profile": { "get": { - "operationId": "listNotificationArchived", + "operationId": "getUserProfile", "tags": [ - "notifications" + "profile" ], - "summary": "List notification archived", - "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", + "summary": "Get user profile", + "description": "**EXCLUDED from the public API** (`console`). 'Current user' only exists for a session; keys are org-scoped, not user-scoped.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "User-scoped console inbox.", + "x-exclusion-reason": "'Current user' only exists for a session; keys are org-scoped, not user-scoped.", "security": [ { "ConsoleSession": [] @@ -15213,25 +16536,34 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/console/notifications/count": { - "get": { - "operationId": "unreadCountNotification", + }, + "patch": { + "operationId": "updateUserProfileField", "tags": [ - "notifications" + "profile" ], - "summary": "Unread count notification", - "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", + "summary": "Update user profile field", + "description": "**EXCLUDED from the public API** (`console`). Personal profile edit for the signed-in user.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "User-scoped console inbox.", + "x-exclusion-reason": "Personal profile edit for the signed-in user.", "security": [ { "ConsoleSession": [] } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -15269,33 +16601,23 @@ } } }, - "/console/notifications/{notificationId}/read": { + "/console/profile/ensure": { "post": { - "operationId": "markReadNotification", + "operationId": "ensureProfileUserProfile", "tags": [ - "notifications" + "profile" ], - "summary": "Mark read notification", - "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", + "summary": "Ensure profile user profile", + "description": "**EXCLUDED from the public API** (`console`). Sign-in bootstrap.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "User-scoped console inbox.", + "x-exclusion-reason": "Sign-in bootstrap.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "notificationId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "requestBody": { "required": false, "content": { @@ -15344,34 +16666,23 @@ } } }, - "/console/notifications/read": { - "post": { - "operationId": "markAllReadNotification", + "/console/teams": { + "get": { + "operationId": "listUserProfileTeams", "tags": [ - "notifications" + "teams" ], - "summary": "Mark all read notification", - "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", + "summary": "List user profile teams", + "description": "**EXCLUDED from the public API** (`console`). Session user's team switcher.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "User-scoped console inbox.", + "x-exclusion-reason": "Session user's team switcher.", "security": [ { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -15409,33 +16720,23 @@ } } }, - "/console/notifications/{notificationId}/archive": { + "/console/profile/avatar/uploads": { "post": { - "operationId": "archiveNotification", + "operationId": "generateUserProfileAvatarUploadUrl", "tags": [ - "notifications" + "profile" ], - "summary": "Archive notification", - "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", + "summary": "Generate user profile avatar upload url", + "description": "**EXCLUDED from the public API** (`console`). Browser avatar-upload flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "User-scoped console inbox.", + "x-exclusion-reason": "Browser avatar-upload flow.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "notificationId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "requestBody": { "required": false, "content": { @@ -15484,18 +16785,18 @@ } } }, - "/console/notifications/archive": { - "post": { - "operationId": "archiveAllNotification", + "/console/profile/avatar": { + "put": { + "operationId": "saveAvatarUserProfile", "tags": [ - "notifications" + "profile" ], - "summary": "Archive all notification", - "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", + "summary": "Save avatar user profile", + "description": "**EXCLUDED from the public API** (`console`). Browser avatar-upload flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "User-scoped console inbox.", + "x-exclusion-reason": "Browser avatar-upload flow.", "security": [ { "ConsoleSession": [] @@ -15547,20 +16848,18 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/console/notifications/preferences": { - "get": { - "operationId": "getNotificationPreferences", + }, + "delete": { + "operationId": "removeAvatarUserProfile", "tags": [ - "notifications" + "profile" ], - "summary": "Get notification preferences", - "description": "**EXCLUDED from the public API** (`console`). Per-user notification preferences.", + "summary": "Remove avatar user profile", + "description": "**EXCLUDED from the public API** (`console`). Personal profile edit.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Per-user notification preferences.", + "x-exclusion-reason": "Personal profile edit.", "security": [ { "ConsoleSession": [] @@ -15601,34 +16900,25 @@ "$ref": "#/components/responses/NotImplemented" } } - }, - "put": { - "operationId": "updateNotificationPreferences", + } + }, + "/console/auth/methods": { + "get": { + "operationId": "getUserProfileAuthMethods", "tags": [ - "notifications" + "auth" ], - "summary": "Update notification preferences", - "description": "**EXCLUDED from the public API** (`console`). Per-user notification preferences.", + "summary": "Get user profile auth methods", + "description": "**EXCLUDED from the public API** (`console`). Account-security introspection for the session user.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Per-user notification preferences.", + "x-exclusion-reason": "Account-security introspection for the session user.", "security": [ { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -15666,18 +16956,18 @@ } } }, - "/console/notifications/test": { + "/console/auth/totp": { "post": { - "operationId": "sendTestNotificationEmail", + "operationId": "enrollTotpUserProfile", "tags": [ - "notifications" + "auth" ], - "summary": "Send test notification email", - "description": "**EXCLUDED from the public API** (`console`). Console settings test button.", + "summary": "Enroll totp user profile", + "description": "**EXCLUDED from the public API** (`console`). MFA enrollment via API key = account-takeover vector.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Console settings test button.", + "x-exclusion-reason": "MFA enrollment via API key = account-takeover vector.", "security": [ { "ConsoleSession": [] @@ -15731,18 +17021,18 @@ } } }, - "/console/quicklink/logo/uploads": { + "/console/auth/totp/verify": { "post": { - "operationId": "generateQuickLinkSettingsLogoUploadUrl", + "operationId": "verifyTotpUserProfile", "tags": [ - "quicklink" + "auth" ], - "summary": "Generate quick link settings logo upload url", - "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", + "summary": "Verify totp user profile", + "description": "**EXCLUDED from the public API** (`console`). Part of the human MFA step-up flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Browser logo-upload flow.", + "x-exclusion-reason": "Part of the human MFA step-up flow.", "security": [ { "ConsoleSession": [] @@ -15796,34 +17086,33 @@ } } }, - "/console/quicklink/logo": { - "put": { - "operationId": "attachLogoQuickLinkSettings", + "/console/auth/factors/{factorId}": { + "delete": { + "operationId": "deleteUserProfileAuthFactor", "tags": [ - "quicklink" + "auth" ], - "summary": "Attach logo quick link settings", - "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", + "summary": "Delete user profile auth factor", + "description": "**EXCLUDED from the public API** (`console`). Removing MFA via API key = account-takeover vector.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Browser logo-upload flow.", + "x-exclusion-reason": "Removing MFA via API key = account-takeover vector.", "security": [ { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "factorId", + "in": "path", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -15859,18 +17148,20 @@ "$ref": "#/components/responses/NotImplemented" } } - }, - "delete": { - "operationId": "removeLogoQuickLinkSettings", + } + }, + "/console/auth/providers": { + "get": { + "operationId": "getUserProfileProviderConnectionUrl", "tags": [ - "quicklink" + "auth" ], - "summary": "Remove logo quick link settings", - "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", + "summary": "Get user profile provider connection url", + "description": "**EXCLUDED from the public API** (`console`). Deferred Better Auth provider-link flow; dashboard identity only.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Browser logo-upload flow.", + "x-exclusion-reason": "Deferred Better Auth provider-link flow; dashboard identity only.", "security": [ { "ConsoleSession": [] @@ -15913,34 +17204,33 @@ } } }, - "/console/quicklink/logo/org": { - "post": { - "operationId": "useOrgLogoQuickLinkSettings", + "/console/auth/providers/{provider}": { + "delete": { + "operationId": "disconnectProviderUserProfile", "tags": [ - "quicklink" + "auth" ], - "summary": "Use org logo quick link settings", - "description": "**EXCLUDED from the public API** (`console`). Console snapshot flow (copies org logo).", + "summary": "Disconnect provider user profile", + "description": "**EXCLUDED from the public API** (`console`). Deferred Better Auth provider-unlink flow; dashboard identity only.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Console snapshot flow (copies org logo).", + "x-exclusion-reason": "Deferred Better Auth provider-unlink flow; dashboard identity only.", "security": [ { "ConsoleSession": [] } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -15978,18 +17268,18 @@ } } }, - "/console/quicklink/logo/project": { + "/console/teams/leave": { "post": { - "operationId": "useProjectIconQuickLinkSettings", + "operationId": "leaveTeamUserProfile", "tags": [ - "quicklink" + "teams" ], - "summary": "Use project icon quick link settings", - "description": "**EXCLUDED from the public API** (`console`). Console snapshot flow (copies project icon).", + "summary": "Leave team user profile", + "description": "**EXCLUDED from the public API** (`console`). Acts on the session user's own membership.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Console snapshot flow (copies project icon).", + "x-exclusion-reason": "Acts on the session user's own membership.", "security": [ { "ConsoleSession": [] @@ -16043,23 +17333,44 @@ } } }, - "/console/webhooks": { - "get": { - "operationId": "listWebhooks", + "/console/teams/{teamId}/delete": { + "post": { + "operationId": "deleteUserProfileTeam", "tags": [ - "webhooks" + "teams" ], - "summary": "List webhook endpoints", - "description": "Lists sanitized webhook endpoint settings for the authenticated organization.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "summary": "Delete user profile team", + "description": "**EXCLUDED from the public API** (`console`). Destructive, human-confirmed flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard webhook settings for the authenticated organization.", + "x-exclusion-reason": "Destructive, human-confirmed flow.", "security": [ { "ConsoleSession": [] } ], + "parameters": [ + { + "name": "teamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -16080,19 +17391,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ConsoleWebhook" - } - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } @@ -16102,36 +17401,30 @@ }, "403": { "$ref": "#/components/responses/Forbidden" + }, + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "post": { - "operationId": "createWebhook", + } + }, + "/console/notifications": { + "get": { + "operationId": "listNotificationInbox", "tags": [ - "webhooks" + "notifications" ], - "summary": "Create a webhook endpoint", - "description": "Creates a webhook endpoint for the authenticated organization. Requires live owner or admin authority.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "summary": "List notification inbox", + "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard webhook settings for the authenticated organization.", - "x-required-organization-role": "owner_or_admin", + "x-exclusion-reason": "User-scoped console inbox.", "security": [ { "ConsoleSession": [] } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateConsoleWebhookRequest" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -16152,71 +17445,40 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/ConsoleWebhook" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" + }, + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/console/webhooks/{webhookId}": { - "patch": { - "operationId": "updateWebhook", + "/console/notifications/archived": { + "get": { + "operationId": "listNotificationArchived", "tags": [ - "webhooks" + "notifications" ], - "summary": "Update a webhook endpoint", - "description": "Updates a webhook endpoint in the authenticated organization. Requires live owner or admin authority.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "summary": "List notification archived", + "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard webhook settings for the authenticated organization.", - "x-required-organization-role": "owner_or_admin", + "x-exclusion-reason": "User-scoped console inbox.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateConsoleWebhookRequest" - } - } - } - }, "responses": { "200": { "description": "Success", @@ -16237,62 +17499,40 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/ConsoleWebhook" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } - }, - "delete": { - "operationId": "deleteWebhook", + } + }, + "/console/notifications/count": { + "get": { + "operationId": "unreadCountNotification", "tags": [ - "webhooks" + "notifications" ], - "summary": "Delete a webhook endpoint", - "description": "Deletes a webhook endpoint in the authenticated organization. Requires live owner or admin authority.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "summary": "Unread count notification", + "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard webhook settings for the authenticated organization.", - "x-required-organization-role": "owner_or_admin", + "x-exclusion-reason": "User-scoped console inbox.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], "responses": { "200": { "description": "Success", @@ -16313,52 +17553,61 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/DeleteConsoleWebhookResult" - } - }, - "required": [ - "data" - ] + "$ref": "#/components/schemas/DataEnvelope" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "404": { - "$ref": "#/components/responses/NotFound" + "501": { + "$ref": "#/components/responses/NotImplemented" } } } }, - "/console/sandbox/pool": { - "get": { - "operationId": "getSandboxProjectPool", + "/console/notifications/{notificationId}/read": { + "post": { + "operationId": "markReadNotification", "tags": [ - "sandbox" + "notifications" ], - "summary": "Get sandbox project project pool", - "description": "**EXCLUDED from the public API** (`console`). Console/sandbox internal wiring.", + "summary": "Mark read notification", + "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Console/sandbox internal wiring.", + "x-exclusion-reason": "User-scoped console inbox.", "security": [ { "ConsoleSession": [] } ], + "parameters": [ + { + "name": "notificationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -16396,23 +17645,34 @@ } } }, - "/console/deployments": { - "get": { - "operationId": "listDeployments", + "/console/notifications/read": { + "post": { + "operationId": "markAllReadNotification", "tags": [ - "deployments" + "notifications" ], - "summary": "List deployments", - "description": "**EXCLUDED from the public API** (`console`). Legacy console surface; not a customer contract.", + "summary": "Mark all read notification", + "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Legacy console surface; not a customer contract.", + "x-exclusion-reason": "User-scoped console inbox.", "security": [ { "ConsoleSession": [] } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -16450,18 +17710,18 @@ } } }, - "/console/calls": { - "get": { - "operationId": "listCalls", + "/console/notifications/{notificationId}/archive": { + "post": { + "operationId": "archiveNotification", "tags": [ - "calls" + "notifications" ], - "summary": "List calls", - "description": "**EXCLUDED from the public API** (`console`). Customer console view of the organization's own call records.", + "summary": "Archive notification", + "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Customer console view of the organization's own call records.", + "x-exclusion-reason": "User-scoped console inbox.", "security": [ { "ConsoleSession": [] @@ -16469,33 +17729,25 @@ ], "parameters": [ { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "sessionId", - "in": "query", - "required": false, + "name": "notificationId", + "in": "path", + "required": true, "schema": { "type": "string" } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -16533,77 +17785,34 @@ } } }, - "/console/insights/logs": { - "get": { - "operationId": "logsInsight", + "/console/notifications/archive": { + "post": { + "operationId": "archiveAllNotification", "tags": [ - "insights" + "notifications" ], - "summary": "Logs insight", - "description": "**EXCLUDED from the public API** (`console`). Customer console observability for the selected organization and project.", + "summary": "Archive all notification", + "description": "**EXCLUDED from the public API** (`console`). User-scoped console inbox.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Customer console observability for the selected organization and project.", + "x-exclusion-reason": "User-scoped console inbox.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "section", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "all", - "api", - "webhooks", - "sessions", - "errors", - "audit" - ], - "default": "all" - } - }, - { - "name": "start", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "end", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 500, - "default": 100 + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -16641,49 +17850,23 @@ } } }, - "/console/insights/usage": { + "/console/notifications/preferences": { "get": { - "operationId": "usageInsight", + "operationId": "getNotificationPreferences", "tags": [ - "insights" + "notifications" ], - "summary": "Usage insight", - "description": "**EXCLUDED from the public API** (`console`). Customer console usage and entitlement presentation.", + "summary": "Get notification preferences", + "description": "**EXCLUDED from the public API** (`console`). Per-user notification preferences.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Customer console usage and entitlement presentation.", + "x-exclusion-reason": "Per-user notification preferences.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "start", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "end", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], "responses": { "200": { "description": "Success", @@ -16719,51 +17902,34 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/console/insights/analytics": { - "get": { - "operationId": "analyticsInsight", + }, + "put": { + "operationId": "updateNotificationPreferences", "tags": [ - "insights" + "notifications" ], - "summary": "Analytics insight", - "description": "**EXCLUDED from the public API** (`console`). Customer console aggregate product analytics; message bodies are never returned.", + "summary": "Update notification preferences", + "description": "**EXCLUDED from the public API** (`console`). Per-user notification preferences.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Customer console aggregate product analytics; message bodies are never returned.", + "x-exclusion-reason": "Per-user notification preferences.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "start", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "end", - "in": "query", - "required": false, - "schema": { - "type": "string" + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -16801,23 +17967,34 @@ } } }, - "/console/insights/preferences": { - "get": { - "operationId": "getInsightPreferences", + "/console/notifications/test": { + "post": { + "operationId": "sendTestNotificationEmail", "tags": [ - "insights" + "notifications" ], - "summary": "Get insight preferences", - "description": "**EXCLUDED from the public API** (`console`). Organization analytics-consent controls for the console.", + "summary": "Send test notification email", + "description": "**EXCLUDED from the public API** (`console`). Console settings test button.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Organization analytics-consent controls for the console.", + "x-exclusion-reason": "Console settings test button.", "security": [ { "ConsoleSession": [] } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -16853,18 +18030,20 @@ "$ref": "#/components/responses/NotImplemented" } } - }, - "put": { - "operationId": "updateInsightPreferences", + } + }, + "/console/quicklink/logo/uploads": { + "post": { + "operationId": "generateQuickLinkSettingsLogoUploadUrl", "tags": [ - "insights" + "quicklink" ], - "summary": "Update insight preferences", - "description": "**EXCLUDED from the public API** (`console`). Organization analytics-consent controls for the console.", + "summary": "Generate quick link settings logo upload url", + "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Organization analytics-consent controls for the console.", + "x-exclusion-reason": "Browser logo-upload flow.", "security": [ { "ConsoleSession": [] @@ -16918,33 +18097,23 @@ } } }, - "/console/organization-invitations/{invitationId}/accept": { - "post": { - "operationId": "acceptOrganizationInvitation", + "/console/quicklink/logo": { + "put": { + "operationId": "attachLogoQuickLinkSettings", "tags": [ - "organization-invitations" + "quicklink" ], - "summary": "Accept organization invitation", - "description": "**EXCLUDED from the public API** (`console`). Deferred Better Auth invitation-acceptance flow; dashboard identity only.", + "summary": "Attach logo quick link settings", + "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Deferred Better Auth invitation-acceptance flow; dashboard identity only.", + "x-exclusion-reason": "Browser logo-upload flow.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "invitationId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "requestBody": { "required": false, "content": { @@ -16991,20 +18160,18 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/admin/orgs": { - "get": { - "operationId": "adminListOrganizations", + }, + "delete": { + "operationId": "removeLogoQuickLinkSettings", "tags": [ - "admin/orgs" + "quicklink" ], - "summary": "List organizations", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Remove logo quick link settings", + "description": "**EXCLUDED from the public API** (`console`). Browser logo-upload flow.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Browser logo-upload flow.", "security": [ { "ConsoleSession": [] @@ -17047,33 +18214,34 @@ } } }, - "/admin/orgs/{orgId}": { - "get": { - "operationId": "adminGetOrganization", + "/console/quicklink/logo/org": { + "post": { + "operationId": "useOrgLogoQuickLinkSettings", "tags": [ - "admin/orgs" + "quicklink" ], - "summary": "Get organization", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Use org logo quick link settings", + "description": "**EXCLUDED from the public API** (`console`). Console snapshot flow (copies org logo).", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Console snapshot flow (copies org logo).", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "orgId", - "in": "path", - "required": true, - "schema": { - "type": "string" + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } } } - ], + }, "responses": { "200": { "description": "Success", @@ -17109,33 +18277,25 @@ "$ref": "#/components/responses/NotImplemented" } } - }, - "patch": { - "operationId": "adminRenameOrganization", + } + }, + "/console/quicklink/logo/project": { + "post": { + "operationId": "useProjectIconQuickLinkSettings", "tags": [ - "admin/orgs" + "quicklink" ], - "summary": "Rename organization", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Use project icon quick link settings", + "description": "**EXCLUDED from the public API** (`console`). Console snapshot flow (copies project icon).", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Console snapshot flow (copies project icon).", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "orgId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "requestBody": { "required": false, "content": { @@ -17182,33 +18342,25 @@ "$ref": "#/components/responses/NotImplemented" } } - }, - "delete": { - "operationId": "adminCascadeDeleteOrganization", + } + }, + "/console/webhooks": { + "get": { + "operationId": "listWebhooks", "tags": [ - "admin/orgs" + "webhooks" ], - "summary": "Cascade delete organization", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "List webhook endpoints", + "description": "Lists sanitized webhook endpoint settings for the authenticated team.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Dashboard webhook settings for the authenticated organization.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "orgId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "responses": { "200": { "description": "Success", @@ -17229,7 +18381,19 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConsoleWebhook" + } + } + }, + "required": [ + "data" + ] } } } @@ -17239,47 +18403,32 @@ }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/admin/orgs/{orgId}/freeze": { + }, "post": { - "operationId": "adminFreezeOrganization", + "operationId": "createWebhook", "tags": [ - "admin/orgs" + "webhooks" ], - "summary": "Freeze organization", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Create a webhook endpoint", + "description": "Creates a webhook endpoint for the authenticated team. Requires live owner or admin authority.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Dashboard webhook settings for the authenticated organization.", + "x-required-organization-role": "owner_or_admin", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "orgId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/CreateConsoleWebhookRequest" } } } @@ -17304,35 +18453,45 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ConsoleWebhook" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" } } } }, - "/admin/orgs/{orgId}/unfreeze": { - "post": { - "operationId": "adminUnfreezeOrganization", + "/console/webhooks/{webhookId}": { + "patch": { + "operationId": "updateWebhook", "tags": [ - "admin/orgs" + "webhooks" ], - "summary": "Unfreeze organization", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Update a webhook endpoint", + "description": "Updates a webhook endpoint in the authenticated team. Requires live owner or admin authority.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Dashboard webhook settings for the authenticated organization.", + "x-required-organization-role": "owner_or_admin", "security": [ { "ConsoleSession": [] @@ -17340,21 +18499,21 @@ ], "parameters": [ { - "name": "orgId", + "name": "webhookId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/UpdateConsoleWebhookRequest" } } } @@ -17379,41 +18538,63 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ConsoleWebhook" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/admin/orgs/features": { - "get": { - "operationId": "adminListOrganizationFeatureGroups", + }, + "delete": { + "operationId": "deleteWebhook", "tags": [ - "admin/orgs" + "webhooks" ], - "summary": "List organization feature groups", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Delete a webhook endpoint", + "description": "Deletes a webhook endpoint in the authenticated team. Requires live owner or admin authority.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Dashboard webhook settings for the authenticated organization.", + "x-required-organization-role": "owner_or_admin", "security": [ { "ConsoleSession": [] } ], - "responses": { + "parameters": [ + { + "name": "webhookId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { "200": { "description": "Success", "headers": { @@ -17433,61 +18614,52 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/DeleteConsoleWebhookResult" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } }, - "/admin/orgs/{orgId}/features": { - "patch": { - "operationId": "adminSetOrganizationFeatureGroups", + "/console/sandbox/pool": { + "get": { + "operationId": "getSandboxProjectPool", "tags": [ - "admin/orgs" + "sandbox" ], - "summary": "Set organization feature groups", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Get sandbox project project pool", + "description": "**EXCLUDED from the public API** (`console`). Console/sandbox internal wiring.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Console/sandbox internal wiring.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "orgId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -17525,18 +18697,18 @@ } } }, - "/admin/users": { + "/console/deployments": { "get": { - "operationId": "adminSearchUser", + "operationId": "listDeployments", "tags": [ - "admin/users" + "deployments" ], - "summary": "Search user", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "List deployments", + "description": "**EXCLUDED from the public API** (`console`). Legacy console surface; not a customer contract.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Legacy console surface; not a customer contract.", "security": [ { "ConsoleSession": [] @@ -17579,18 +18751,18 @@ } } }, - "/admin/users/{userId}": { + "/console/calls": { "get": { - "operationId": "adminGetUser", + "operationId": "listCalls", "tags": [ - "admin/users" + "calls" ], - "summary": "Get user", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "List calls", + "description": "**EXCLUDED from the public API** (`console`). Customer console view of the organization's own call records.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Customer console view of the organization's own call records.", "security": [ { "ConsoleSession": [] @@ -17598,12 +18770,31 @@ ], "parameters": [ { - "name": "userId", - "in": "path", - "required": true, + "name": "projectId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sessionId", + "in": "query", + "required": false, "schema": { "type": "string" } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } } ], "responses": { @@ -17643,18 +18834,18 @@ } } }, - "/admin/users/{userId}/onboarding": { - "delete": { - "operationId": "adminResetOnboardingUser", + "/console/insights/logs": { + "get": { + "operationId": "logsInsight", "tags": [ - "admin/users" + "insights" ], - "summary": "Reset onboarding user", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Logs insight", + "description": "**EXCLUDED from the public API** (`console`). Customer console observability for the selected organization and project.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Customer console observability for the selected organization and project.", "security": [ { "ConsoleSession": [] @@ -17662,12 +18853,56 @@ ], "parameters": [ { - "name": "userId", - "in": "path", - "required": true, + "name": "projectId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "section", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "all", + "api", + "webhooks", + "sessions", + "errors", + "audit" + ], + "default": "all" + } + }, + { + "name": "start", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "end", + "in": "query", + "required": false, "schema": { "type": "string" } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 100 + } } ], "responses": { @@ -17707,18 +18942,18 @@ } } }, - "/admin/users/{userId}/suspend": { - "post": { - "operationId": "adminSuspendUser", + "/console/insights/usage": { + "get": { + "operationId": "usageInsight", "tags": [ - "admin/users" + "insights" ], - "summary": "Suspend user user", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Usage insight", + "description": "**EXCLUDED from the public API** (`console`). Customer console usage and entitlement presentation.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Customer console usage and entitlement presentation.", "security": [ { "ConsoleSession": [] @@ -17726,25 +18961,30 @@ ], "parameters": [ { - "name": "userId", - "in": "path", - "required": true, + "name": "projectId", + "in": "query", + "required": false, "schema": { "type": "string" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + }, + { + "name": "start", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "end", + "in": "query", + "required": false, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -17782,18 +19022,18 @@ } } }, - "/admin/users/{userId}/unsuspend": { - "post": { - "operationId": "adminUnsuspendUser", + "/console/insights/analytics": { + "get": { + "operationId": "analyticsInsight", "tags": [ - "admin/users" + "insights" ], - "summary": "Unsuspend user user", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Analytics insight", + "description": "**EXCLUDED from the public API** (`console`). Customer console aggregate product analytics; message bodies are never returned.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Customer console aggregate product analytics; message bodies are never returned.", "security": [ { "ConsoleSession": [] @@ -17801,25 +19041,30 @@ ], "parameters": [ { - "name": "userId", - "in": "path", - "required": true, + "name": "projectId", + "in": "query", + "required": false, "schema": { "type": "string" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } + }, + { + "name": "start", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "end", + "in": "query", + "required": false, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "Success", @@ -17857,44 +19102,23 @@ } } }, - "/admin/users/{userId}/signout": { - "post": { - "operationId": "adminForceSignOutUser", + "/console/insights/preferences": { + "get": { + "operationId": "getInsightPreferences", "tags": [ - "admin/users" + "insights" ], - "summary": "Force sign out user", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Get insight preferences", + "description": "**EXCLUDED from the public API** (`console`). Organization analytics-consent controls for the console.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Organization analytics-consent controls for the console.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, "responses": { "200": { "description": "Success", @@ -17930,35 +19154,23 @@ "$ref": "#/components/responses/NotImplemented" } } - } - }, - "/admin/members/{memberId}": { - "patch": { - "operationId": "adminSetMemberRole", + }, + "put": { + "operationId": "updateInsightPreferences", "tags": [ - "admin/members" + "insights" ], - "summary": "Set member role", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Update insight preferences", + "description": "**EXCLUDED from the public API** (`console`). Organization analytics-consent controls for the console.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Organization analytics-consent controls for the console.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "memberId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "requestBody": { "required": false, "content": { @@ -18005,18 +19217,20 @@ "$ref": "#/components/responses/NotImplemented" } } - }, - "delete": { - "operationId": "adminRemoveMember", + } + }, + "/console/organization-invitations/{invitationId}/accept": { + "post": { + "operationId": "acceptOrganizationInvitation", "tags": [ - "admin/members" + "organization-invitations" ], - "summary": "Remove member member", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Accept organization invitation", + "description": "**EXCLUDED from the public API** (`console`). Deferred Better Auth invitation-acceptance flow; dashboard identity only.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Deferred Better Auth invitation-acceptance flow; dashboard identity only.", "security": [ { "ConsoleSession": [] @@ -18024,7 +19238,7 @@ ], "parameters": [ { - "name": "memberId", + "name": "invitationId", "in": "path", "required": true, "schema": { @@ -18032,6 +19246,17 @@ } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, "responses": { "200": { "description": "Success", @@ -18069,30 +19294,55 @@ } } }, - "/admin/members/invites": { + "/api/account/features/evaluate": { "post": { - "operationId": "adminInviteMember", + "operationId": "evaluateAccountFeatures", "tags": [ - "admin/members" + "Feature availability" ], - "summary": "Invite member member", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "Evaluate feature availability", + "description": "Evaluate feature decisions for the live dashboard organization. With measurement consent, assigned experiments return a five-minute exposure token. No permission is granted by a decision. Unoffered, unenrolled betas are omitted. Offered betas include terms before enrollment; enrolled betas remain visible for withdrawal.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Private dashboard feature discovery; requires live organization membership.", "security": [ { "ConsoleSession": [] } ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { "type": "object", - "additionalProperties": true + "additionalProperties": false, + "required": [ + "surface", + "keys" + ], + "properties": { + "surface": { + "type": "string", + "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$", + "maxLength": 100 + }, + "keys": { + "type": "array", + "maxItems": 50, + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$", + "maxLength": 100 + } + }, + "measurementAllowed": { + "type": "boolean", + "default": false, + "description": "Explicit measurement consent; the server also requires telemetry configuration." + } + } } } } @@ -18112,71 +19362,169 @@ "type": "string" }, "description": "API version applied to this response." + }, + "Cache-Control": { + "schema": { + "type": "string", + "example": "no-store" + } } }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "decision", + "title", + "description", + "termsRevision", + "withdrawal", + "exposureToken" + ], + "properties": { + "decision": { + "type": "object", + "required": [ + "key", + "version", + "available", + "enrolled", + "variant", + "experimentAssigned" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$", + "maxLength": 100 + }, + "version": { + "type": "integer", + "minimum": 1 + }, + "available": { + "type": "boolean" + }, + "enrolled": { + "type": "boolean" + }, + "variant": { + "type": "string", + "nullable": true + }, + "experimentAssigned": { + "type": "boolean", + "description": "True only when an experiment assigned a registered variant with measurement enabled." + } + } + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsRevision": { + "type": "string", + "nullable": true + }, + "withdrawal": { + "type": "string", + "nullable": true + }, + "exposureToken": { + "type": "string", + "nullable": true, + "description": "Null when unavailable, no experiment is assigned, exposure context is absent, or measurement is disabled." + } + } + } + } + }, + "required": [ + "success", + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "413": { + "$ref": "#/components/responses/PayloadTooLarge" + }, + "500": { + "$ref": "#/components/responses/InternalError" } } } }, - "/admin/bansafe/enforcement/{orgId}/{sessionId}/lift": { + "/api/account/features/enrollment": { "post": { - "operationId": "adminLiftBanSafeEnforcement", + "operationId": "setFeatureEnrollment", "tags": [ - "admin/bansafe" + "Feature availability" ], - "summary": "Lift ban safe enforcement", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "setFeatureEnrollment", + "description": "Live organization owner/admin only. Enabling requires current terms, entitlement, release eligibility and no operational disable. Withdrawal does not delete resources.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Private beta enrollment; requires live organization owner or admin authority.", + "x-required-organization-role": "owner_or_admin", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "orgId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "sessionId", - "in": "path", - "required": true, - "description": "Database session UUID or stable session slug.", - "schema": { - "$ref": "#/components/schemas/SessionIdentifier" - } - } - ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { "type": "object", - "additionalProperties": true + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$", + "maxLength": 100 + }, + "enabled": { + "type": "boolean" + }, + "termsRevision": { + "type": "string", + "nullable": true, + "maxLength": 100 + } + }, + "required": [ + "key", + "enabled", + "termsRevision" + ] } } } @@ -18201,223 +19549,103 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/bansafe/claims": { - "get": { - "operationId": "adminListBanSafeClaims", - "tags": [ - "admin/bansafe" - ], - "summary": "List ban safe claims", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "orgId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "status", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "filed", - "under_review", - "approved", - "denied", - "paid", - "reversed" - ] - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "type": "object", + "required": [ + "enabled", + "termsRevision" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "termsRevision": { + "type": "string" + } + } + } + }, + "required": [ + "success", + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/bansafe/claims/{claimId}/approve": { - "post": { - "operationId": "adminApproveBanSafeClaim", - "tags": [ - "admin/bansafe" - ], - "summary": "Approve ban safe claim", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "claimId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } + "404": { + "$ref": "#/components/responses/NotFound" }, - "401": { - "$ref": "#/components/responses/Unauthorized" + "409": { + "$ref": "#/components/responses/Conflict" }, - "403": { - "$ref": "#/components/responses/Forbidden" + "413": { + "$ref": "#/components/responses/PayloadTooLarge" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "500": { + "$ref": "#/components/responses/InternalError" } } } }, - "/admin/bansafe/claims/{claimId}/deny": { + "/api/account/features/exposure": { "post": { - "operationId": "adminDenyBanSafeClaim", + "operationId": "recordFeatureExposure", "tags": [ - "admin/bansafe" + "Feature availability" ], - "summary": "Deny ban safe claim", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", + "summary": "recordFeatureExposure", + "description": "Record actual exposure using the signed evaluation token. Binds user, organization, version and variant; tokens expire after five minutes. Deduplicates per assignment/version/variant and five-minute bucket.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", + "x-exclusion-reason": "Private measurement receipt; requires the issuing dashboard identity and consent.", "security": [ { "ConsoleSession": [] } ], - "parameters": [ - { - "name": "claimId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { "type": "object", - "additionalProperties": true + "additionalProperties": false, + "properties": { + "token": { + "type": "string", + "maxLength": 4096 + }, + "measurementAllowed": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "token", + "measurementAllowed" + ] } } } @@ -18442,2546 +19670,52 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + } + } } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "413": { + "$ref": "#/components/responses/PayloadTooLarge" + }, + "500": { + "$ref": "#/components/responses/InternalError" } } - } - }, - "/admin/bansafe/claims/{claimId}/reverse": { - "post": { - "operationId": "adminReverseBanSafeClaim", - "tags": [ - "admin/bansafe" - ], - "summary": "Reverse ban safe claim", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "claimId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/sessions": { - "get": { - "operationId": "adminListSessions", - "tags": [ - "admin/sessions" - ], - "summary": "List sessions", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/sessions/{sessionId}/stop": { - "post": { - "operationId": "adminForceStopSession", - "tags": [ - "admin/sessions" - ], - "summary": "Force stop session", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "sessionId", - "in": "path", - "required": true, - "description": "Database session UUID or stable session slug.", - "schema": { - "$ref": "#/components/schemas/SessionIdentifier" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/sessions/{sessionId}": { - "delete": { - "operationId": "adminForceDeleteSession", - "tags": [ - "admin/sessions" - ], - "summary": "Force delete session", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "sessionId", - "in": "path", - "required": true, - "description": "Database session UUID or stable session slug.", - "schema": { - "$ref": "#/components/schemas/SessionIdentifier" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/sessions/{sessionId}/comp": { - "post": { - "operationId": "adminCompSessionDaySession", - "tags": [ - "admin/sessions" - ], - "summary": "Comp session day session", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "sessionId", - "in": "path", - "required": true, - "description": "Database session UUID or stable session slug.", - "schema": { - "$ref": "#/components/schemas/SessionIdentifier" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/campaigns": { - "get": { - "operationId": "adminListCampaigns", - "tags": [ - "admin/campaigns" - ], - "summary": "List campaigns", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/campaigns/{campaignId}/pause": { - "post": { - "operationId": "adminForcePauseCampaign", - "tags": [ - "admin/campaigns" - ], - "summary": "Force pause campaign", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "campaignId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/campaigns/{campaignId}/stop": { - "post": { - "operationId": "adminForceStopCampaign", - "tags": [ - "admin/campaigns" - ], - "summary": "Force stop campaign", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "campaignId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/campaigns/optouts": { - "get": { - "operationId": "adminOptOutLeaderboardCampaign", - "tags": [ - "admin/campaigns" - ], - "summary": "Opt out leaderboard campaign", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/billing/revenue": { - "get": { - "operationId": "adminPlatformRevenueBilling", - "tags": [ - "admin/billing" - ], - "summary": "Platform revenue billing", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/billing/leaderboard": { - "get": { - "operationId": "adminTopOrgsByRevenueBilling", - "tags": [ - "admin/billing" - ], - "summary": "Top orgs by revenue billing", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/billing/lowbalance": { - "get": { - "operationId": "adminLowBalanceOrgsBilling", - "tags": [ - "admin/billing" - ], - "summary": "Low balance orgs billing", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/billing/credits": { - "post": { - "operationId": "adminCompCreditsBilling", - "tags": [ - "admin/billing" - ], - "summary": "Comp credits billing", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/billing/refunds": { - "post": { - "operationId": "adminRefundBilling", - "tags": [ - "admin/billing" - ], - "summary": "Refund billing", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/keys": { - "get": { - "operationId": "adminListApiKeys", - "tags": [ - "admin/keys" - ], - "summary": "List api keys", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/keys/{keyId}": { - "delete": { - "operationId": "adminRevokeApiKeyApiKey", - "tags": [ - "admin/keys" - ], - "summary": "Revoke api key api key", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "keyId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/tenants": { - "get": { - "operationId": "adminListTenants", - "tags": [ - "admin/tenants" - ], - "summary": "List tenants", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/tenants/{tenantId}": { - "patch": { - "operationId": "adminSetTenantActive", - "tags": [ - "admin/tenants" - ], - "summary": "Set tenant active", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-merged-endpoints": [ - "adminUpdateTenantRateLimits", - "adminUpdateTenantLogLevel", - "adminToggleDebugModeTenant" - ], - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "tenantId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/pricing": { - "get": { - "operationId": "adminListPricing", - "tags": [ - "admin/pricing" - ], - "summary": "List pricing", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/pricing/{tier}": { - "put": { - "operationId": "adminUpdatePricing", - "tags": [ - "admin/pricing" - ], - "summary": "Update pricing", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "tier", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - }, - "patch": { - "operationId": "adminSetPricingActive", - "tags": [ - "admin/pricing" - ], - "summary": "Set pricing active", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "tier", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/organization-invitations": { - "get": { - "operationId": "adminListOrganizationInvitations", - "tags": [ - "admin/organization-invitations" - ], - "summary": "List organization invitations", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - }, - "post": { - "operationId": "adminCreateOrganizationInvitation", - "tags": [ - "admin/organization-invitations" - ], - "summary": "Create organization invitation", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/organization-invitations/{invitationId}": { - "delete": { - "operationId": "adminRevokeOrganizationInvitation", - "tags": [ - "admin/organization-invitations" - ], - "summary": "Revoke organization invitation", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "invitationId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/webhooks": { - "get": { - "operationId": "adminListWebhooks", - "tags": [ - "admin/webhooks" - ], - "summary": "List webhooks", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/webhooks/{webhookId}/disable": { - "post": { - "operationId": "adminForceDisableWebhook", - "tags": [ - "admin/webhooks" - ], - "summary": "Force disable webhook", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "webhookId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/audit": { - "get": { - "operationId": "adminListAuditLogGlobal", - "tags": [ - "admin/audit" - ], - "summary": "List audit log global", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/audit/actions": { - "get": { - "operationId": "adminListAuditLogAdminActions", - "tags": [ - "admin/audit" - ], - "summary": "List audit log admin actions", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/kpis": { - "get": { - "operationId": "adminPlatformKpisPlatform", - "tags": [ - "admin/kpis" - ], - "summary": "Platform kpis platform", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/signups": { - "get": { - "operationId": "adminRecentSignupsPlatform", - "tags": [ - "admin/signups" - ], - "summary": "Recent signups platform", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/sync": { - "post": { - "operationId": "adminEnsureCurrentUserStaffSyncStaffSync", - "tags": [ - "admin/sync" - ], - "summary": "Ensure current user staff sync staff sync", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/admin/bans": { - "get": { - "operationId": "adminListRecentSessionBans", - "tags": [ - "admin/bans" - ], - "summary": "List recent session bans", - "description": "**EXCLUDED from the public API** (`staff`). Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/api/account/features/evaluate": { - "post": { - "operationId": "evaluateAccountFeatures", - "tags": [ - "Feature availability" - ], - "summary": "Evaluate feature availability", - "description": "Evaluate feature decisions for the live dashboard organization. With measurement consent, assigned experiments return a five-minute exposure token. No permission is granted by a decision. Unoffered, unenrolled betas are omitted. Offered betas include terms before enrollment; enrolled betas remain visible for withdrawal.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Private dashboard feature discovery; requires live organization membership.", - "security": [ - { - "ConsoleSession": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "required": [ - "surface", - "keys" - ], - "properties": { - "surface": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$", - "maxLength": 100 - }, - "keys": { - "type": "array", - "maxItems": 50, - "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$", - "maxLength": 100 - } - }, - "measurementAllowed": { - "type": "boolean", - "default": false, - "description": "Explicit measurement consent; the server also requires telemetry configuration." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - }, - "Cache-Control": { - "schema": { - "type": "string", - "example": "no-store" - } - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "type": "array", - "items": { - "type": "object", - "required": [ - "decision", - "title", - "description", - "termsRevision", - "withdrawal", - "exposureToken" - ], - "properties": { - "decision": { - "type": "object", - "required": [ - "key", - "version", - "available", - "enrolled", - "variant", - "experimentAssigned" - ], - "properties": { - "key": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$", - "maxLength": 100 - }, - "version": { - "type": "integer", - "minimum": 1 - }, - "available": { - "type": "boolean" - }, - "enrolled": { - "type": "boolean" - }, - "variant": { - "type": "string", - "nullable": true - }, - "experimentAssigned": { - "type": "boolean", - "description": "True only when an experiment assigned a registered variant with measurement enabled." - } - } - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "termsRevision": { - "type": "string", - "nullable": true - }, - "withdrawal": { - "type": "string", - "nullable": true - }, - "exposureToken": { - "type": "string", - "nullable": true, - "description": "Null when unavailable, no experiment is assigned, exposure context is absent, or measurement is disabled." - } - } - } - } - }, - "required": [ - "success", - "data" - ] - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "413": { - "$ref": "#/components/responses/PayloadTooLarge" - }, - "500": { - "$ref": "#/components/responses/InternalError" - } - } - } - }, - "/api/account/features/enrollment": { - "post": { - "operationId": "setFeatureEnrollment", - "tags": [ - "Feature availability" - ], - "summary": "setFeatureEnrollment", - "description": "Live organization owner/admin only. Enabling requires current terms, entitlement, release eligibility and no operational disable. Withdrawal does not delete resources.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Private beta enrollment; requires live organization owner or admin authority.", - "x-required-organization-role": "owner_or_admin", - "security": [ - { - "ConsoleSession": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "key": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$", - "maxLength": 100 - }, - "enabled": { - "type": "boolean" - }, - "termsRevision": { - "type": "string", - "nullable": true, - "maxLength": 100 - } - }, - "required": [ - "key", - "enabled", - "termsRevision" - ] - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - }, - "data": { - "type": "object", - "required": [ - "enabled", - "termsRevision" - ], - "properties": { - "enabled": { - "type": "boolean" - }, - "termsRevision": { - "type": "string" - } - } - } - }, - "required": [ - "success", - "data" - ] - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" - }, - "413": { - "$ref": "#/components/responses/PayloadTooLarge" - }, - "500": { - "$ref": "#/components/responses/InternalError" - } - } - } - }, - "/api/account/features/exposure": { - "post": { - "operationId": "recordFeatureExposure", - "tags": [ - "Feature availability" - ], - "summary": "recordFeatureExposure", - "description": "Record actual exposure using the signed evaluation token. Binds user, organization, version and variant; tokens expire after five minutes. Deduplicates per assignment/version/variant and five-minute bucket.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "console", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Private measurement receipt; requires the issuing dashboard identity and consent.", - "security": [ - { - "ConsoleSession": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "token": { - "type": "string", - "maxLength": 4096 - }, - "measurementAllowed": { - "type": "boolean", - "enum": [ - true - ] - } - }, - "required": [ - "token", - "measurementAllowed" - ] - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "success" - ], - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "413": { - "$ref": "#/components/responses/PayloadTooLarge" - }, - "500": { - "$ref": "#/components/responses/InternalError" - } - } - } - }, - "/api/admin/features/control": { - "post": { - "operationId": "setFeatureControl", - "tags": [ - "Feature availability" - ], - "summary": "setFeatureControl", - "description": "Live staff only, with an active organization for audit attribution. Global disable applies to subsequent engine evaluations independently of PostHog.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", - "x-audience": "staff", - "x-polymorfa-resource-boundary": "human_only", - "x-excluded-from-public": true, - "x-exclusion-reason": "Polymorfa staff endpoint (cross-org / platform control). Requires a staff dashboard identity; never key-authenticated.", - "security": [ - { - "ConsoleSession": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "key": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$", - "maxLength": 100 - }, - "disabled": { - "type": "boolean" - }, - "reason": { - "type": "string", - "minLength": 1, - "maxLength": 1000 - } - }, - "required": [ - "key", - "disabled", - "reason" - ] - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "success" - ], - "properties": { - "success": { - "type": "boolean", - "enum": [ - true - ] - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "413": { - "$ref": "#/components/responses/PayloadTooLarge" - }, - "500": { - "$ref": "#/components/responses/InternalError" - } - } - } - } - }, - "components": { - "securitySchemes": { - "ApiKeyAuth": { - "type": "http", - "scheme": "bearer", - "description": "Polymorfa server credential: a scoped pmfa_ organization key, or a pmfa_pt_ project token on project- and session-bound operations inside its exact authenticated project. Keys may be personal or service-account owned. Both enforce selected action scopes; organization keys can address team resources and every project in the organization. Listener and browser client credentials are rejected." - }, - "ConsoleSession": { - "type": "http", - "scheme": "bearer", - "description": "Verified dashboard JWT for signed-in Console requests. This is not a server credential." + } + } + }, + "components": { + "securitySchemes": { + "ApiKeyAuth": { + "type": "http", + "scheme": "bearer", + "description": "Send your team API key or project token as `Authorization: Bearer `. The credential must have the required permissions. Project tokens can access only their own project." + }, + "ConsoleSession": { + "type": "http", + "scheme": "bearer", + "description": "Verified dashboard JWT for signed-in Console requests. This is not a server credential." } }, "schemas": { @@ -21058,7 +19792,7 @@ "ProjectId": { "type": "string", "format": "uuid", - "description": "Project UUID. Optional where an operation can infer or search organization scope; required where the operation creates a project-owned resource." + "description": "Project UUID. Optional where an operation can infer or search team scope; required where the operation creates a project-owned resource." }, "CustomerId": { "type": "string", @@ -21814,7 +20548,7 @@ "expectedPhone": { "type": "string", "nullable": true, - "description": "Optional expected phone, normalized to E.164. Creation checks WhatsApp registration; confirmed nonregistration is rejected and lookup unavailability is retryable." + "description": "Optional expected phone, normalized to E.164. Creation attempts WhatsApp registration resolution; confirmed nonregistration is rejected, while an inconclusive result remains unknown." }, "methods": { "type": "array", @@ -21898,10 +20632,102 @@ "type": "object", "additionalProperties": false, "properties": { - "id": { - "type": "string", - "format": "uuid" - }, + "id": { + "type": "string", + "format": "uuid" + }, + "url": { + "type": "string", + "format": "uri", + "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://", + "minLength": 1, + "maxLength": 2048, + "description": "Public HTTP(S) endpoint. Private, reserved, link-local, multicast, and credential-bearing targets are rejected." + }, + "events": { + "type": "array", + "items": { + "type": "string" + } + }, + "retries": { + "$ref": "#/components/schemas/ConsoleWebhookRetry" + }, + "enabled": { + "type": "boolean" + }, + "format": { + "type": "string", + "enum": [ + "native", + "meta" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "hasSigningSecret": { + "type": "boolean" + } + }, + "required": [ + "id", + "url", + "events", + "retries", + "enabled", + "format", + "createdAt", + "hasSigningSecret" + ] + }, + "CreateConsoleWebhookRequest": { + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "format": "uri", + "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://", + "minLength": 1, + "maxLength": 2048, + "description": "Public HTTP(S) endpoint. Private, reserved, link-local, multicast, and credential-bearing targets are rejected." + }, + "events": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "retries": { + "$ref": "#/components/schemas/ConsoleWebhookRetry" + }, + "signingSecret": { + "type": "string", + "minLength": 16, + "maxLength": 4096, + "writeOnly": true + }, + "format": { + "type": "string", + "enum": [ + "native", + "meta" + ], + "default": "native" + } + }, + "required": [ + "url" + ] + }, + "UpdateConsoleWebhookRequest": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { "url": { "type": "string", "format": "uri", @@ -21919,6 +20745,15 @@ "retries": { "$ref": "#/components/schemas/ConsoleWebhookRetry" }, + "signingSecret": { + "type": "string", + "minLength": 16, + "maxLength": 4096, + "writeOnly": true + }, + "clearSigningSecret": { + "type": "boolean" + }, "enabled": { "type": "boolean" }, @@ -21928,137 +20763,634 @@ "native", "meta" ] - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "hasSigningSecret": { - "type": "boolean" + } + }, + "not": { + "required": [ + "signingSecret", + "clearSigningSecret" + ], + "properties": { + "clearSigningSecret": { + "enum": [ + true + ] + } + } + } + }, + "DeleteConsoleWebhookResult": { + "type": "object", + "additionalProperties": false, + "properties": { + "removed": { + "type": "boolean", + "enum": [ + true + ] } }, "required": [ - "id", - "url", - "events", - "retries", - "enabled", - "format", - "createdAt", - "hasSigningSecret" + "removed" ] }, - "CreateConsoleWebhookRequest": { + "SessionConfigurationView": { "type": "object", - "additionalProperties": false, "properties": { - "url": { + "requestedHistory": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "required": [ + "mode", + "requestFull" + ], + "additionalProperties": false + }, + "historyConsent": { "type": "string", - "format": "uri", - "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://", - "minLength": 1, - "maxLength": 2048, - "description": "Public HTTP(S) endpoint. Private, reserved, link-local, multicast, and credential-bearing targets are rejected." + "enum": [ + "pending", + "accepted", + "declined" + ] }, - "events": { - "type": "array", - "items": { - "type": "string" + "effective": { + "type": "object", + "properties": { + "observation": { + "type": "object", + "properties": { + "presenceMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "typingMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "labelMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache", + "project" + ] + }, + "quickReplyMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + } + }, + "required": [ + "presenceMode", + "typingMode", + "labelMode", + "quickReplyMode" + ], + "additionalProperties": false + }, + "historySync": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "required": [ + "mode", + "requestFull" + ], + "additionalProperties": false + }, + "hms": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + false + ] + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + true + ] + }, + "region": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" + }, + "policy": { + "type": "string", + "enum": [ + "short", + "standard", + "extended", + "compliance", + "enterprise_archive" + ] + }, + "retentionDays": { + "type": "integer", + "minimum": 1, + "maximum": 2555 + }, + "policyVersion": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "legalHold": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "region", + "policy" + ], + "additionalProperties": false + } + ] + } }, - "default": [] + "required": [ + "observation", + "historySync", + "hms" + ], + "additionalProperties": false }, - "retries": { - "$ref": "#/components/schemas/ConsoleWebhookRetry" + "overrides": { + "type": "object", + "properties": { + "historySync": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "hms": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + false + ] + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + true + ] + }, + "region": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" + }, + "policy": { + "type": "string", + "enum": [ + "short", + "standard", + "extended", + "compliance", + "enterprise_archive" + ] + }, + "retentionDays": { + "type": "integer", + "minimum": 1, + "maximum": 2555 + }, + "policyVersion": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "legalHold": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "region", + "policy" + ], + "additionalProperties": false + } + ] + }, + "observation": { + "type": "object", + "properties": { + "presenceMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "typingMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "labelMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache", + "project" + ] + }, + "quickReplyMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "sources": { + "type": "object", + "properties": { + "observation.presenceMode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + }, + "observation.typingMode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + }, + "observation.labelMode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + }, + "observation.quickReplyMode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + }, + "historySync.mode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session", + "consent" + ] + }, + "historySync.requestFull": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session", + "consent" + ] + }, + "hms": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + } + }, + "required": [ + "observation.presenceMode", + "observation.typingMode", + "observation.labelMode", + "observation.quickReplyMode", + "historySync.mode", + "historySync.requestFull", + "hms" + ], + "additionalProperties": false }, - "signingSecret": { - "type": "string", - "minLength": 16, - "maxLength": 4096, - "writeOnly": true + "revisions": { + "type": "object", + "properties": { + "team": { + "type": "integer", + "minimum": 0 + }, + "project": { + "type": "integer", + "minimum": 0 + }, + "session": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "team", + "project", + "session" + ], + "additionalProperties": false }, - "format": { - "type": "string", - "enum": [ - "native", - "meta" + "application": { + "type": "object", + "properties": { + "desiredGeneration": { + "type": "integer", + "minimum": 0 + }, + "appliedGeneration": { + "type": "integer", + "minimum": 0 + }, + "status": { + "type": "string", + "enum": [ + "pending", + "applied" + ] + } + }, + "required": [ + "desiredGeneration", + "appliedGeneration", + "status" ], - "default": "native" + "additionalProperties": false } }, "required": [ - "url" - ] + "requestedHistory", + "effective", + "overrides", + "sources", + "revisions" + ], + "additionalProperties": false }, - "UpdateConsoleWebhookRequest": { + "SessionConfigurationUpdateRequest": { "type": "object", - "additionalProperties": false, - "minProperties": 1, "properties": { - "url": { - "type": "string", - "format": "uri", - "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://", - "minLength": 1, - "maxLength": 2048, - "description": "Public HTTP(S) endpoint. Private, reserved, link-local, multicast, and credential-bearing targets are rejected." - }, - "events": { - "type": "array", - "items": { - "type": "string" - } - }, - "retries": { - "$ref": "#/components/schemas/ConsoleWebhookRetry" - }, - "signingSecret": { + "projectId": { "type": "string", - "minLength": 16, - "maxLength": 4096, - "writeOnly": true - }, - "clearSigningSecret": { - "type": "boolean" + "format": "uuid" }, - "enabled": { - "type": "boolean" + "revision": { + "type": "integer", + "minimum": 0 }, - "format": { - "type": "string", - "enum": [ - "native", - "meta" - ] - } - }, - "not": { - "required": [ - "signingSecret", - "clearSigningSecret" - ], - "properties": { - "clearSigningSecret": { - "enum": [ - true - ] - } - } - } - }, - "DeleteConsoleWebhookResult": { - "type": "object", - "additionalProperties": false, - "properties": { - "removed": { - "type": "boolean", - "enum": [ - true - ] + "configuration": { + "type": "object", + "properties": { + "set": { + "type": "object", + "properties": { + "historySync": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "hms": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + false + ] + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + true + ] + }, + "region": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" + }, + "policy": { + "type": "string", + "enum": [ + "short", + "standard", + "extended", + "compliance", + "enterprise_archive" + ] + }, + "retentionDays": { + "type": "integer", + "minimum": 1, + "maximum": 2555 + }, + "policyVersion": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "legalHold": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "region", + "policy" + ], + "additionalProperties": false + } + ] + }, + "observation": { + "type": "object", + "properties": { + "presenceMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "typingMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "labelMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache", + "project" + ] + }, + "quickReplyMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "reset": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "historySync", + "historySync.mode", + "historySync.requestFull", + "hms", + "observation", + "observation.presenceMode", + "observation.typingMode", + "observation.labelMode", + "observation.quickReplyMode" + ] + } + } + }, + "additionalProperties": false } }, "required": [ - "removed" - ] + "revision", + "configuration" + ], + "additionalProperties": false }, "QuickLinkSettings": { "type": "object", @@ -22240,7 +21572,7 @@ "UpdateQuickLinkSettingsRequest": { "type": "object", "additionalProperties": false, - "description": "QuickLink settings for the organization or one project. Omitted fields keep their saved value; `null` clears an optional field.", + "description": "QuickLink settings for the team or one project. Omitted fields keep their saved value; `null` clears an optional field.", "properties": { "projectId": { "$ref": "#/components/schemas/ProjectId" @@ -22721,6 +22053,102 @@ } }, "SessionTierOverrideRequest": { + "type": "object", + "additionalProperties": false, + "properties": { + "projectId": { + "$ref": "#/components/schemas/ProjectId" + }, + "quoteId": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "quoteId" + ] + }, + "NumberTierChange": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "quoted", + "queued", + "applied", + "rejected" + ] + }, + "failureReason": { + "type": "string", + "nullable": true + }, + "expiresAtMs": { + "type": "integer" + }, + "quote": { + "type": "object", + "additionalProperties": false, + "properties": { + "tier": { + "type": "string" + }, + "tierOverride": { + "type": "string", + "nullable": true + }, + "amountCents": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001, + "description": "Credits charged on application, with up to six decimal places." + }, + "priceVersion": { + "type": "string" + }, + "action": { + "type": "string", + "enum": [ + "upgrade", + "downgrade", + "configure" + ] + }, + "effectiveAtMs": { + "type": "integer" + }, + "replacesWindowId": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "tier", + "tierOverride", + "amountCents", + "priceVersion", + "action", + "effectiveAtMs", + "replacesWindowId" + ] + } + }, + "required": [ + "id", + "status", + "failureReason", + "expiresAtMs", + "quote" + ] + }, + "NumberTierQuoteRequest": { "type": "object", "additionalProperties": false, "properties": { @@ -22757,30 +22185,10 @@ "items": { "$ref": "#/components/schemas/SessionIdentifier" } - }, - "ids": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "uniqueItems": true, - "deprecated": true, - "description": "Compatibility alias for sessionIds.", - "items": { - "$ref": "#/components/schemas/SessionIdentifier" - } } }, - "anyOf": [ - { - "required": [ - "sessionIds" - ] - }, - { - "required": [ - "ids" - ] - } + "required": [ + "sessionIds" ] }, "CreateTestingNumberRequest": { @@ -23154,6 +22562,11 @@ "nullable": true, "description": "Last activity time as Unix epoch milliseconds." }, + "paidUntil": { + "type": "number", + "nullable": true, + "description": "End of the charged rolling 24-hour window as Unix epoch milliseconds. Null when no charged window exists; expired windows retain their expiry." + }, "customer": { "type": "object", "nullable": true, @@ -23244,6 +22657,7 @@ "statusReason", "messageCount", "lastActiveAt", + "paidUntil", "customer", "claim" ] @@ -23316,619 +22730,1124 @@ "type": "integer", "minimum": 0, "maximum": 100 - } - }, - "required": [ - "stopping" - ] - }, - "SessionBatchRemoveResult": { - "type": "object", - "additionalProperties": false, - "properties": { - "removed": { + } + }, + "required": [ + "stopping" + ] + }, + "SessionBatchRemoveResult": { + "type": "object", + "additionalProperties": false, + "properties": { + "removed": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + }, + "required": [ + "removed" + ] + }, + "TestingSessionId": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Opaque Number ID. Keep it as a string; use testMode to identify Test Numbers." + }, + "ProductionEnrollmentRequest": { + "type": "object", + "additionalProperties": false, + "properties": { + "business": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 2, + "maxLength": 120 + }, + "website": { + "type": "string", + "format": "uri", + "pattern": "^https://", + "maxLength": 2048 + }, + "supportEmail": { + "type": "string", + "format": "email", + "maxLength": 320 + } + }, + "required": [ + "name", + "website", + "supportEmail" + ] + } + }, + "required": [ + "business" + ] + }, + "ProductionEnrollmentResult": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/ProjectId" + }, + "orgId": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "stage": { + "type": "string", + "enum": [ + "development" + ] + }, + "operationId": { + "type": "string", + "format": "uuid" + }, + "enrollmentStatus": { + "type": "string", + "enum": [ + "requested", + "approval_required", + "provisioning", + "ready" + ] + } + }, + "required": [ + "id", + "orgId", + "name", + "slug", + "stage", + "operationId", + "enrollmentStatus" + ] + }, + "ProductionEnrollmentCommandResult": { + "type": "object", + "additionalProperties": false, + "properties": { + "operationId": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string", + "enum": [ + "approve", + "cancel" + ] + }, + "accepted": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "operationId", + "action", + "accepted" + ] + }, + "CursorPageMetadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "nextCursor": { + "type": "string", + "nullable": true + }, + "hasMore": { + "type": "boolean" + } + }, + "required": [ + "nextCursor", + "hasMore" + ] + }, + "BanSafeHealthProbabilities": { + "type": "object", + "additionalProperties": false, + "properties": { + "healthy": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "limited": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "restricted": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "banned": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "required": [ + "healthy", + "limited", + "restricted", + "banned" + ] + }, + "BanSafeObservedAccountState": { + "type": "object", + "additionalProperties": false, + "description": "Direct account-state evidence. This is not an ML estimate.", + "properties": { + "state": { + "type": "string", + "enum": [ + "healthy", + "limited", + "restricted", + "banned" + ] + }, + "observedAt": { + "type": "string", + "format": "date-time" + }, + "source": { + "type": "string", + "enum": [ + "account_check", + "restriction_event" + ] + } + }, + "required": [ + "state", + "observedAt", + "source" + ] + }, + "BanSafeHealthExplanationFactor": { + "type": "object", + "additionalProperties": false, + "description": "One factual rules-v1 input. penalty is the number of Health points this input subtracts.", + "properties": { + "group": { + "type": "string", + "enum": [ + "direct_condition", + "delivery", + "connection", + "conduct", + "cadence" + ] + }, + "key": { + "type": "string", + "enum": [ + "observed_limited", + "observed_restricted", + "observed_banned", + "send_failures", + "receipt_errors", + "server_ack_tail", + "connection_failures", + "connection_churn", + "connection_disconnected", + "auto_reconnect_errors", + "keep_alive_errors", + "unanswered_first_contacts", + "unsolicited_sends", + "send_burst", + "fixed_send_rhythm" + ] + }, + "penalty": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "observedValue": { + "type": "number" + }, + "sampleSize": { "type": "integer", - "minimum": 0, - "maximum": 100 + "minimum": 0 } }, "required": [ - "removed" + "group", + "key", + "penalty", + "observedValue", + "sampleSize" ] }, - "TestingSessionId": { - "type": "string", - "pattern": "^[1-9][0-9]{0,18}$", - "description": "Opaque Number ID. Keep it as a string; use testMode to identify Test Numbers." - }, - "ProductionEnrollmentRequest": { + "BanSafeHealthExplanation": { "type": "object", "additionalProperties": false, + "description": "Bounded rules-v1 inputs and deductions. It is not a forecast or confidence score.", "properties": { - "business": { + "penalties": { "type": "object", "additionalProperties": false, "properties": { - "name": { - "type": "string", - "minLength": 2, - "maxLength": 120 + "conduct": { + "type": "number", + "minimum": 0, + "maximum": 100 }, - "website": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 + "delivery": { + "type": "number", + "minimum": 0, + "maximum": 100 }, - "supportEmail": { - "type": "string", - "format": "email", - "maxLength": 320 + "connection": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "restriction": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "total": { + "type": "number", + "minimum": 0, + "maximum": 100 } }, "required": [ - "name", - "website", - "supportEmail" + "conduct", + "delivery", + "connection", + "restriction", + "total" ] + }, + "factors": { + "type": "array", + "maxItems": 8, + "items": { + "$ref": "#/components/schemas/BanSafeHealthExplanationFactor" + } + }, + "measuredGroups": { + "type": "array", + "maxItems": 5, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "direct_condition", + "delivery", + "connection", + "conduct", + "cadence" + ] + } + }, + "missingGroups": { + "type": "array", + "maxItems": 5, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "direct_condition", + "delivery", + "connection", + "conduct", + "cadence" + ] + } } }, "required": [ - "business" + "penalties", + "factors", + "measuredGroups", + "missingGroups" ] }, - "ProductionEnrollmentResult": { + "BanSafeSignalDefinition": { "type": "object", "additionalProperties": false, "properties": { - "id": { - "$ref": "#/components/schemas/ProjectId" - }, - "orgId": { - "type": "string", - "format": "uuid" + "key": { + "type": "string" }, - "name": { + "label": { "type": "string" }, - "slug": { + "group": { "type": "string" }, - "stage": { + "kind": { "type": "string", "enum": [ - "development" + "number", + "boolean", + "enum", + "histogram", + "code_counts" ] }, - "operationId": { - "type": "string", - "format": "uuid" - }, - "enrollmentStatus": { + "unit": { "type": "string", "enum": [ - "requested", - "approval_required", - "provisioning", - "ready" + "count", + "milliseconds", + "unix_milliseconds", + "ratio", + "none" ] + }, + "description": { + "type": "string" } }, "required": [ - "id", - "orgId", - "name", - "slug", - "stage", - "operationId", - "enrollmentStatus" + "key", + "label", + "group", + "kind", + "unit", + "description" ] }, - "ProductionEnrollmentCommandResult": { + "BanSafeSignalCodeCount": { "type": "object", "additionalProperties": false, "properties": { - "operationId": { - "type": "string", - "format": "uuid" - }, - "action": { - "type": "string", - "enum": [ - "approve", - "cancel" - ] + "code": { + "type": "integer", + "minimum": 0 }, - "accepted": { - "type": "boolean", - "enum": [ - true - ] + "count": { + "type": "integer", + "minimum": 0 } }, "required": [ - "operationId", - "action", - "accepted" + "code", + "count" ] }, - "CursorPageMetadata": { + "BanSafeSignal": { "type": "object", "additionalProperties": false, "properties": { - "nextCursor": { + "key": { + "type": "string" + }, + "label": { + "type": "string" + }, + "group": { + "type": "string" + }, + "kind": { "type": "string", - "nullable": true + "enum": [ + "number", + "boolean", + "enum", + "histogram", + "code_counts" + ] }, - "hasMore": { + "unit": { + "type": "string", + "enum": [ + "count", + "milliseconds", + "unix_milliseconds", + "ratio", + "none" + ] + }, + "description": { + "type": "string" + }, + "measured": { "type": "boolean" - } - }, - "required": [ - "nextCursor", - "hasMore" - ] - }, - "BanSafeHealthProbabilities": { - "type": "object", - "additionalProperties": false, - "properties": { - "healthy": { - "type": "number", - "minimum": 0, - "maximum": 1 }, - "limited": { - "type": "number", - "minimum": 0, - "maximum": 1 + "value": { + "nullable": true, + "oneOf": [ + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "number" + } + } + ] }, - "restricted": { - "type": "number", + "sampleSize": { + "type": "integer", "minimum": 0, - "maximum": 1 + "nullable": true }, - "banned": { - "type": "number", - "minimum": 0, - "maximum": 1 + "codes": { + "type": "array", + "nullable": true, + "items": { + "$ref": "#/components/schemas/BanSafeSignalCodeCount" + } } }, "required": [ - "healthy", - "limited", - "restricted", - "banned" + "key", + "label", + "group", + "kind", + "unit", + "description", + "measured", + "value", + "sampleSize", + "codes" ] }, - "BanSafeObservedAccountState": { + "BanSafeCollectionStatus": { "type": "object", "additionalProperties": false, - "description": "Direct account-state evidence. This is not an ML estimate.", "properties": { "state": { "type": "string", "enum": [ - "healthy", - "limited", - "restricted", - "banned" + "fresh", + "stale", + "not_collected", + "unsupported" ] }, - "observedAt": { + "latestFlushedAt": { "type": "string", - "format": "date-time" + "format": "date-time", + "nullable": true }, - "source": { + "latestReceivedAt": { "type": "string", - "enum": [ - "account_check", - "restriction_event" - ] + "format": "date-time", + "nullable": true + }, + "freshUntil": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "recordVersion": { + "type": "integer", + "minimum": 0, + "nullable": true + }, + "collectorVersion": { + "type": "integer", + "minimum": 0, + "nullable": true + }, + "partial": { + "type": "boolean", + "nullable": true + }, + "droppedRecords": { + "type": "integer", + "minimum": 0, + "nullable": true } }, "required": [ "state", - "observedAt", - "source" + "latestFlushedAt", + "latestReceivedAt", + "freshUntil", + "recordVersion", + "collectorVersion", + "partial", + "droppedRecords" ] }, - "BanSafeHealthExplanationFactor": { + "BanSafeTelemetrySnapshot": { "type": "object", "additionalProperties": false, - "description": "One factual rules-v1 input. penalty is the number of Health points this input subtracts.", "properties": { - "group": { + "bucketStart": { "type": "string", - "enum": [ - "direct_condition", - "delivery", - "connection", - "conduct", - "cadence" - ] + "format": "date-time" }, - "key": { + "flushedAt": { "type": "string", - "enum": [ - "observed_limited", - "observed_restricted", - "observed_banned", - "send_failures", - "receipt_errors", - "server_ack_tail", - "connection_failures", - "connection_churn", - "connection_disconnected", - "auto_reconnect_errors", - "keep_alive_errors", - "unanswered_first_contacts", - "unsolicited_sends", - "send_burst", - "fixed_send_rhythm" - ] + "format": "date-time" }, - "penalty": { - "type": "number", - "minimum": 0, - "maximum": 100 + "receivedAt": { + "type": "string", + "format": "date-time" }, - "observedValue": { - "type": "number" + "partial": { + "type": "boolean" }, - "sampleSize": { + "recordVersion": { "type": "integer", - "minimum": 0 + "minimum": 0, + "nullable": true + }, + "signals": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeSignal" + } } }, "required": [ - "group", - "key", - "penalty", - "observedValue", - "sampleSize" + "bucketStart", + "flushedAt", + "receivedAt", + "partial", + "recordVersion", + "signals" ] }, - "BanSafeHealthExplanation": { + "BanSafeCollectionSession": { "type": "object", "additionalProperties": false, - "description": "Bounded rules-v1 inputs and deductions. It is not a forecast or confidence score.", "properties": { - "penalties": { - "type": "object", - "additionalProperties": false, - "properties": { - "conduct": { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - "delivery": { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - "connection": { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - "restriction": { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - "total": { - "type": "number", - "minimum": 0, - "maximum": 100 - } - }, - "required": [ - "conduct", - "delivery", - "connection", - "restriction", - "total" - ] + "sessionId": { + "type": "string", + "format": "uuid" }, - "factors": { - "type": "array", - "maxItems": 8, - "items": { - "$ref": "#/components/schemas/BanSafeHealthExplanationFactor" - } + "session": { + "type": "string" }, - "measuredGroups": { - "type": "array", - "maxItems": 5, - "uniqueItems": true, - "items": { - "type": "string", - "enum": [ - "direct_condition", - "delivery", - "connection", - "conduct", - "cadence" - ] - } + "projectId": { + "type": "string", + "format": "uuid" }, - "missingGroups": { - "type": "array", - "maxItems": 5, - "uniqueItems": true, - "items": { - "type": "string", - "enum": [ - "direct_condition", - "delivery", - "connection", - "conduct", - "cadence" - ] - } + "collection": { + "$ref": "#/components/schemas/BanSafeCollectionStatus" } }, "required": [ - "penalties", - "factors", - "measuredGroups", - "missingGroups" + "sessionId", + "session", + "projectId", + "collection" ] }, - "BanSafeSignalDefinition": { + "BanSafeTelemetryDetail": { "type": "object", "additionalProperties": false, "properties": { - "key": { - "type": "string" + "sessionId": { + "type": "string", + "format": "uuid" }, - "label": { + "session": { "type": "string" }, - "group": { + "projectId": { + "type": "string", + "format": "uuid" + }, + "collection": { + "$ref": "#/components/schemas/BanSafeCollectionStatus" + }, + "snapshot": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/BanSafeTelemetrySnapshot" + } + ] + } + }, + "required": [ + "sessionId", + "session", + "projectId", + "collection", + "snapshot" + ] + }, + "BanSafeHealthAction": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "sessionId": { + "type": "string", + "format": "uuid" + }, + "session": { "type": "string" }, - "kind": { + "projectId": { + "type": "string", + "format": "uuid" + }, + "mode": { "type": "string", "enum": [ - "number", - "boolean", - "enum", - "histogram", - "code_counts" + "apply", + "clear" ] }, - "unit": { + "action": { "type": "string", "enum": [ - "count", - "milliseconds", - "unix_milliseconds", - "ratio", - "none" + "stop", + "slow_down", + "log_out", + "email", + "webhook" ] }, - "description": { + "status": { + "type": "string", + "enum": [ + "pending", + "running", + "succeeded", + "failed", + "cancelled" + ] + }, + "health": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "threshold": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "healthSource": { + "type": "string", + "enum": [ + "rules_v1", + "ml_model" + ] + }, + "estimatorVersion": { "type": "string" + }, + "modelVersion": { + "type": "string", + "nullable": true + }, + "slowDownMps": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true, + "nullable": true + }, + "evaluatedAt": { + "type": "string", + "format": "date-time" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "completedAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "outcome": { + "type": "string", + "nullable": true, + "enum": [ + "applied", + "cleared", + "notification_queued", + "superseded", + "expired", + "not_applied", + "delivery_failed", + null + ] } }, "required": [ - "key", - "label", - "group", - "kind", - "unit", - "description" + "id", + "sessionId", + "session", + "projectId", + "mode", + "action", + "status", + "health", + "threshold", + "healthSource", + "estimatorVersion", + "modelVersion", + "slowDownMps", + "evaluatedAt", + "createdAt", + "completedAt", + "outcome" ] }, - "BanSafeSignalCodeCount": { + "BanSafeNumberEnforcement": { "type": "object", "additionalProperties": false, "properties": { - "code": { + "rung": { + "type": "string", + "enum": [ + "none", + "notify", + "throttle", + "block_cold", + "suspend" + ] + }, + "previousRung": { + "type": "string", + "enum": [ + "none", + "notify", + "throttle", + "block_cold", + "suspend" + ] + }, + "organizationFloor": { + "type": "string", + "enum": [ + "none", + "notify", + "throttle", + "block_cold", + "suspend" + ], + "description": "The lowest restriction every number in this workspace is held to." + }, + "reason": { + "type": "string" + }, + "source": { + "type": "string", + "enum": [ + "automatic", + "operator" + ] + }, + "throughputPerMinute": { + "type": "number", + "nullable": true, + "description": "Messages per minute this number is paced to, or null when it is not paced." + }, + "blocksUnsolicited": { + "type": "boolean", + "description": "First messages to people who never wrote to this number are refused outside Campaigns." + }, + "suspended": { + "type": "boolean", + "description": "This number cannot originate a message. It still receives, reads, and shows presence." + }, + "startedAt": { + "type": "string", + "format": "date-time" + }, + "eligibleLiftAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "exitProgress": { "type": "integer", - "minimum": 0 + "minimum": 0, + "description": "Consecutive checks already passed toward lifting." + }, + "blockingFindings": { + "type": "array", + "items": { + "type": "string" + } + }, + "operatorHold": { + "type": "boolean" + }, + "appealState": { + "type": "string", + "enum": [ + "none", + "requested", + "granted", + "denied" + ] }, - "count": { - "type": "integer", - "minimum": 0 + "state": { + "type": "string", + "enum": [ + "applied", + "applying" + ], + "description": "`applying` while the number has not confirmed the restriction." } }, "required": [ - "code", - "count" + "rung", + "previousRung", + "organizationFloor", + "reason", + "source", + "throughputPerMinute", + "blocksUnsolicited", + "suspended", + "startedAt", + "eligibleLiftAt", + "exitProgress", + "blockingFindings", + "operatorHold", + "appealState", + "state" ] }, - "BanSafeSignal": { + "BanSafeNumber": { "type": "object", "additionalProperties": false, "properties": { - "key": { - "type": "string" + "sessionId": { + "type": "string", + "format": "uuid" }, - "label": { + "session": { "type": "string" }, - "group": { + "phoneNumber": { "type": "string" }, - "kind": { + "projectId": { "type": "string", - "enum": [ - "number", - "boolean", - "enum", - "histogram", - "code_counts" - ] + "format": "uuid" }, - "unit": { + "health": { + "type": "number", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "Estimate of present account Health. Higher is healthier. Read healthSource, healthReliability, and healthFeatureCoverage with it." + }, + "band": { "type": "string", "enum": [ - "count", - "milliseconds", - "unix_milliseconds", - "ratio", - "none" + "good", + "fair", + "poor", + "failing", + "unknown" ] }, - "description": { - "type": "string" + "healthSource": { + "type": "string", + "enum": [ + "rules_v1", + "ml_model", + "unavailable" + ], + "description": "Estimator family. rules_v1 is deterministic; unavailable carries no numeric Health." }, - "measured": { - "type": "boolean" + "healthEstimatorVersion": { + "type": "string", + "nullable": true, + "description": "Version of the estimator that produced Health." }, - "value": { + "healthModelVersion": { + "type": "string", "nullable": true, - "oneOf": [ - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "number" - } - } - ] + "description": "Learned-model artifact version. Null for rules_v1." }, - "sampleSize": { - "type": "integer", + "healthEvaluatedAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "healthFeatureCoverage": { + "type": "number", "minimum": 0, + "maximum": 1, "nullable": true }, - "codes": { - "type": "array", - "nullable": true, - "items": { - "$ref": "#/components/schemas/BanSafeSignalCodeCount" - } - } - }, - "required": [ - "key", - "label", - "group", - "kind", - "unit", - "description", - "measured", - "value", - "sampleSize", - "codes" - ] - }, - "BanSafeCollectionStatus": { - "type": "object", - "additionalProperties": false, - "properties": { - "state": { + "healthReliability": { "type": "string", "enum": [ - "fresh", - "stale", - "not_collected", - "unsupported" - ] + "rules_based", + "validated", + "unavailable" + ], + "description": "Support category, not a confidence score." }, - "latestFlushedAt": { + "healthUnavailableReason": { "type": "string", - "format": "date-time", - "nullable": true + "nullable": true, + "enum": [ + "no_active_model", + "invalid_active_model", + "insufficient_fresh_features", + null + ] }, - "latestReceivedAt": { - "type": "string", - "format": "date-time", - "nullable": true + "healthProbabilities": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/BanSafeHealthProbabilities" + } + ] }, - "freshUntil": { + "mostLikelyHealthState": { "type": "string", - "format": "date-time", - "nullable": true - }, - "recordVersion": { - "type": "integer", - "minimum": 0, + "enum": [ + "healthy", + "limited", + "restricted", + "banned", + null + ], "nullable": true }, - "collectorVersion": { - "type": "integer", - "minimum": 0, - "nullable": true + "healthExplanation": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/BanSafeHealthExplanation" + } + ] }, - "partial": { - "type": "boolean", - "nullable": true + "observedAccountState": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/BanSafeObservedAccountState" + } + ] }, - "droppedRecords": { - "type": "integer", - "minimum": 0, - "nullable": true + "enforcement": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/BanSafeNumberEnforcement" + } + ] } }, "required": [ - "state", - "latestFlushedAt", - "latestReceivedAt", - "freshUntil", - "recordVersion", - "collectorVersion", - "partial", - "droppedRecords" + "sessionId", + "session", + "phoneNumber", + "projectId", + "health", + "band", + "healthSource", + "healthEstimatorVersion", + "healthModelVersion", + "healthEvaluatedAt", + "healthFeatureCoverage", + "healthReliability", + "healthUnavailableReason", + "healthProbabilities", + "mostLikelyHealthState", + "healthExplanation", + "observedAccountState", + "enforcement" ] }, - "BanSafeTelemetrySnapshot": { + "BanSafeFinding": { "type": "object", "additionalProperties": false, + "description": "One safety finding, with the sentences to show a customer. A `not_measured` entry has no stored lifecycle and reports null timestamps; it is never reported as resolved and never omitted.", "properties": { - "bucketStart": { + "id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "key": { "type": "string", - "format": "date-time" + "maxLength": 64 }, - "flushedAt": { + "title": { "type": "string", - "format": "date-time" + "description": "Customer-visible name of the check. A `not_measured` entry carries the same title as a stored one." }, - "receivedAt": { + "summary": { "type": "string", - "format": "date-time" + "description": "The evidence sentence for this finding, with the numbers from `evidence` already filled in. A number the check could not read reads as `unknown`, and a `not_measured` entry therefore reads as the sentence with no numbers in it. Empty for a key Polymorfa has retired." }, - "partial": { - "type": "boolean" + "fix": { + "type": "string", + "description": "What to change to clear the finding. Empty for a key Polymorfa has retired." }, - "recordVersion": { - "type": "integer", - "minimum": 0, + "status": { + "type": "string", + "enum": [ + "open", + "acknowledged", + "resolved", + "not_measured" + ] + }, + "severity": { + "type": "string", + "enum": [ + "info", + "warning", + "critical", + null + ], "nullable": true }, - "signals": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeSignal" - } - } - }, - "required": [ - "bucketStart", - "flushedAt", - "receivedAt", - "partial", - "recordVersion", - "signals" - ] - }, - "BanSafeCollectionSession": { - "type": "object", - "additionalProperties": false, - "properties": { + "occurrences": { + "type": "integer", + "minimum": 0 + }, + "reopenedCount": { + "type": "integer", + "minimum": 0 + }, + "evidence": { + "type": "object", + "additionalProperties": true, + "description": "Numbers only. Never message content, a message identifier, or a recipient identifier." + }, "sessionId": { "type": "string", "format": "uuid" @@ -23936,321 +23855,332 @@ "session": { "type": "string" }, - "projectId": { + "phoneNumber": { + "type": "string" + }, + "firstSeenAt": { "type": "string", - "format": "uuid" + "format": "date-time", + "nullable": true }, - "collection": { - "$ref": "#/components/schemas/BanSafeCollectionStatus" - } - }, - "required": [ - "sessionId", - "session", - "projectId", - "collection" - ] - }, - "BanSafeTelemetryDetail": { - "type": "object", - "additionalProperties": false, - "properties": { - "sessionId": { + "lastSeenAt": { "type": "string", - "format": "uuid" + "format": "date-time", + "nullable": true }, - "session": { - "type": "string" + "acknowledgedAt": { + "type": "string", + "format": "date-time", + "nullable": true }, - "projectId": { + "acknowledgedBy": { "type": "string", - "format": "uuid" + "nullable": true }, - "collection": { - "$ref": "#/components/schemas/BanSafeCollectionStatus" + "acknowledgementNote": { + "type": "string", + "nullable": true, + "maxLength": 500 }, - "snapshot": { + "snoozedUntil": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolvedAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolveReason": { + "type": "string", "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/BanSafeTelemetrySnapshot" - } + "enum": [ + "clean", + "key_retired", + "number_removed", + "stale", + null ] } }, "required": [ + "id", + "key", + "title", + "summary", + "fix", + "status", + "severity", + "occurrences", + "reopenedCount", + "evidence", "sessionId", "session", - "projectId", - "collection", - "snapshot" + "phoneNumber", + "firstSeenAt", + "lastSeenAt", + "acknowledgedAt", + "acknowledgedBy", + "acknowledgementNote", + "snoozedUntil", + "resolvedAt", + "resolveReason" ] }, - "BanSafeHealthAction": { + "BanSafeNumberDetail": { "type": "object", "additionalProperties": false, "properties": { - "id": { - "type": "string", - "format": "uuid" + "health": { + "type": "number", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "Estimate of present account Health. Higher is healthier. Read healthSource, healthReliability, and healthFeatureCoverage with it." }, - "sessionId": { + "band": { "type": "string", - "format": "uuid" - }, - "session": { - "type": "string" + "enum": [ + "good", + "fair", + "poor", + "failing", + "unknown" + ] }, - "projectId": { + "healthSource": { "type": "string", - "format": "uuid" + "enum": [ + "rules_v1", + "ml_model", + "unavailable" + ], + "description": "Estimator family. rules_v1 is deterministic; unavailable carries no numeric Health." }, - "mode": { + "healthEstimatorVersion": { "type": "string", - "enum": [ - "apply", - "clear" - ] + "nullable": true, + "description": "Version of the estimator that produced Health." }, - "action": { + "healthModelVersion": { "type": "string", - "enum": [ - "stop", - "slow_down", - "log_out", - "email", - "webhook" - ] + "nullable": true, + "description": "Learned-model artifact version. Null for rules_v1." }, - "status": { + "healthEvaluatedAt": { "type": "string", - "enum": [ - "pending", - "running", - "succeeded", - "failed", - "cancelled" - ] + "format": "date-time", + "nullable": true }, - "health": { + "healthFeatureCoverage": { "type": "number", "minimum": 0, - "maximum": 100 + "maximum": 1, + "nullable": true }, - "threshold": { - "type": "number", - "minimum": 0, - "maximum": 100 + "healthReliability": { + "type": "string", + "enum": [ + "rules_based", + "validated", + "unavailable" + ], + "description": "Support category, not a confidence score." }, - "healthSource": { + "healthUnavailableReason": { "type": "string", + "nullable": true, "enum": [ - "rules_v1", - "ml_model" + "no_active_model", + "invalid_active_model", + "insufficient_fresh_features", + null ] }, - "estimatorVersion": { - "type": "string" + "healthProbabilities": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/BanSafeHealthProbabilities" + } + ] }, - "modelVersion": { + "mostLikelyHealthState": { "type": "string", + "enum": [ + "healthy", + "limited", + "restricted", + "banned", + null + ], "nullable": true }, - "slowDownMps": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true, - "nullable": true + "healthExplanation": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/BanSafeHealthExplanation" + } + ] }, - "evaluatedAt": { - "type": "string", - "format": "date-time" + "observedAccountState": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/BanSafeObservedAccountState" + } + ] }, - "createdAt": { - "type": "string", - "format": "date-time" + "warmup": { + "$ref": "#/components/schemas/BanSafeNumberWarmup" }, - "completedAt": { - "type": "string", - "format": "date-time", - "nullable": true + "findings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeFinding" + } }, - "outcome": { + "liftRequires": { "type": "string", "nullable": true, + "description": "The exact remaining condition for the restriction to lift." + }, + "appealState": { + "type": "string", "enum": [ - "applied", - "cleared", - "notification_queued", - "superseded", - "expired", - "not_applied", - "delivery_failed", - null + "none", + "requested", + "granted", + "denied" ] } }, "required": [ - "id", "sessionId", "session", + "phoneNumber", "projectId", - "mode", - "action", - "status", "health", - "threshold", + "band", "healthSource", - "estimatorVersion", - "modelVersion", - "slowDownMps", - "evaluatedAt", - "createdAt", - "completedAt", - "outcome" + "healthEstimatorVersion", + "healthModelVersion", + "healthEvaluatedAt", + "healthFeatureCoverage", + "healthReliability", + "healthUnavailableReason", + "healthProbabilities", + "mostLikelyHealthState", + "healthExplanation", + "observedAccountState", + "enforcement", + "warmup", + "findings", + "liftRequires", + "appealState" ] }, - "BanSafeNumberEnforcement": { + "BanSafeNumberWarmup": { "type": "object", "additionalProperties": false, "properties": { - "rung": { - "type": "string", - "enum": [ - "none", - "notify", - "throttle", - "block_cold", - "suspend" - ] - }, - "previousRung": { - "type": "string", - "enum": [ - "none", - "notify", - "throttle", - "block_cold", - "suspend" - ] + "enabled": { + "type": "boolean" }, - "organizationFloor": { + "tenureSource": { "type": "string", "enum": [ - "none", - "notify", - "throttle", - "block_cold", - "suspend" + "history", + "link", + "plan", + null ], - "description": "The lowest restriction every number in this workspace is held to." - }, - "reason": { - "type": "string" - }, - "source": { - "type": "string", - "enum": [ - "automatic", - "operator" - ] - }, - "throughputPerMinute": { - "type": "number", "nullable": true, - "description": "Messages per minute this number is paced to, or null when it is not paced." + "description": "Retained evidence anchoring the ramp; null when legacy evidence cannot be classified." }, - "blocksUnsolicited": { - "type": "boolean", - "description": "First messages to people who never wrote to this number are refused outside Campaigns." + "tenureDay": { + "type": "integer", + "minimum": 0, + "description": "Whole days since the number's known tenure lower bound; zero when unknown." }, - "suspended": { - "type": "boolean", - "description": "This number cannot originate a message. It still receives, reads, and shows presence." + "allowance": { + "type": "integer", + "minimum": 0, + "nullable": true, + "description": "Messages allowed today; null when the plan is disabled." }, - "startedAt": { - "type": "string", - "format": "date-time" + "sentToday": { + "type": "integer", + "minimum": 0, + "nullable": true, + "description": "Durable reservations in the active allowance window. Successful current reads return 0 before the first reservation; null remains accepted for older stored responses." }, - "eligibleLiftAt": { + "resetsAt": { "type": "string", "format": "date-time", "nullable": true }, - "exitProgress": { - "type": "integer", - "minimum": 0, - "description": "Consecutive checks already passed toward lifting." - }, - "blockingFindings": { + "curve": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/WarmupCurvePoint" } - }, - "operatorHold": { - "type": "boolean" - }, - "appealState": { - "type": "string", - "enum": [ - "none", - "requested", - "granted", - "denied" - ] - }, - "state": { - "type": "string", - "enum": [ - "applied", - "applying" - ], - "description": "`applying` while the number has not confirmed the restriction." } }, "required": [ - "rung", - "previousRung", - "organizationFloor", - "reason", - "source", - "throughputPerMinute", - "blocksUnsolicited", - "suspended", - "startedAt", - "eligibleLiftAt", - "exitProgress", - "blockingFindings", - "operatorHold", - "appealState", - "state" + "enabled", + "tenureSource", + "tenureDay", + "allowance", + "sentToday", + "resetsAt", + "curve" ] }, - "BanSafeNumber": { + "ProjectInsuranceEvidence": { "type": "object", "additionalProperties": false, "properties": { - "sessionId": { - "type": "string", - "format": "uuid" - }, - "session": { - "type": "string" - }, - "phoneNumber": { - "type": "string" - }, "projectId": { "type": "string", "format": "uuid" }, + "enabled": { + "type": "boolean" + }, + "banInsuranceIncluded": { + "type": "boolean" + } + }, + "required": [ + "projectId", + "enabled", + "banInsuranceIncluded" + ] + }, + "UpdateProjectInsuranceEvidenceInput": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + }, + "BanSafeHealthPoint": { + "type": "object", + "additionalProperties": false, + "properties": { "health": { "type": "number", "minimum": 0, "maximum": 100, - "nullable": true, - "description": "Estimate of present account Health. Higher is healthier. Read healthSource, healthReliability, and healthFeatureCoverage with it." + "nullable": true }, "band": { "type": "string", @@ -24259,8 +24189,10 @@ "fair", "poor", "failing", - "unknown" - ] + "unknown", + null + ], + "nullable": true }, "healthSource": { "type": "string", @@ -24268,23 +24200,19 @@ "rules_v1", "ml_model", "unavailable" - ], - "description": "Estimator family. rules_v1 is deterministic; unavailable carries no numeric Health." + ] }, "healthEstimatorVersion": { "type": "string", - "nullable": true, - "description": "Version of the estimator that produced Health." + "nullable": true }, "healthModelVersion": { "type": "string", - "nullable": true, - "description": "Learned-model artifact version. Null for rules_v1." + "nullable": true }, "healthEvaluatedAt": { "type": "string", - "format": "date-time", - "nullable": true + "format": "date-time" }, "healthFeatureCoverage": { "type": "number", @@ -24298,8 +24226,7 @@ "rules_based", "validated", "unavailable" - ], - "description": "Support category, not a confidence score." + ] }, "healthUnavailableReason": { "type": "string", @@ -24345,21 +24272,9 @@ "$ref": "#/components/schemas/BanSafeObservedAccountState" } ] - }, - "enforcement": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/BanSafeNumberEnforcement" - } - ] } }, "required": [ - "sessionId", - "session", - "phoneNumber", - "projectId", "health", "band", "healthSource", @@ -24372,68 +24287,54 @@ "healthProbabilities", "mostLikelyHealthState", "healthExplanation", - "observedAccountState", - "enforcement" + "observedAccountState" ] }, - "BanSafeFinding": { + "BanSafeHealthHistory": { "type": "object", "additionalProperties": false, - "description": "One safety finding, with the sentences to show a customer. A `not_measured` entry has no stored lifecycle and reports null timestamps; it is never reported as resolved and never omitted.", "properties": { - "id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "key": { - "type": "string", - "maxLength": 64 - }, - "title": { - "type": "string", - "description": "Customer-visible name of the check. A `not_measured` entry carries the same title as a stored one." - }, - "summary": { + "sessionId": { "type": "string", - "description": "The evidence sentence for this finding, with the numbers from `evidence` already filled in. A number the check could not read reads as `unknown`, and a `not_measured` entry therefore reads as the sentence with no numbers in it. Empty for a key Polymorfa has retired." + "format": "uuid" }, - "fix": { - "type": "string", - "description": "What to change to clear the finding. Empty for a key Polymorfa has retired." + "session": { + "type": "string" }, - "status": { - "type": "string", - "enum": [ - "open", - "acknowledged", - "resolved", - "not_measured" - ] + "points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BanSafeHealthPoint" + } + } + }, + "required": [ + "sessionId", + "session", + "points" + ] + }, + "BanSafeFindingAcknowledgement": { + "type": "object", + "additionalProperties": false, + "properties": { + "finding": { + "$ref": "#/components/schemas/BanSafeFinding" }, - "severity": { + "message": { "type": "string", - "enum": [ - "info", - "warning", - "critical", - null - ], - "nullable": true - }, - "occurrences": { - "type": "integer", - "minimum": 0 - }, - "reopenedCount": { - "type": "integer", - "minimum": 0 - }, - "evidence": { - "type": "object", - "additionalProperties": true, - "description": "Numbers only. Never message content, a message identifier, or a recipient identifier." - }, + "description": "The exact sentence shown in the console next to the button." + } + }, + "required": [ + "finding", + "message" + ] + }, + "BanSafeEnforcementSummary": { + "type": "object", + "additionalProperties": false, + "properties": { "sessionId": { "type": "string", "format": "uuid" @@ -24444,80 +24345,60 @@ "phoneNumber": { "type": "string" }, - "firstSeenAt": { + "projectId": { "type": "string", - "format": "date-time", - "nullable": true + "format": "uuid" }, - "lastSeenAt": { + "rung": { "type": "string", - "format": "date-time", - "nullable": true + "enum": [ + "none", + "notify", + "throttle", + "block_cold", + "suspend" + ] }, - "acknowledgedAt": { + "previousRung": { "type": "string", - "format": "date-time", - "nullable": true + "enum": [ + "none", + "notify", + "throttle", + "block_cold", + "suspend" + ] }, - "acknowledgedBy": { + "organizationFloor": { "type": "string", - "nullable": true + "enum": [ + "none", + "notify", + "throttle", + "block_cold", + "suspend" + ] }, - "acknowledgementNote": { - "type": "string", - "nullable": true, - "maxLength": 500 + "reason": { + "type": "string" }, - "snoozedUntil": { + "source": { "type": "string", - "format": "date-time", - "nullable": true + "enum": [ + "automatic", + "operator" + ] }, - "resolvedAt": { - "type": "string", - "format": "date-time", + "throughputPerMinute": { + "type": "number", "nullable": true }, - "resolveReason": { - "type": "string", - "nullable": true, - "enum": [ - "clean", - "key_retired", - "number_removed", - "stale", - null - ] - } - }, - "required": [ - "id", - "key", - "title", - "summary", - "fix", - "status", - "severity", - "occurrences", - "reopenedCount", - "evidence", - "sessionId", - "session", - "phoneNumber", - "firstSeenAt", - "lastSeenAt", - "acknowledgedAt", - "acknowledgedBy", - "acknowledgementNote", - "snoozedUntil", - "resolvedAt", - "resolveReason" - ] - }, - "BanSafeNumberDetail": { - "type": "object", - "additionalProperties": false, - "properties": { + "blocksUnsolicited": { + "type": "boolean" + }, + "suspended": { + "type": "boolean" + }, "health": { "type": "number", "minimum": 0, @@ -24619,19 +24500,34 @@ } ] }, - "warmup": { - "$ref": "#/components/schemas/BanSafeNumberWarmup" + "enforcement": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/BanSafeNumberEnforcement" + } + ] }, - "findings": { + "blockingFindings": { "type": "array", "items": { - "$ref": "#/components/schemas/BanSafeFinding" + "type": "string" } }, - "liftRequires": { + "startedAt": { "type": "string", - "nullable": true, - "description": "The exact remaining condition for the restriction to lift." + "format": "date-time" + }, + "eligibleLiftAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "liftRequires": { + "type": "string" + }, + "operatorHold": { + "type": "boolean" }, "appealState": { "type": "string", @@ -24641,6 +24537,13 @@ "granted", "denied" ] + }, + "state": { + "type": "string", + "enum": [ + "applied", + "applying" + ] } }, "required": [ @@ -24648,6 +24551,14 @@ "session", "phoneNumber", "projectId", + "rung", + "previousRung", + "organizationFloor", + "reason", + "source", + "throughputPerMinute", + "blocksUnsolicited", + "suspended", "health", "band", "healthSource", @@ -24661,1788 +24572,1933 @@ "mostLikelyHealthState", "healthExplanation", "observedAccountState", - "enforcement", - "warmup", - "findings", + "blockingFindings", + "startedAt", + "eligibleLiftAt", "liftRequires", - "appealState" + "operatorHold", + "appealState", + "state" ] }, - "BanSafeNumberWarmup": { + "BanSafeAppealReceipt": { "type": "object", "additionalProperties": false, "properties": { - "enabled": { - "type": "boolean" - }, - "tenureSource": { + "sessionId": { "type": "string", - "enum": [ - "history", - "link", - "plan", - null - ], - "nullable": true, - "description": "Retained evidence anchoring the ramp; null when legacy evidence cannot be classified." + "format": "uuid" }, - "tenureDay": { - "type": "integer", - "minimum": 0, - "description": "Whole days since the number's known tenure lower bound; zero when unknown." + "session": { + "type": "string" }, - "allowance": { - "type": "integer", - "minimum": 0, - "nullable": true, - "description": "Messages allowed today; null when the plan is disabled." + "rung": { + "type": "string", + "enum": [ + "none", + "notify", + "throttle", + "block_cold", + "suspend" + ] }, - "sentToday": { - "type": "integer", - "minimum": 0, - "nullable": true, - "description": "Durable reservations in the active allowance window. Successful current reads return 0 before the first reservation; null remains accepted for older stored responses." + "appealState": { + "type": "string", + "enum": [ + "none", + "requested", + "granted", + "denied" + ] }, - "resetsAt": { + "requestedAt": { "type": "string", "format": "date-time", "nullable": true }, - "curve": { - "type": "array", - "items": { - "$ref": "#/components/schemas/WarmupCurvePoint" - } + "liftRequires": { + "type": "string" + }, + "message": { + "type": "string" } }, "required": [ - "enabled", - "tenureSource", - "tenureDay", - "allowance", - "sentToday", - "resetsAt", - "curve" + "sessionId", + "session", + "rung", + "appealState", + "requestedAt", + "liftRequires", + "message" ] }, - "ProjectInsuranceEvidence": { + "BanSafeIncident": { "type": "object", "additionalProperties": false, "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "sessionId": { + "type": "string", + "format": "uuid" + }, + "session": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, "projectId": { "type": "string", "format": "uuid" }, - "enabled": { - "type": "boolean" + "kind": { + "type": "string", + "enum": [ + "cap_warning", + "cap_reached", + "timelock", + "temporary_ban", + "permanent_ban", + "connect_blocked", + "customer_report" + ] }, - "banInsuranceIncluded": { + "source": { + "type": "string", + "enum": [ + "runtime", + "customer" + ] + }, + "resolution": { + "type": "string" + }, + "ambiguous": { "type": "boolean" + }, + "startedAt": { + "type": "string", + "format": "date-time" + }, + "endsAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "closedAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "closedBy": { + "type": "string", + "nullable": true + }, + "claimId": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "note": { + "type": "string", + "nullable": true, + "maxLength": 500 + }, + "reportedBy": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" } }, "required": [ + "id", + "sessionId", + "session", + "phoneNumber", "projectId", - "enabled", - "banInsuranceIncluded" + "kind", + "source", + "resolution", + "ambiguous", + "startedAt", + "endsAt", + "note", + "reportedBy", + "createdAt", + "closedAt", + "closedBy", + "claimId" ] }, - "UpdateProjectInsuranceEvidenceInput": { + "BanSafeIncidentReceipt": { "type": "object", "additionalProperties": false, "properties": { - "enabled": { - "type": "boolean" + "incidentId": { + "type": "string", + "format": "uuid" + }, + "created": { + "type": "boolean", + "description": "False when the same Idempotency-Key already recorded this report." + }, + "sessionId": { + "type": "string", + "format": "uuid" + }, + "session": { + "type": "string" + }, + "occurredAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "incidentId", + "created", + "sessionId", + "session", + "occurredAt" + ] + }, + "BanSafeClaimEvidence": { + "type": "object", + "additionalProperties": false, + "description": "Counts behind the attribution. Never a recipient, a message, or a device model.", + "properties": { + "attributionRuleVersion": { + "type": "integer", + "minimum": 1, + "nullable": true, + "description": "Attribution rules used when the claim was filed. Null for claims filed before rule versioning." + }, + "windowDays": { + "type": "integer", + "minimum": 1 + }, + "deviceEvidence": { + "type": "boolean", + "description": "Whether ban insurance evidence collection is on for the project." + }, + "otherDevices": { + "type": "integer", + "minimum": 0 + }, + "restrictedInWindow": { + "type": "boolean", + "description": "Whether Polymorfa was restricting this number's sending inside the window." + }, + "criticalFindingDays": { + "type": "integer", + "minimum": 0, + "description": "Days one safety check stayed at its most serious level inside the window." + }, + "sharedConnection": { + "type": "boolean", + "description": "Whether this number shared its connection with numbers that were being restricted." + }, + "measuredHours": { + "type": "integer", + "minimum": 0, + "description": "Hours of measured activity Polymorfa holds for the window." } }, "required": [ - "enabled" + "attributionRuleVersion", + "windowDays", + "deviceEvidence", + "otherDevices", + "restrictedInWindow", + "criticalFindingDays", + "sharedConnection", + "measuredHours" ] }, - "BanSafeHealthPoint": { + "BanSafeClaim": { "type": "object", "additionalProperties": false, "properties": { - "health": { - "type": "number", - "minimum": 0, - "maximum": 100, - "nullable": true + "id": { + "type": "string", + "format": "uuid" }, - "band": { + "incidentId": { "type": "string", - "enum": [ - "good", - "fair", - "poor", - "failing", - "unknown", - null - ], - "nullable": true + "format": "uuid" }, - "healthSource": { + "sessionId": { + "type": "string", + "format": "uuid" + }, + "session": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "status": { "type": "string", "enum": [ - "rules_v1", - "ml_model", - "unavailable" + "filed", + "under_review", + "approved", + "denied", + "paid", + "reversed" ] }, - "healthEstimatorVersion": { + "verdict": { "type": "string", - "nullable": true + "enum": [ + "other_device", + "customer_conduct", + "shared_network", + "ours", + "inconclusive" + ] }, - "healthModelVersion": { + "windowStart": { "type": "string", - "nullable": true + "format": "date-time" }, - "healthEvaluatedAt": { + "windowEnd": { "type": "string", "format": "date-time" }, - "healthFeatureCoverage": { + "measuredCents": { "type": "number", "minimum": 0, - "maximum": 1, - "nullable": true + "multipleOf": 0.000001, + "description": "Credit quantity this number consumed inside the window, with up to six decimal places." }, - "healthReliability": { - "type": "string", - "enum": [ - "rules_based", - "validated", - "unavailable" - ] + "capCents": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001, + "description": "Maximum credit quantity this claim can return, with up to six decimal places." }, - "healthUnavailableReason": { - "type": "string", - "nullable": true, - "enum": [ - "no_active_model", - "invalid_active_model", - "insufficient_fresh_features", - null - ] + "amountCents": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001, + "description": "Credit quantity this claim returns, with up to six decimal places." }, - "healthProbabilities": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/BanSafeHealthProbabilities" - } - ] + "evidence": { + "$ref": "#/components/schemas/BanSafeClaimEvidence" }, - "mostLikelyHealthState": { + "summary": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "decidedAt": { "type": "string", - "enum": [ - "healthy", - "limited", - "restricted", - "banned", - null - ], + "format": "date-time", "nullable": true }, - "healthExplanation": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/BanSafeHealthExplanation" - } - ] + "paidAt": { + "type": "string", + "format": "date-time", + "nullable": true }, - "observedAccountState": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/BanSafeObservedAccountState" - } - ] + "createdAt": { + "type": "string", + "format": "date-time" } }, "required": [ - "health", - "band", - "healthSource", - "healthEstimatorVersion", - "healthModelVersion", - "healthEvaluatedAt", - "healthFeatureCoverage", - "healthReliability", - "healthUnavailableReason", - "healthProbabilities", - "mostLikelyHealthState", - "healthExplanation", - "observedAccountState" + "id", + "incidentId", + "sessionId", + "session", + "phoneNumber", + "projectId", + "status", + "verdict", + "windowStart", + "windowEnd", + "measuredCents", + "capCents", + "amountCents", + "evidence", + "summary", + "reason", + "decidedAt", + "paidAt", + "createdAt" ] }, - "BanSafeHealthHistory": { + "AcknowledgeBanSafeFindingInput": { "type": "object", "additionalProperties": false, "properties": { - "sessionId": { + "note": { "type": "string", - "format": "uuid" - }, - "session": { - "type": "string" + "maxLength": 500 }, - "points": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BanSafeHealthPoint" - } + "snoozeDays": { + "type": "integer", + "minimum": 1, + "maximum": 30, + "description": "Silence this finding's notifications for this many days." } - }, - "required": [ - "sessionId", - "session", - "points" - ] + } }, - "BanSafeFindingAcknowledgement": { + "AppealBanSafeEnforcementInput": { "type": "object", "additionalProperties": false, "properties": { - "finding": { - "$ref": "#/components/schemas/BanSafeFinding" - }, - "message": { + "note": { "type": "string", - "description": "The exact sentence shown in the console next to the button." + "minLength": 1, + "maxLength": 1000 } }, "required": [ - "finding", - "message" + "note" ] }, - "BanSafeEnforcementSummary": { + "ReportBanSafeIncidentInput": { "type": "object", "additionalProperties": false, "properties": { - "sessionId": { - "type": "string", - "format": "uuid" - }, "session": { - "type": "string" - }, - "phoneNumber": { - "type": "string" + "type": "string", + "minLength": 1, + "maxLength": 256 }, - "projectId": { + "occurredAt": { "type": "string", - "format": "uuid" + "format": "date-time" }, - "rung": { + "note": { + "type": "string", + "maxLength": 500 + } + }, + "required": [ + "session" + ] + }, + "SafeModeSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "presence": { "type": "string", "enum": [ - "none", - "notify", - "throttle", - "block_cold", - "suspend" + "dark", + "online_while_sending", + "online_hours" ] }, - "previousRung": { + "typing": { "type": "string", "enum": [ - "none", - "notify", - "throttle", - "block_cold", - "suspend" + "off", + "before_text", + "before_all" ] }, - "organizationFloor": { + "reads": { "type": "string", "enum": [ - "none", - "notify", - "throttle", - "block_cold", - "suspend" + "off", + "replied_chats", + "all_inbound" ] }, - "reason": { - "type": "string" - }, - "source": { + "pacing": { "type": "string", "enum": [ - "automatic", - "operator" + "off", + "jittered", + "conversation" ] }, - "throughputPerMinute": { - "type": "number", - "nullable": true - }, - "blocksUnsolicited": { - "type": "boolean" - }, - "suspended": { - "type": "boolean" - }, - "health": { - "type": "number", + "onlineStart": { + "type": "integer", "minimum": 0, - "maximum": 100, - "nullable": true, - "description": "Estimate of present account Health. Higher is healthier. Read healthSource, healthReliability, and healthFeatureCoverage with it." - }, - "band": { - "type": "string", - "enum": [ - "good", - "fair", - "poor", - "failing", - "unknown" - ] - }, - "healthSource": { - "type": "string", - "enum": [ - "rules_v1", - "ml_model", - "unavailable" - ], - "description": "Estimator family. rules_v1 is deterministic; unavailable carries no numeric Health." - }, - "healthEstimatorVersion": { - "type": "string", - "nullable": true, - "description": "Version of the estimator that produced Health." - }, - "healthModelVersion": { - "type": "string", - "nullable": true, - "description": "Learned-model artifact version. Null for rules_v1." - }, - "healthEvaluatedAt": { - "type": "string", - "format": "date-time", - "nullable": true + "maximum": 23 }, - "healthFeatureCoverage": { - "type": "number", + "onlineEnd": { + "type": "integer", "minimum": 0, - "maximum": 1, - "nullable": true - }, - "healthReliability": { - "type": "string", - "enum": [ - "rules_based", - "validated", - "unavailable" - ], - "description": "Support category, not a confidence score." - }, - "healthUnavailableReason": { + "maximum": 23 + } + }, + "required": [ + "presence", + "typing", + "reads", + "pacing", + "onlineStart", + "onlineEnd" + ] + }, + "SafeModeOverride": { + "type": "object", + "additionalProperties": false, + "properties": { + "presence": { "type": "string", - "nullable": true, "enum": [ - "no_active_model", - "invalid_active_model", - "insufficient_fresh_features", - null - ] - }, - "healthProbabilities": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/BanSafeHealthProbabilities" - } + "inherit", + "dark", + "online_while_sending", + "online_hours" ] }, - "mostLikelyHealthState": { + "typing": { "type": "string", "enum": [ - "healthy", - "limited", - "restricted", - "banned", - null - ], - "nullable": true - }, - "healthExplanation": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/BanSafeHealthExplanation" - } - ] - }, - "observedAccountState": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/BanSafeObservedAccountState" - } - ] - }, - "enforcement": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/BanSafeNumberEnforcement" - } + "inherit", + "off", + "before_text", + "before_all" ] }, - "blockingFindings": { - "type": "array", - "items": { - "type": "string" - } - }, - "startedAt": { - "type": "string", - "format": "date-time" - }, - "eligibleLiftAt": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "liftRequires": { - "type": "string" - }, - "operatorHold": { - "type": "boolean" - }, - "appealState": { + "reads": { "type": "string", "enum": [ - "none", - "requested", - "granted", - "denied" + "inherit", + "off", + "replied_chats", + "all_inbound" ] }, - "state": { + "pacing": { "type": "string", "enum": [ - "applied", - "applying" + "inherit", + "off", + "jittered", + "conversation" ] } }, "required": [ - "sessionId", - "session", - "phoneNumber", - "projectId", - "rung", - "previousRung", - "organizationFloor", - "reason", - "source", - "throughputPerMinute", - "blocksUnsolicited", - "suspended", - "health", - "band", - "healthSource", - "healthEstimatorVersion", - "healthModelVersion", - "healthEvaluatedAt", - "healthFeatureCoverage", - "healthReliability", - "healthUnavailableReason", - "healthProbabilities", - "mostLikelyHealthState", - "healthExplanation", - "observedAccountState", - "blockingFindings", - "startedAt", - "eligibleLiftAt", - "liftRequires", - "operatorHold", - "appealState", - "state" + "presence", + "typing", + "reads", + "pacing" ] }, - "BanSafeAppealReceipt": { + "SafeModeApplied": { "type": "object", "additionalProperties": false, + "description": "What the number itself last reported it is running. An observation, not a claim.", "properties": { - "sessionId": { + "observedAt": { "type": "string", - "format": "uuid" - }, - "session": { - "type": "string" + "format": "date-time" }, - "rung": { + "presence": { "type": "string", - "enum": [ - "none", - "notify", - "throttle", - "block_cold", - "suspend" - ] + "nullable": true }, - "appealState": { + "typing": { "type": "string", - "enum": [ - "none", - "requested", - "granted", - "denied" - ] + "nullable": true }, - "requestedAt": { + "reads": { "type": "string", - "format": "date-time", "nullable": true }, - "liftRequires": { - "type": "string" - }, - "message": { - "type": "string" + "pacing": { + "type": "string", + "nullable": true } }, "required": [ - "sessionId", - "session", - "rung", - "appealState", - "requestedAt", - "liftRequires", - "message" + "observedAt", + "presence", + "typing", + "reads", + "pacing" ] }, - "BanSafeIncident": { + "ProjectSafeMode": { "type": "object", "additionalProperties": false, "properties": { - "id": { + "projectId": { "type": "string", "format": "uuid" }, - "sessionId": { - "type": "string", - "format": "uuid" + "ceiling": { + "$ref": "#/components/schemas/SafeModeSettings" }, - "session": { - "type": "string" + "entitled": { + "type": "boolean", + "description": "Whether this project's plan includes BanSafe Lite, which Safe Mode is part of. When false, a request that raises a setting is refused." }, - "phoneNumber": { + "entitlementReason": { + "type": "string", + "nullable": true, + "description": "Why a raise would be refused. Null when the plan includes BanSafe Lite." + } + }, + "required": [ + "projectId", + "ceiling", + "entitled", + "entitlementReason" + ] + }, + "SessionSafeMode": { + "type": "object", + "additionalProperties": false, + "properties": { + "session": { "type": "string" }, "projectId": { "type": "string", "format": "uuid" }, - "kind": { + "project": { + "$ref": "#/components/schemas/SafeModeSettings" + }, + "override": { + "$ref": "#/components/schemas/SafeModeOverride" + }, + "effective": { + "$ref": "#/components/schemas/SafeModeSettings" + }, + "applied": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/SafeModeApplied" + } + ], + "nullable": true + }, + "mismatch": { + "type": "boolean", + "description": "True when the number reports something other than the policy in force." + }, + "entitled": { + "type": "boolean", + "description": "Whether this number's plan includes BanSafe Lite, which Safe Mode is part of. When false, a request that raises a setting is refused." + }, + "entitlementReason": { + "type": "string", + "nullable": true, + "description": "Why a raise would be refused. Null when the plan includes BanSafe Lite." + } + }, + "required": [ + "session", + "projectId", + "project", + "override", + "effective", + "applied", + "mismatch", + "entitled", + "entitlementReason" + ] + }, + "UpdateProjectSafeModeInput": { + "type": "object", + "additionalProperties": false, + "description": "A partial update: send only the settings you want to change.", + "properties": { + "presence": { "type": "string", "enum": [ - "cap_warning", - "cap_reached", - "timelock", - "temporary_ban", - "permanent_ban", - "connect_blocked", - "customer_report" + "dark", + "online_while_sending", + "online_hours" ] }, - "source": { + "typing": { "type": "string", "enum": [ - "runtime", - "customer" + "off", + "before_text", + "before_all" ] }, - "resolution": { - "type": "string" - }, - "ambiguous": { - "type": "boolean" - }, - "startedAt": { + "reads": { "type": "string", - "format": "date-time" + "enum": [ + "off", + "replied_chats", + "all_inbound" + ] }, - "endsAt": { + "pacing": { "type": "string", - "format": "date-time", - "nullable": true + "enum": [ + "off", + "jittered", + "conversation" + ] }, - "closedAt": { - "type": "string", - "format": "date-time", - "nullable": true + "onlineStart": { + "type": "integer", + "minimum": 0, + "maximum": 23 }, - "closedBy": { + "onlineEnd": { + "type": "integer", + "minimum": 0, + "maximum": 23 + } + } + }, + "UpdateSessionSafeModeInput": { + "type": "object", + "additionalProperties": false, + "description": "A partial update. `inherit` follows the project ceiling.", + "properties": { + "presence": { "type": "string", - "nullable": true + "enum": [ + "inherit", + "dark", + "online_while_sending", + "online_hours" + ] }, - "claimId": { + "typing": { "type": "string", - "format": "uuid", - "nullable": true + "enum": [ + "inherit", + "off", + "before_text", + "before_all" + ] }, - "note": { + "reads": { "type": "string", - "nullable": true, - "maxLength": 500 + "enum": [ + "inherit", + "off", + "replied_chats", + "all_inbound" + ] }, - "reportedBy": { + "pacing": { "type": "string", - "nullable": true + "enum": [ + "inherit", + "off", + "jittered", + "conversation" + ] + } + } + }, + "WarmupPlanSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether every number in this project has a daily allowance." + }, + "warmupDays": { + "type": "integer", + "minimum": 1, + "maximum": 90, + "description": "Days the ramp takes to reach the per-number daily cap." + }, + "dailyStart": { + "type": "integer", + "minimum": 1, + "maximum": 2000, + "description": "Messages a number may send on its first day." + } + }, + "required": [ + "enabled", + "warmupDays", + "dailyStart" + ] + }, + "WarmupCurvePoint": { + "type": "object", + "additionalProperties": false, + "properties": { + "day": { + "type": "integer", + "minimum": 0, + "description": "Days since the number's first proven activity." }, - "createdAt": { - "type": "string", - "format": "date-time" + "allowance": { + "type": "integer", + "minimum": 0, + "description": "Messages allowed on that day." } }, "required": [ - "id", - "sessionId", - "session", - "phoneNumber", - "projectId", - "kind", - "source", - "resolution", - "ambiguous", - "startedAt", - "endsAt", - "note", - "reportedBy", - "createdAt", - "closedAt", - "closedBy", - "claimId" + "day", + "allowance" ] }, - "BanSafeIncidentReceipt": { + "ProjectWarmupPlan": { "type": "object", "additionalProperties": false, "properties": { - "incidentId": { + "projectId": { "type": "string", "format": "uuid" }, - "created": { - "type": "boolean", - "description": "False when the same Idempotency-Key already recorded this report." + "plan": { + "$ref": "#/components/schemas/WarmupPlanSettings" }, - "sessionId": { - "type": "string", - "format": "uuid" + "ceiling": { + "type": "integer", + "minimum": 1, + "description": "The per-number daily cap the ramp climbs to." }, - "session": { - "type": "string" + "curve": { + "type": "array", + "maxItems": 91, + "items": { + "$ref": "#/components/schemas/WarmupCurvePoint" + }, + "description": "The projected allowance day by day for a number whose health is good. A number in a lower health band gets less." }, - "occurredAt": { + "entitled": { + "type": "boolean", + "description": "Whether this project's plan includes BanSafe Lite, which the warm-up plan is part of. When false, turning the warm-up plan on is refused." + }, + "entitlementReason": { "type": "string", - "format": "date-time" + "nullable": true, + "description": "Why turning the plan on would be refused. Null when the plan includes BanSafe Lite." } }, "required": [ - "incidentId", - "created", - "sessionId", - "session", - "occurredAt" + "projectId", + "plan", + "ceiling", + "curve", + "entitled", + "entitlementReason" ] }, - "BanSafeClaimEvidence": { + "UpdateProjectWarmupPlanInput": { "type": "object", "additionalProperties": false, - "description": "Counts behind the attribution. Never a recipient, a message, or a device model.", + "description": "A partial update: send only the settings you want to change.", "properties": { - "attributionRuleVersion": { - "type": "integer", - "minimum": 1, - "nullable": true, - "description": "Attribution rules used when the claim was filed. Null for claims filed before rule versioning." + "enabled": { + "type": "boolean" }, - "windowDays": { + "warmupDays": { "type": "integer", - "minimum": 1 - }, - "deviceEvidence": { - "type": "boolean", - "description": "Whether ban insurance evidence collection is on for the project." + "minimum": 1, + "maximum": 90 }, - "otherDevices": { + "dailyStart": { "type": "integer", - "minimum": 0 - }, - "restrictedInWindow": { + "minimum": 1, + "maximum": 2000 + } + } + }, + "ProjectHealthPolicyIntegrations": { + "type": "object", + "additionalProperties": false, + "properties": { + "emailConfigured": { "type": "boolean", - "description": "Whether Polymorfa was restricting this number's sending inside the window." - }, - "criticalFindingDays": { - "type": "integer", - "minimum": 0, - "description": "Days one safety check stayed at its most serious level inside the window." + "description": "Whether notification email delivery is configured." }, - "sharedConnection": { + "webhookConfigured": { "type": "boolean", - "description": "Whether this number shared its connection with numbers that were being restricted." - }, - "measuredHours": { - "type": "integer", - "minimum": 0, - "description": "Hours of measured activity Polymorfa holds for the window." + "description": "Whether an enabled webhook exists for this team or project." } }, "required": [ - "attributionRuleVersion", - "windowDays", - "deviceEvidence", - "otherDevices", - "restrictedInWindow", - "criticalFindingDays", - "sharedConnection", - "measuredHours" + "emailConfigured", + "webhookConfigured" ] }, - "BanSafeClaim": { + "ProjectHealthPolicy": { "type": "object", "additionalProperties": false, "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "incidentId": { - "type": "string", - "format": "uuid" - }, - "sessionId": { + "projectId": { "type": "string", "format": "uuid" }, - "session": { - "type": "string" + "version": { + "type": "integer", + "minimum": 0, + "description": "Revision required by the next update." }, - "phoneNumber": { - "type": "string" + "enabled": { + "type": "boolean" }, - "projectId": { - "type": "string", - "format": "uuid" + "threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Actions trigger only when an available health estimate is below this value." }, - "status": { + "sessionAction": { "type": "string", "enum": [ - "filed", - "under_review", - "approved", - "denied", - "paid", - "reversed" + "none", + "stop", + "slow_down", + "log_out" ] }, - "verdict": { - "type": "string", - "enum": [ - "other_device", - "customer_conduct", - "shared_network", - "ours", - "inconclusive" - ] + "slowDownMps": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true, + "nullable": true }, - "windowStart": { - "type": "string", - "format": "date-time" + "emailNotification": { + "type": "boolean" }, - "windowEnd": { - "type": "string", - "format": "date-time" + "webhookNotification": { + "type": "boolean" }, - "measuredCents": { + "integrations": { + "$ref": "#/components/schemas/ProjectHealthPolicyIntegrations" + } + }, + "required": [ + "projectId", + "version", + "enabled", + "threshold", + "sessionAction", + "slowDownMps", + "emailNotification", + "webhookNotification", + "integrations" + ] + }, + "UpdateProjectHealthPolicyInput": { + "type": "object", + "additionalProperties": false, + "properties": { + "version": { "type": "integer", - "minimum": 0, - "description": "Credits this number consumed inside the window, in cents." + "minimum": 0 }, - "capCents": { - "type": "integer", - "minimum": 0, - "description": "The most a claim on this number's plan can return, in cents." + "enabled": { + "type": "boolean" }, - "amountCents": { + "threshold": { "type": "integer", - "minimum": 0, - "description": "What this claim returns, in cents." - }, - "evidence": { - "$ref": "#/components/schemas/BanSafeClaimEvidence" - }, - "summary": { - "type": "string" - }, - "reason": { - "type": "string" + "minimum": 0, + "maximum": 100 }, - "decidedAt": { + "sessionAction": { "type": "string", - "format": "date-time", - "nullable": true + "enum": [ + "none", + "stop", + "slow_down", + "log_out" + ] }, - "paidAt": { - "type": "string", - "format": "date-time", + "slowDownMps": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true, "nullable": true }, - "createdAt": { - "type": "string", - "format": "date-time" + "emailNotification": { + "type": "boolean" + }, + "webhookNotification": { + "type": "boolean" } }, "required": [ - "id", - "incidentId", - "sessionId", - "session", - "phoneNumber", - "projectId", - "status", - "verdict", - "windowStart", - "windowEnd", - "measuredCents", - "capCents", - "amountCents", - "evidence", - "summary", - "reason", - "decidedAt", - "paidAt", - "createdAt" + "version", + "enabled", + "threshold", + "sessionAction", + "slowDownMps", + "emailNotification", + "webhookNotification" ] }, - "AcknowledgeBanSafeFindingInput": { + "WebhookRetryPolicyInput": { "type": "object", "additionalProperties": false, "properties": { - "note": { + "maximumAttempts": { + "type": "integer", + "minimum": 1, + "maximum": 10 + }, + "backoff": { "type": "string", - "maxLength": 500 + "enum": [ + "constant", + "linear", + "exponential" + ] }, - "snoozeDays": { + "initialDelaySeconds": { "type": "integer", "minimum": 1, - "maximum": 30, - "description": "Silence this finding's notifications for this many days." - } - } - }, - "AppealBanSafeEnforcementInput": { - "type": "object", - "additionalProperties": false, - "properties": { - "note": { - "type": "string", - "minLength": 1, - "maxLength": 1000 + "maximum": 3600 } }, "required": [ - "note" + "maximumAttempts", + "backoff", + "initialDelaySeconds" ] }, - "ReportBanSafeIncidentInput": { + "WebhookRetryPolicy": { "type": "object", "additionalProperties": false, "properties": { - "session": { - "type": "string", - "minLength": 1, - "maxLength": 256 + "maximumAttempts": { + "type": "integer", + "minimum": 1, + "maximum": 10 }, - "occurredAt": { + "backoff": { "type": "string", - "format": "date-time" + "enum": [ + "constant", + "linear", + "exponential" + ] }, - "note": { - "type": "string", - "maxLength": 500 + "initialDelaySeconds": { + "type": "integer", + "minimum": 1, + "maximum": 3600 } }, "required": [ - "session" + "maximumAttempts", + "backoff", + "initialDelaySeconds" ] }, - "SafeModeSettings": { + "WebhookHeaderInput": { "type": "object", "additionalProperties": false, "properties": { - "presence": { - "type": "string", - "enum": [ - "dark", - "online_while_sending", - "online_hours" - ] - }, - "typing": { - "type": "string", - "enum": [ - "off", - "before_text", - "before_all" - ] - }, - "reads": { + "name": { "type": "string", - "enum": [ - "off", - "replied_chats", - "all_inbound" - ] + "minLength": 1, + "maxLength": 128, + "pattern": "^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$" }, - "pacing": { + "value": { "type": "string", - "enum": [ - "off", - "jittered", - "conversation" - ] - }, - "onlineStart": { - "type": "integer", - "minimum": 0, - "maximum": 23 - }, - "onlineEnd": { - "type": "integer", - "minimum": 0, - "maximum": 23 + "maxLength": 4096, + "pattern": "^(?![\\s\\S]*[\\u0000-\\u0008\\u000A-\\u001F\\u007F])[\\s\\S]*$", + "x-polymorfa-max-utf8-bytes": 4096, + "writeOnly": true, + "description": "Encrypted write-only HTTP field value. Limited to 4096 UTF-8 bytes. Line breaks, NUL, and other control characters except horizontal tab are rejected." } }, "required": [ - "presence", - "typing", - "reads", - "pacing", - "onlineStart", - "onlineEnd" + "name", + "value" ] }, - "SafeModeOverride": { + "WebhookHeaderMetadata": { "type": "object", "additionalProperties": false, "properties": { - "presence": { - "type": "string", - "enum": [ - "inherit", - "dark", - "online_while_sending", - "online_hours" - ] - }, - "typing": { - "type": "string", - "enum": [ - "inherit", - "off", - "before_text", - "before_all" - ] - }, - "reads": { - "type": "string", - "enum": [ - "inherit", - "off", - "replied_chats", - "all_inbound" - ] - }, - "pacing": { + "name": { "type": "string", - "enum": [ - "inherit", - "off", - "jittered", - "conversation" - ] + "minLength": 1, + "maxLength": 128 } }, "required": [ - "presence", - "typing", - "reads", - "pacing" + "name" ] }, - "SafeModeApplied": { + "WebhookSigningSecretMetadata": { "type": "object", "additionalProperties": false, - "description": "What the number itself last reported it is running. An observation, not a claim.", "properties": { - "observedAt": { - "type": "string", - "format": "date-time" - }, - "presence": { - "type": "string", - "nullable": true - }, - "typing": { - "type": "string", - "nullable": true + "version": { + "type": "integer", + "minimum": 1 }, - "reads": { + "createdAt": { "type": "string", - "nullable": true + "format": "date-time" }, - "pacing": { + "previousValidUntil": { "type": "string", + "format": "date-time", "nullable": true } }, "required": [ - "observedAt", - "presence", - "typing", - "reads", - "pacing" + "version", + "createdAt", + "previousValidUntil" ] }, - "ProjectSafeMode": { + "CreateOrganizationWebhookInput": { "type": "object", "additionalProperties": false, "properties": { - "projectId": { + "url": { "type": "string", - "format": "uuid" + "format": "uri", + "minLength": 1, + "maxLength": 2048 }, - "ceiling": { - "$ref": "#/components/schemas/SafeModeSettings" + "eventTypes": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } }, - "entitled": { + "enabled": { "type": "boolean", - "description": "Whether this project's plan includes BanSafe Lite, which Safe Mode is part of. When false, a request that raises a setting is refused." + "default": true }, - "entitlementReason": { + "format": { "type": "string", - "nullable": true, - "description": "Why a raise would be refused. Null when the plan includes BanSafe Lite." + "enum": [ + "native", + "meta" + ], + "default": "native" + }, + "retryPolicy": { + "$ref": "#/components/schemas/WebhookRetryPolicyInput" + }, + "headers": { + "type": "array", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/WebhookHeaderInput" + } } }, "required": [ - "projectId", - "ceiling", - "entitled", - "entitlementReason" + "url", + "eventTypes" ] }, - "SessionSafeMode": { + "CreateProjectWebhookInput": { "type": "object", "additionalProperties": false, "properties": { - "session": { - "type": "string" - }, - "projectId": { + "url": { "type": "string", - "format": "uuid" - }, - "project": { - "$ref": "#/components/schemas/SafeModeSettings" + "format": "uri", + "minLength": 1, + "maxLength": 2048 }, - "override": { - "$ref": "#/components/schemas/SafeModeOverride" + "eventTypes": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } }, - "effective": { - "$ref": "#/components/schemas/SafeModeSettings" + "enabled": { + "type": "boolean", + "default": true }, - "applied": { - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/SafeModeApplied" - } + "format": { + "type": "string", + "enum": [ + "native", + "meta" ], - "nullable": true - }, - "mismatch": { - "type": "boolean", - "description": "True when the number reports something other than the policy in force." + "default": "native" }, - "entitled": { - "type": "boolean", - "description": "Whether this number's plan includes BanSafe Lite, which Safe Mode is part of. When false, a request that raises a setting is refused." + "retryPolicy": { + "$ref": "#/components/schemas/WebhookRetryPolicyInput" }, - "entitlementReason": { - "type": "string", - "nullable": true, - "description": "Why a raise would be refused. Null when the plan includes BanSafe Lite." + "headers": { + "type": "array", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/WebhookHeaderInput" + } } }, "required": [ - "session", - "projectId", - "project", - "override", - "effective", - "applied", - "mismatch", - "entitled", - "entitlementReason" + "url", + "eventTypes" ] }, - "UpdateProjectSafeModeInput": { + "UpdateOrganizationWebhookInput": { "type": "object", "additionalProperties": false, - "description": "A partial update: send only the settings you want to change.", "properties": { - "presence": { + "url": { "type": "string", - "enum": [ - "dark", - "online_while_sending", - "online_hours" - ] + "format": "uri", + "minLength": 1, + "maxLength": 2048 }, - "typing": { - "type": "string", - "enum": [ - "off", - "before_text", - "before_all" - ] + "eventTypes": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } }, - "reads": { - "type": "string", - "enum": [ - "off", - "replied_chats", - "all_inbound" - ] + "enabled": { + "type": "boolean" }, - "pacing": { + "format": { "type": "string", "enum": [ - "off", - "jittered", - "conversation" + "native", + "meta" ] }, - "onlineStart": { - "type": "integer", - "minimum": 0, - "maximum": 23 + "retryPolicy": { + "$ref": "#/components/schemas/WebhookRetryPolicyInput" }, - "onlineEnd": { - "type": "integer", - "minimum": 0, - "maximum": 23 + "headers": { + "type": "array", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/WebhookHeaderInput" + } } - } + }, + "minProperties": 1 }, - "UpdateSessionSafeModeInput": { + "UpdateProjectWebhookInput": { "type": "object", "additionalProperties": false, - "description": "A partial update. `inherit` follows the project ceiling.", "properties": { - "presence": { + "url": { "type": "string", - "enum": [ - "inherit", - "dark", - "online_while_sending", - "online_hours" - ] + "format": "uri", + "minLength": 1, + "maxLength": 2048 }, - "typing": { - "type": "string", - "enum": [ - "inherit", - "off", - "before_text", - "before_all" - ] + "eventTypes": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } }, - "reads": { - "type": "string", - "enum": [ - "inherit", - "off", - "replied_chats", - "all_inbound" - ] + "enabled": { + "type": "boolean" }, - "pacing": { + "format": { "type": "string", "enum": [ - "inherit", - "off", - "jittered", - "conversation" + "native", + "meta" ] - } - } - }, - "WarmupPlanSettings": { - "type": "object", - "additionalProperties": false, - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether every number in this project has a daily allowance." }, - "warmupDays": { - "type": "integer", - "minimum": 1, - "maximum": 90, - "description": "Days the ramp takes to reach the per-number daily cap." + "retryPolicy": { + "$ref": "#/components/schemas/WebhookRetryPolicyInput" }, - "dailyStart": { - "type": "integer", - "minimum": 1, - "maximum": 2000, - "description": "Messages a number may send on its first day." + "headers": { + "type": "array", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/WebhookHeaderInput" + } } }, - "required": [ - "enabled", - "warmupDays", - "dailyStart" - ] + "minProperties": 1 }, - "WarmupCurvePoint": { + "RotateWebhookSecretInput": { "type": "object", "additionalProperties": false, "properties": { - "day": { - "type": "integer", - "minimum": 0, - "description": "Days since the number's first proven activity." - }, - "allowance": { + "overlapSeconds": { "type": "integer", "minimum": 0, - "description": "Messages allowed on that day." + "maximum": 86400, + "default": 3600 } - }, - "required": [ - "day", - "allowance" - ] + } }, - "ProjectWarmupPlan": { + "OrganizationWebhook": { "type": "object", "additionalProperties": false, "properties": { - "projectId": { + "id": { "type": "string", "format": "uuid" }, - "plan": { - "$ref": "#/components/schemas/WarmupPlanSettings" + "organizationId": { + "type": "string", + "format": "uuid" }, - "ceiling": { - "type": "integer", - "minimum": 1, - "description": "The per-number daily cap the ramp climbs to." + "owner": { + "type": "string", + "enum": [ + "organization" + ] }, - "curve": { + "projectId": { + "type": "string", + "nullable": true, + "enum": [ + null + ] + }, + "url": { + "type": "string", + "format": "uri", + "maxLength": 2048 + }, + "eventTypes": { "type": "array", - "maxItems": 91, + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, "items": { - "$ref": "#/components/schemas/WarmupCurvePoint" - }, - "description": "The projected allowance day by day for a number whose health is good. A number in a lower health band gets less." + "type": "string", + "minLength": 1, + "maxLength": 128 + } }, - "entitled": { - "type": "boolean", - "description": "Whether this project's plan includes BanSafe Lite, which the warm-up plan is part of. When false, turning the warm-up plan on is refused." + "enabled": { + "type": "boolean" }, - "entitlementReason": { + "format": { "type": "string", - "nullable": true, - "description": "Why turning the plan on would be refused. Null when the plan includes BanSafe Lite." + "enum": [ + "native", + "meta" + ] + }, + "retryPolicy": { + "$ref": "#/components/schemas/WebhookRetryPolicy" + }, + "headers": { + "type": "array", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/WebhookHeaderMetadata" + } + }, + "secret": { + "$ref": "#/components/schemas/WebhookSigningSecretMetadata" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" } }, "required": [ + "id", + "organizationId", + "owner", "projectId", - "plan", - "ceiling", - "curve", - "entitled", - "entitlementReason" + "url", + "eventTypes", + "enabled", + "format", + "retryPolicy", + "headers", + "secret", + "createdAt", + "updatedAt" ] }, - "UpdateProjectWarmupPlanInput": { + "ProjectWebhook": { "type": "object", "additionalProperties": false, - "description": "A partial update: send only the settings you want to change.", "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "organizationId": { + "type": "string", + "format": "uuid" + }, + "owner": { + "type": "string", + "enum": [ + "project" + ] + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "url": { + "type": "string", + "format": "uri", + "maxLength": 2048 + }, + "eventTypes": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + }, "enabled": { "type": "boolean" }, - "warmupDays": { - "type": "integer", - "minimum": 1, - "maximum": 90 + "format": { + "type": "string", + "enum": [ + "native", + "meta" + ] }, - "dailyStart": { - "type": "integer", - "minimum": 1, - "maximum": 2000 - } - } - }, - "ProjectHealthPolicyIntegrations": { - "type": "object", - "additionalProperties": false, - "properties": { - "emailConfigured": { - "type": "boolean", - "description": "Whether notification email delivery is configured." + "retryPolicy": { + "$ref": "#/components/schemas/WebhookRetryPolicy" }, - "webhookConfigured": { - "type": "boolean", - "description": "Whether an enabled webhook exists for this organization or project." + "headers": { + "type": "array", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/WebhookHeaderMetadata" + } + }, + "secret": { + "$ref": "#/components/schemas/WebhookSigningSecretMetadata" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" } }, "required": [ - "emailConfigured", - "webhookConfigured" + "id", + "organizationId", + "owner", + "projectId", + "url", + "eventTypes", + "enabled", + "format", + "retryPolicy", + "headers", + "secret", + "createdAt", + "updatedAt" ] }, - "ProjectHealthPolicy": { + "OrganizationWebhookCreationReceipt": { "type": "object", "additionalProperties": false, "properties": { - "projectId": { - "type": "string", - "format": "uuid" - }, - "version": { - "type": "integer", - "minimum": 0, - "description": "Revision required by the next update." - }, - "enabled": { - "type": "boolean" - }, - "threshold": { - "type": "integer", - "minimum": 0, - "maximum": 100, - "description": "Actions trigger only when an available health estimate is below this value." + "webhook": { + "$ref": "#/components/schemas/OrganizationWebhook" }, - "sessionAction": { + "operationId": { "type": "string", + "nullable": true, "enum": [ - "none", - "stop", - "slow_down", - "log_out" + null ] }, - "slowDownMps": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true, - "nullable": true + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" }, - "emailNotification": { - "type": "boolean" + "secret": { + "type": "string", + "nullable": true, + "writeOnly": true }, - "webhookNotification": { + "secretAvailable": { "type": "boolean" - }, - "integrations": { - "$ref": "#/components/schemas/ProjectHealthPolicyIntegrations" } }, "required": [ - "projectId", - "version", - "enabled", - "threshold", - "sessionAction", - "slowDownMps", - "emailNotification", - "webhookNotification", - "integrations" + "webhook", + "operationId", + "idempotency", + "secret", + "secretAvailable" ] }, - "UpdateProjectHealthPolicyInput": { + "ProjectWebhookCreationReceipt": { "type": "object", "additionalProperties": false, "properties": { - "version": { - "type": "integer", - "minimum": 0 - }, - "enabled": { - "type": "boolean" - }, - "threshold": { - "type": "integer", - "minimum": 0, - "maximum": 100 + "webhook": { + "$ref": "#/components/schemas/ProjectWebhook" }, - "sessionAction": { + "operationId": { "type": "string", + "nullable": true, "enum": [ - "none", - "stop", - "slow_down", - "log_out" + null ] }, - "slowDownMps": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true, - "nullable": true + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" }, - "emailNotification": { - "type": "boolean" + "secret": { + "type": "string", + "nullable": true, + "writeOnly": true }, - "webhookNotification": { + "secretAvailable": { "type": "boolean" } }, "required": [ - "version", - "enabled", - "threshold", - "sessionAction", - "slowDownMps", - "emailNotification", - "webhookNotification" + "webhook", + "operationId", + "idempotency", + "secret", + "secretAvailable" ] }, - "WebhookRetryPolicyInput": { + "OrganizationWebhookMutationReceipt": { "type": "object", "additionalProperties": false, "properties": { - "maximumAttempts": { - "type": "integer", - "minimum": 1, - "maximum": 10 + "webhook": { + "$ref": "#/components/schemas/OrganizationWebhook" }, - "backoff": { + "operationId": { "type": "string", + "nullable": true, "enum": [ - "constant", - "linear", - "exponential" + null ] }, - "initialDelaySeconds": { - "type": "integer", - "minimum": 1, - "maximum": 3600 + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" } }, "required": [ - "maximumAttempts", - "backoff", - "initialDelaySeconds" + "webhook", + "operationId", + "idempotency" ] }, - "WebhookRetryPolicy": { + "ProjectWebhookMutationReceipt": { "type": "object", "additionalProperties": false, "properties": { - "maximumAttempts": { - "type": "integer", - "minimum": 1, - "maximum": 10 + "webhook": { + "$ref": "#/components/schemas/ProjectWebhook" }, - "backoff": { + "operationId": { "type": "string", + "nullable": true, "enum": [ - "constant", - "linear", - "exponential" + null ] }, - "initialDelaySeconds": { - "type": "integer", - "minimum": 1, - "maximum": 3600 + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" } }, "required": [ - "maximumAttempts", - "backoff", - "initialDelaySeconds" + "webhook", + "operationId", + "idempotency" ] }, - "WebhookHeaderInput": { + "OrganizationWebhookDeletionReceipt": { "type": "object", "additionalProperties": false, "properties": { - "name": { + "webhookId": { "type": "string", - "minLength": 1, - "maxLength": 128, - "pattern": "^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$" + "format": "uuid" }, - "value": { + "deleted": { + "type": "boolean", + "enum": [ + true + ] + }, + "operationId": { "type": "string", - "maxLength": 4096, - "pattern": "^(?![\\s\\S]*[\\u0000-\\u0008\\u000A-\\u001F\\u007F])[\\s\\S]*$", - "x-polymorfa-max-utf8-bytes": 4096, - "writeOnly": true, - "description": "Encrypted write-only HTTP field value. Limited to 4096 UTF-8 bytes. Line breaks, NUL, and other control characters except horizontal tab are rejected." + "nullable": true, + "enum": [ + null + ] + }, + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" } }, "required": [ - "name", - "value" + "webhookId", + "deleted", + "operationId", + "idempotency" ] }, - "WebhookHeaderMetadata": { + "ProjectWebhookDeletionReceipt": { "type": "object", "additionalProperties": false, "properties": { - "name": { + "webhookId": { "type": "string", - "minLength": 1, - "maxLength": 128 + "format": "uuid" + }, + "deleted": { + "type": "boolean", + "enum": [ + true + ] + }, + "operationId": { + "type": "string", + "nullable": true, + "enum": [ + null + ] + }, + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" } }, "required": [ - "name" + "webhookId", + "deleted", + "operationId", + "idempotency" ] }, - "WebhookSigningSecretMetadata": { + "OrganizationWebhookSecretRotationReceipt": { "type": "object", "additionalProperties": false, "properties": { - "version": { - "type": "integer", - "minimum": 1 + "webhookId": { + "type": "string", + "format": "uuid" }, - "createdAt": { + "operationId": { "type": "string", - "format": "date-time" + "nullable": true, + "enum": [ + null + ] }, - "previousValidUntil": { + "secret": { "type": "string", - "format": "date-time", - "nullable": true + "nullable": true, + "writeOnly": true + }, + "secretAvailable": { + "type": "boolean" + }, + "secretMetadata": { + "$ref": "#/components/schemas/WebhookSigningSecretMetadata" + }, + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" } }, "required": [ - "version", - "createdAt", - "previousValidUntil" + "webhookId", + "operationId", + "secret", + "secretAvailable", + "secretMetadata", + "idempotency" ] }, - "CreateOrganizationWebhookInput": { + "ProjectWebhookSecretRotationReceipt": { "type": "object", "additionalProperties": false, "properties": { - "url": { + "webhookId": { "type": "string", - "format": "uri", - "minLength": 1, - "maxLength": 2048 - }, - "eventTypes": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - } + "format": "uuid" }, - "enabled": { - "type": "boolean", - "default": true + "operationId": { + "type": "string", + "nullable": true, + "enum": [ + null + ] }, - "format": { + "secret": { "type": "string", - "enum": [ - "native", - "meta" - ], - "default": "native" + "nullable": true, + "writeOnly": true }, - "retryPolicy": { - "$ref": "#/components/schemas/WebhookRetryPolicyInput" + "secretAvailable": { + "type": "boolean" }, - "headers": { - "type": "array", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/WebhookHeaderInput" - } + "secretMetadata": { + "$ref": "#/components/schemas/WebhookSigningSecretMetadata" + }, + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" } }, "required": [ - "url", - "eventTypes" + "webhookId", + "operationId", + "secret", + "secretAvailable", + "secretMetadata", + "idempotency" ] }, - "CreateProjectWebhookInput": { + "EncodedEventPayload": { "type": "object", "additionalProperties": false, "properties": { - "url": { + "encoding": { "type": "string", - "format": "uri", - "minLength": 1, - "maxLength": 2048 - }, - "eventTypes": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - } - }, - "enabled": { - "type": "boolean", - "default": true + "enum": [ + "base64" + ] }, - "format": { + "contentType": { "type": "string", "enum": [ - "native", - "meta" - ], - "default": "native" - }, - "retryPolicy": { - "$ref": "#/components/schemas/WebhookRetryPolicyInput" + "application/json" + ] }, - "headers": { - "type": "array", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/WebhookHeaderInput" - } + "data": { + "type": "string", + "format": "byte", + "maxLength": 87384, + "description": "Canonical base64 for the complete native webhook body; decoded bytes are limited to 65,536 bytes." } }, "required": [ - "url", - "eventTypes" + "encoding", + "contentType", + "data" ] }, - "UpdateOrganizationWebhookInput": { + "PayloadAvailability": { + "type": "string", + "enum": [ + "available", + "not_retained", + "expired", + "redacted", + "unavailable" + ] + }, + "OrganizationEvent": { "type": "object", "additionalProperties": false, "properties": { - "url": { + "id": { + "type": "string", + "format": "uuid" + }, + "organizationId": { + "type": "string", + "format": "uuid" + }, + "projectId": { + "type": "string", + "nullable": true, + "enum": [ + null + ] + }, + "type": { "type": "string", - "format": "uri", "minLength": 1, - "maxLength": 2048 + "maxLength": 128 }, - "eventTypes": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - } + "source": { + "type": "string", + "enum": [ + "runtime", + "platform", + "test" + ] }, - "enabled": { - "type": "boolean" + "environment": { + "type": "string", + "enum": [ + "development", + "production" + ] }, - "format": { + "createdAt": { + "type": "string", + "format": "date-time" + }, + "payloadAvailability": { "type": "string", "enum": [ - "native", - "meta" + "available", + "not_retained", + "expired", + "redacted", + "unavailable" ] }, - "retryPolicy": { - "$ref": "#/components/schemas/WebhookRetryPolicyInput" + "payload": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/EncodedEventPayload" + } + ] }, - "headers": { - "type": "array", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/WebhookHeaderInput" - } + "replayableUntil": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metadataExpiresAt": { + "type": "string", + "format": "date-time" } }, - "minProperties": 1 + "required": [ + "id", + "organizationId", + "projectId", + "type", + "source", + "environment", + "createdAt", + "payloadAvailability", + "payload", + "replayableUntil", + "metadataExpiresAt" + ] }, - "UpdateProjectWebhookInput": { + "ProjectEvent": { "type": "object", "additionalProperties": false, "properties": { - "url": { + "id": { + "type": "string", + "format": "uuid" + }, + "organizationId": { + "type": "string", + "format": "uuid" + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "type": { "type": "string", - "format": "uri", "minLength": 1, - "maxLength": 2048 + "maxLength": 128 }, - "eventTypes": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - } + "source": { + "type": "string", + "enum": [ + "runtime", + "platform", + "test" + ] }, - "enabled": { - "type": "boolean" + "environment": { + "type": "string", + "enum": [ + "development", + "production" + ] }, - "format": { + "createdAt": { + "type": "string", + "format": "date-time" + }, + "payloadAvailability": { "type": "string", "enum": [ - "native", - "meta" + "available", + "not_retained", + "expired", + "redacted", + "unavailable" + ] + }, + "payload": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/EncodedEventPayload" + } ] }, - "retryPolicy": { - "$ref": "#/components/schemas/WebhookRetryPolicyInput" + "replayableUntil": { + "type": "string", + "format": "date-time", + "nullable": true }, - "headers": { - "type": "array", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/WebhookHeaderInput" - } + "metadataExpiresAt": { + "type": "string", + "format": "date-time" } }, - "minProperties": 1 - }, - "RotateWebhookSecretInput": { - "type": "object", - "additionalProperties": false, - "properties": { - "overlapSeconds": { - "type": "integer", - "minimum": 0, - "maximum": 86400, - "default": 3600 - } - } + "required": [ + "id", + "organizationId", + "projectId", + "type", + "source", + "environment", + "createdAt", + "payloadAvailability", + "payload", + "replayableUntil", + "metadataExpiresAt" + ] }, - "OrganizationWebhook": { + "OrganizationWebhookDelivery": { "type": "object", "additionalProperties": false, "properties": { @@ -26454,12 +26510,6 @@ "type": "string", "format": "uuid" }, - "owner": { - "type": "string", - "enum": [ - "organization" - ] - }, "projectId": { "type": "string", "nullable": true, @@ -26467,44 +26517,73 @@ null ] }, - "url": { + "eventId": { "type": "string", - "format": "uri", - "maxLength": 2048 + "format": "uuid" }, - "eventTypes": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - } + "webhookId": { + "type": "string", + "format": "uuid" }, - "enabled": { - "type": "boolean" + "status": { + "type": "string", + "enum": [ + "pending", + "delivering", + "retrying", + "succeeded", + "failed" + ] }, - "format": { + "attemptCount": { + "type": "integer", + "minimum": 0 + }, + "capabilities": { + "type": "object", + "additionalProperties": false, + "properties": { + "retryable": { + "type": "boolean" + } + }, + "required": [ + "retryable" + ] + }, + "payloadAvailability": { "type": "string", "enum": [ - "native", - "meta" + "available", + "not_retained", + "expired", + "redacted", + "unavailable" ] }, - "retryPolicy": { - "$ref": "#/components/schemas/WebhookRetryPolicy" + "replayableUntil": { + "type": "string", + "format": "date-time", + "nullable": true }, - "headers": { - "type": "array", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/WebhookHeaderMetadata" - } + "metadataExpiresAt": { + "type": "string", + "format": "date-time" }, - "secret": { - "$ref": "#/components/schemas/WebhookSigningSecretMetadata" + "nextAttemptAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "lastAttemptAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completedAt": { + "type": "string", + "format": "date-time", + "nullable": true }, "createdAt": { "type": "string", @@ -26513,25 +26592,50 @@ "updatedAt": { "type": "string", "format": "date-time" + }, + "lastOutcome": { + "type": "object", + "nullable": true, + "additionalProperties": false, + "properties": { + "statusCode": { + "type": "integer", + "minimum": 100, + "maximum": 599, + "nullable": true + }, + "errorCode": { + "type": "string", + "nullable": true + } + }, + "required": [ + "statusCode", + "errorCode" + ] } }, "required": [ "id", "organizationId", - "owner", "projectId", - "url", - "eventTypes", - "enabled", - "format", - "retryPolicy", - "headers", - "secret", + "eventId", + "webhookId", + "status", + "attemptCount", + "capabilities", + "payloadAvailability", + "replayableUntil", + "metadataExpiresAt", + "nextAttemptAt", + "lastAttemptAt", + "completedAt", "createdAt", - "updatedAt" + "updatedAt", + "lastOutcome" ] }, - "ProjectWebhook": { + "ProjectWebhookDelivery": { "type": "object", "additionalProperties": false, "properties": { @@ -26543,54 +26647,77 @@ "type": "string", "format": "uuid" }, - "owner": { + "projectId": { "type": "string", - "enum": [ - "project" - ] + "format": "uuid" }, - "projectId": { + "eventId": { "type": "string", "format": "uuid" }, - "url": { + "webhookId": { "type": "string", - "format": "uri", - "maxLength": 2048 + "format": "uuid" }, - "eventTypes": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - } + "status": { + "type": "string", + "enum": [ + "pending", + "delivering", + "retrying", + "succeeded", + "failed" + ] }, - "enabled": { - "type": "boolean" + "attemptCount": { + "type": "integer", + "minimum": 0 }, - "format": { + "capabilities": { + "type": "object", + "additionalProperties": false, + "properties": { + "retryable": { + "type": "boolean" + } + }, + "required": [ + "retryable" + ] + }, + "payloadAvailability": { "type": "string", "enum": [ - "native", - "meta" + "available", + "not_retained", + "expired", + "redacted", + "unavailable" ] }, - "retryPolicy": { - "$ref": "#/components/schemas/WebhookRetryPolicy" + "replayableUntil": { + "type": "string", + "format": "date-time", + "nullable": true }, - "headers": { - "type": "array", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/WebhookHeaderMetadata" - } + "metadataExpiresAt": { + "type": "string", + "format": "date-time" + }, + "nextAttemptAt": { + "type": "string", + "format": "date-time", + "nullable": true }, - "secret": { - "$ref": "#/components/schemas/WebhookSigningSecretMetadata" + "lastAttemptAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completedAt": { + "type": "string", + "format": "date-time", + "nullable": true }, "createdAt": { "type": "string", @@ -26599,489 +26726,390 @@ "updatedAt": { "type": "string", "format": "date-time" + }, + "lastOutcome": { + "type": "object", + "nullable": true, + "additionalProperties": false, + "properties": { + "statusCode": { + "type": "integer", + "minimum": 100, + "maximum": 599, + "nullable": true + }, + "errorCode": { + "type": "string", + "nullable": true + } + }, + "required": [ + "statusCode", + "errorCode" + ] } }, "required": [ "id", "organizationId", - "owner", "projectId", - "url", - "eventTypes", - "enabled", - "format", - "retryPolicy", - "headers", - "secret", + "eventId", + "webhookId", + "status", + "attemptCount", + "capabilities", + "payloadAvailability", + "replayableUntil", + "metadataExpiresAt", + "nextAttemptAt", + "lastAttemptAt", + "completedAt", "createdAt", - "updatedAt" + "updatedAt", + "lastOutcome" ] }, - "OrganizationWebhookCreationReceipt": { + "OrganizationWebhookDeliveryAttempt": { "type": "object", "additionalProperties": false, "properties": { - "webhook": { - "$ref": "#/components/schemas/OrganizationWebhook" - }, - "operationId": { + "id": { "type": "string", - "nullable": true, - "enum": [ - null - ] - }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" + "format": "uuid" }, - "secret": { + "organizationId": { "type": "string", - "nullable": true, - "writeOnly": true - }, - "secretAvailable": { - "type": "boolean" - } - }, - "required": [ - "webhook", - "operationId", - "idempotency", - "secret", - "secretAvailable" - ] - }, - "ProjectWebhookCreationReceipt": { - "type": "object", - "additionalProperties": false, - "properties": { - "webhook": { - "$ref": "#/components/schemas/ProjectWebhook" + "format": "uuid" }, - "operationId": { + "projectId": { "type": "string", "nullable": true, "enum": [ null ] }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - }, - "secret": { + "deliveryId": { "type": "string", - "nullable": true, - "writeOnly": true + "format": "uuid" }, - "secretAvailable": { - "type": "boolean" - } - }, - "required": [ - "webhook", - "operationId", - "idempotency", - "secret", - "secretAvailable" - ] - }, - "OrganizationWebhookMutationReceipt": { - "type": "object", - "additionalProperties": false, - "properties": { - "webhook": { - "$ref": "#/components/schemas/OrganizationWebhook" + "number": { + "type": "integer", + "minimum": 1 }, - "operationId": { + "status": { "type": "string", - "nullable": true, "enum": [ - null + "pending", + "delivering", + "succeeded", + "failed" ] }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - } - }, - "required": [ - "webhook", - "operationId", - "idempotency" - ] - }, - "ProjectWebhookMutationReceipt": { - "type": "object", - "additionalProperties": false, - "properties": { - "webhook": { - "$ref": "#/components/schemas/ProjectWebhook" - }, - "operationId": { + "startedAt": { "type": "string", - "nullable": true, - "enum": [ - null - ] + "format": "date-time", + "nullable": true }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - } - }, - "required": [ - "webhook", - "operationId", - "idempotency" - ] - }, - "OrganizationWebhookDeletionReceipt": { - "type": "object", - "additionalProperties": false, - "properties": { - "webhookId": { + "completedAt": { "type": "string", - "format": "uuid" - }, - "deleted": { - "type": "boolean", - "enum": [ - true - ] + "format": "date-time", + "nullable": true }, - "operationId": { + "nextRetryAt": { "type": "string", - "nullable": true, - "enum": [ - null - ] + "format": "date-time", + "nullable": true }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - } - }, - "required": [ - "webhookId", - "deleted", - "operationId", - "idempotency" - ] - }, - "ProjectWebhookDeletionReceipt": { - "type": "object", - "additionalProperties": false, - "properties": { - "webhookId": { - "type": "string", - "format": "uuid" + "durationMs": { + "type": "integer", + "minimum": 0, + "nullable": true }, - "deleted": { - "type": "boolean", - "enum": [ - true - ] + "statusCode": { + "type": "integer", + "minimum": 100, + "maximum": 599, + "nullable": true }, - "operationId": { + "errorCode": { "type": "string", + "nullable": true + }, + "response": { + "type": "object", "nullable": true, - "enum": [ - null + "additionalProperties": false, + "description": "A redacted excerpt from a failed HTTP response. Successful responses and network errors do not include a response body.", + "properties": { + "contentType": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "excerpt": { + "type": "string", + "x-polymorfa-max-utf8-bytes": 8192, + "description": "Redacted response text, limited to 8192 UTF-8 bytes." + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "contentType", + "excerpt", + "truncated" ] }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" + "metadataExpiresAt": { + "type": "string", + "format": "date-time" } }, "required": [ - "webhookId", - "deleted", - "operationId", - "idempotency" + "id", + "organizationId", + "projectId", + "deliveryId", + "number", + "status", + "startedAt", + "completedAt", + "nextRetryAt", + "durationMs", + "statusCode", + "errorCode", + "response", + "metadataExpiresAt" ] }, - "OrganizationWebhookSecretRotationReceipt": { + "ProjectWebhookDeliveryAttempt": { "type": "object", "additionalProperties": false, "properties": { - "webhookId": { + "id": { "type": "string", "format": "uuid" }, - "operationId": { + "organizationId": { "type": "string", - "nullable": true, - "enum": [ - null - ] + "format": "uuid" }, - "secret": { + "projectId": { "type": "string", - "nullable": true, - "writeOnly": true - }, - "secretAvailable": { - "type": "boolean" - }, - "secretMetadata": { - "$ref": "#/components/schemas/WebhookSigningSecretMetadata" + "format": "uuid" }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - } - }, - "required": [ - "webhookId", - "operationId", - "secret", - "secretAvailable", - "secretMetadata", - "idempotency" - ] - }, - "ProjectWebhookSecretRotationReceipt": { - "type": "object", - "additionalProperties": false, - "properties": { - "webhookId": { + "deliveryId": { "type": "string", "format": "uuid" }, - "operationId": { + "number": { + "type": "integer", + "minimum": 1 + }, + "status": { "type": "string", - "nullable": true, "enum": [ - null + "pending", + "delivering", + "succeeded", + "failed" ] }, - "secret": { + "startedAt": { "type": "string", - "nullable": true, - "writeOnly": true - }, - "secretAvailable": { - "type": "boolean" + "format": "date-time", + "nullable": true }, - "secretMetadata": { - "$ref": "#/components/schemas/WebhookSigningSecretMetadata" + "completedAt": { + "type": "string", + "format": "date-time", + "nullable": true }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - } - }, - "required": [ - "webhookId", - "operationId", - "secret", - "secretAvailable", - "secretMetadata", - "idempotency" - ] - }, - "EncodedEventPayload": { - "type": "object", - "additionalProperties": false, - "properties": { - "encoding": { + "nextRetryAt": { "type": "string", - "enum": [ - "base64" - ] + "format": "date-time", + "nullable": true }, - "contentType": { + "durationMs": { + "type": "integer", + "minimum": 0, + "nullable": true + }, + "statusCode": { + "type": "integer", + "minimum": 100, + "maximum": 599, + "nullable": true + }, + "errorCode": { "type": "string", - "enum": [ - "application/json" + "nullable": true + }, + "response": { + "type": "object", + "nullable": true, + "additionalProperties": false, + "description": "A redacted excerpt from a failed HTTP response. Successful responses and network errors do not include a response body.", + "properties": { + "contentType": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "excerpt": { + "type": "string", + "x-polymorfa-max-utf8-bytes": 8192, + "description": "Redacted response text, limited to 8192 UTF-8 bytes." + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "contentType", + "excerpt", + "truncated" ] }, - "data": { + "metadataExpiresAt": { "type": "string", - "format": "byte", - "maxLength": 87384, - "description": "Canonical base64 for the complete native webhook body; decoded bytes are limited to 65,536 bytes." + "format": "date-time" } }, "required": [ - "encoding", - "contentType", - "data" - ] - }, - "PayloadAvailability": { - "type": "string", - "enum": [ - "available", - "not_retained", - "expired", - "redacted", - "unavailable" + "id", + "organizationId", + "projectId", + "deliveryId", + "number", + "status", + "startedAt", + "completedAt", + "nextRetryAt", + "durationMs", + "statusCode", + "errorCode", + "response", + "metadataExpiresAt" ] }, - "OrganizationEvent": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "organizationId": { - "type": "string", - "format": "uuid" - }, - "projectId": { + "JsonValue": { + "nullable": true, + "oneOf": [ + { "type": "string", "nullable": true, "enum": [ null ] }, - "type": { - "type": "string", - "minLength": 1, - "maxLength": 128 + { + "type": "string" }, - "source": { - "type": "string", - "enum": [ - "runtime", - "platform", - "test" - ] + { + "type": "number" }, - "environment": { - "type": "string", - "enum": [ - "development", - "production" - ] + { + "type": "boolean" }, - "createdAt": { - "type": "string", - "format": "date-time" + { + "type": "array", + "items": { + "$ref": "#/components/schemas/JsonValue" + } }, - "payloadAvailability": { - "type": "string", - "enum": [ - "available", - "not_retained", - "expired", - "redacted", - "unavailable" - ] + { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/JsonValue" + } + } + ] + }, + "OperationCapabilities": { + "type": "object", + "additionalProperties": false, + "properties": { + "cancellable": { + "type": "boolean" }, - "payload": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/EncodedEventPayload" - } - ] + "watchable": { + "type": "boolean" + } + }, + "required": [ + "cancellable", + "watchable" + ] + }, + "OperationProgress": { + "type": "object", + "additionalProperties": false, + "properties": { + "code": { + "type": "string" }, - "replayableUntil": { - "type": "string", - "format": "date-time", + "current": { + "type": "integer", "nullable": true }, - "metadataExpiresAt": { - "type": "string", - "format": "date-time" + "total": { + "type": "integer", + "nullable": true } }, "required": [ - "id", - "organizationId", - "projectId", - "type", - "source", - "environment", - "createdAt", - "payloadAvailability", - "payload", - "replayableUntil", - "metadataExpiresAt" + "code", + "current", + "total" ] }, - "ProjectEvent": { + "OperationError": { "type": "object", "additionalProperties": false, "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "organizationId": { - "type": "string", - "format": "uuid" - }, - "projectId": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "source": { - "type": "string", - "enum": [ - "runtime", - "platform", - "test" - ] - }, - "environment": { - "type": "string", - "enum": [ - "development", - "production" - ] - }, - "createdAt": { - "type": "string", - "format": "date-time" + "code": { + "type": "string" }, - "payloadAvailability": { - "type": "string", - "enum": [ - "available", - "not_retained", - "expired", - "redacted", - "unavailable" - ] + "retryable": { + "type": "boolean" }, - "payload": { + "details": { + "type": "object", "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/EncodedEventPayload" - } - ] - }, - "replayableUntil": { - "type": "string", - "format": "date-time", - "nullable": true + "additionalProperties": { + "$ref": "#/components/schemas/JsonValue" + } + } + }, + "required": [ + "code", + "retryable", + "details" + ] + }, + "OperationActionRequired": { + "type": "object", + "additionalProperties": false, + "properties": { + "code": { + "type": "string" }, - "metadataExpiresAt": { - "type": "string", - "format": "date-time" + "details": { + "type": "object", + "nullable": true, + "additionalProperties": { + "$ref": "#/components/schemas/JsonValue" + } } }, "required": [ - "id", - "organizationId", - "projectId", - "type", - "source", - "environment", - "createdAt", - "payloadAvailability", - "payload", - "replayableUntil", - "metadataExpiresAt" + "code", + "details" ] }, - "OrganizationWebhookDelivery": { + "OrganizationOperation": { "type": "object", "additionalProperties": false, "properties": { @@ -27100,73 +27128,88 @@ null ] }, - "eventId": { - "type": "string", - "format": "uuid" - }, - "webhookId": { - "type": "string", - "format": "uuid" - }, - "status": { + "kind": { "type": "string", "enum": [ - "pending", - "delivering", - "retrying", - "succeeded", - "failed" + "auth_projection_repair", + "session_lifecycle", + "auth_session_purge", + "label_projection_purge", + "billing_reconciliation", + "auto_top_up", + "campaign", + "production_enrollment", + "retention_sweep", + "webhook_redrive" ] }, - "attemptCount": { - "type": "integer", - "minimum": 0 - }, - "capabilities": { + "resource": { "type": "object", "additionalProperties": false, "properties": { - "retryable": { - "type": "boolean" + "type": { + "type": "string" + }, + "id": { + "type": "string" } }, "required": [ - "retryable" + "type", + "id" ] }, - "payloadAvailability": { + "status": { "type": "string", "enum": [ - "available", - "not_retained", - "expired", - "redacted", - "unavailable" + "pending", + "running", + "action_required", + "cancelling", + "succeeded", + "failed", + "cancelled" ] }, - "replayableUntil": { - "type": "string", - "format": "date-time", - "nullable": true + "sequence": { + "type": "integer", + "format": "int64", + "minimum": 1 }, - "metadataExpiresAt": { - "type": "string", - "format": "date-time" + "capabilities": { + "$ref": "#/components/schemas/OperationCapabilities" }, - "nextAttemptAt": { - "type": "string", - "format": "date-time", - "nullable": true + "progress": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/OperationProgress" + } + ] }, - "lastAttemptAt": { - "type": "string", - "format": "date-time", - "nullable": true + "result": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/JsonValue" + } + ] }, - "completedAt": { - "type": "string", - "format": "date-time", - "nullable": true + "error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/OperationError" + } + ] + }, + "actionRequired": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/OperationActionRequired" + } + ] }, "createdAt": { "type": "string", @@ -27176,49 +27219,31 @@ "type": "string", "format": "date-time" }, - "lastOutcome": { - "type": "object", - "nullable": true, - "additionalProperties": false, - "properties": { - "statusCode": { - "type": "integer", - "minimum": 100, - "maximum": 599, - "nullable": true - }, - "errorCode": { - "type": "string", - "nullable": true - } - }, - "required": [ - "statusCode", - "errorCode" - ] + "completedAt": { + "type": "string", + "format": "date-time", + "nullable": true } }, "required": [ "id", "organizationId", "projectId", - "eventId", - "webhookId", + "kind", + "resource", "status", - "attemptCount", + "sequence", "capabilities", - "payloadAvailability", - "replayableUntil", - "metadataExpiresAt", - "nextAttemptAt", - "lastAttemptAt", - "completedAt", + "progress", + "result", + "error", + "actionRequired", "createdAt", "updatedAt", - "lastOutcome" + "completedAt" ] }, - "ProjectWebhookDelivery": { + "ProjectOperation": { "type": "object", "additionalProperties": false, "properties": { @@ -27234,73 +27259,88 @@ "type": "string", "format": "uuid" }, - "eventId": { - "type": "string", - "format": "uuid" - }, - "webhookId": { - "type": "string", - "format": "uuid" - }, - "status": { + "kind": { "type": "string", "enum": [ - "pending", - "delivering", - "retrying", - "succeeded", - "failed" + "auth_projection_repair", + "session_lifecycle", + "auth_session_purge", + "label_projection_purge", + "billing_reconciliation", + "auto_top_up", + "campaign", + "production_enrollment", + "retention_sweep", + "webhook_redrive" ] }, - "attemptCount": { - "type": "integer", - "minimum": 0 - }, - "capabilities": { + "resource": { "type": "object", "additionalProperties": false, "properties": { - "retryable": { - "type": "boolean" + "type": { + "type": "string" + }, + "id": { + "type": "string" } }, "required": [ - "retryable" + "type", + "id" ] }, - "payloadAvailability": { + "status": { "type": "string", "enum": [ - "available", - "not_retained", - "expired", - "redacted", - "unavailable" + "pending", + "running", + "action_required", + "cancelling", + "succeeded", + "failed", + "cancelled" ] }, - "replayableUntil": { - "type": "string", - "format": "date-time", - "nullable": true + "sequence": { + "type": "integer", + "format": "int64", + "minimum": 1 }, - "metadataExpiresAt": { - "type": "string", - "format": "date-time" + "capabilities": { + "$ref": "#/components/schemas/OperationCapabilities" }, - "nextAttemptAt": { - "type": "string", - "format": "date-time", - "nullable": true + "progress": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/OperationProgress" + } + ] }, - "lastAttemptAt": { - "type": "string", - "format": "date-time", - "nullable": true + "result": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/JsonValue" + } + ] }, - "completedAt": { - "type": "string", - "format": "date-time", - "nullable": true + "error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/OperationError" + } + ] + }, + "actionRequired": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/OperationActionRequired" + } + ] }, "createdAt": { "type": "string", @@ -27310,163 +27350,124 @@ "type": "string", "format": "date-time" }, - "lastOutcome": { - "type": "object", - "nullable": true, - "additionalProperties": false, - "properties": { - "statusCode": { - "type": "integer", - "minimum": 100, - "maximum": 599, - "nullable": true - }, - "errorCode": { - "type": "string", - "nullable": true - } - }, - "required": [ - "statusCode", - "errorCode" - ] + "completedAt": { + "type": "string", + "format": "date-time", + "nullable": true } }, "required": [ "id", "organizationId", "projectId", - "eventId", - "webhookId", + "kind", + "resource", "status", - "attemptCount", + "sequence", "capabilities", - "payloadAvailability", - "replayableUntil", - "metadataExpiresAt", - "nextAttemptAt", - "lastAttemptAt", - "completedAt", + "progress", + "result", + "error", + "actionRequired", "createdAt", "updatedAt", - "lastOutcome" + "completedAt" ] }, - "OrganizationWebhookDeliveryAttempt": { + "OperationTransition": { "type": "object", "additionalProperties": false, "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "organizationId": { - "type": "string", - "format": "uuid" - }, - "projectId": { - "type": "string", - "nullable": true, - "enum": [ - null - ] - }, - "deliveryId": { + "operationId": { "type": "string", "format": "uuid" }, - "number": { + "sequence": { "type": "integer", + "format": "int64", "minimum": 1 }, - "status": { + "fromStatus": { "type": "string", "enum": [ "pending", - "delivering", + "running", + "action_required", + "cancelling", "succeeded", - "failed" - ] - }, - "startedAt": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "completedAt": { - "type": "string", - "format": "date-time", + "failed", + "cancelled", + null + ], "nullable": true }, - "nextRetryAt": { + "toStatus": { "type": "string", - "format": "date-time", - "nullable": true - }, - "durationMs": { - "type": "integer", - "minimum": 0, - "nullable": true + "enum": [ + "pending", + "running", + "action_required", + "cancelling", + "succeeded", + "failed", + "cancelled" + ] }, - "statusCode": { - "type": "integer", - "minimum": 100, - "maximum": 599, + "reasonCode": { + "type": "string", "nullable": true }, - "errorCode": { + "occurredAt": { "type": "string", - "nullable": true + "format": "date-time" }, - "response": { + "snapshot": { "type": "object", - "nullable": true, "additionalProperties": false, - "description": "A redacted excerpt from a failed HTTP response. Successful responses and network errors do not include a response body.", "properties": { - "contentType": { - "type": "string", - "minLength": 1, - "maxLength": 128 + "progress": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/OperationProgress" + } + ] }, - "excerpt": { - "type": "string", - "x-polymorfa-max-utf8-bytes": 8192, - "description": "Redacted response text, limited to 8192 UTF-8 bytes." + "error": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/OperationError" + } + ] }, - "truncated": { - "type": "boolean" + "actionRequired": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/OperationActionRequired" + } + ] } }, "required": [ - "contentType", - "excerpt", - "truncated" + "progress", + "error", + "actionRequired" ] - }, - "metadataExpiresAt": { - "type": "string", - "format": "date-time" } }, "required": [ - "id", - "organizationId", - "projectId", - "deliveryId", - "number", - "status", - "startedAt", - "completedAt", - "nextRetryAt", - "durationMs", - "statusCode", - "errorCode", - "response", - "metadataExpiresAt" + "operationId", + "sequence", + "fromStatus", + "toStatus", + "reasonCode", + "occurredAt", + "snapshot" ] }, - "ProjectWebhookDeliveryAttempt": { + "IdempotencyReceipt": { "type": "object", "additionalProperties": false, "properties": { @@ -27474,936 +27475,1532 @@ "type": "string", "format": "uuid" }, - "organizationId": { - "type": "string", - "format": "uuid" - }, - "projectId": { - "type": "string", - "format": "uuid" - }, - "deliveryId": { - "type": "string", - "format": "uuid" - }, - "number": { - "type": "integer", - "minimum": 1 - }, - "status": { + "key": { "type": "string", - "enum": [ - "pending", - "delivering", - "succeeded", - "failed" - ] + "minLength": 1, + "maxLength": 255 }, - "startedAt": { - "type": "string", - "format": "date-time", - "nullable": true + "replayed": { + "type": "boolean" }, - "completedAt": { + "createdAt": { "type": "string", - "format": "date-time", - "nullable": true + "format": "date-time" }, - "nextRetryAt": { + "expiresAt": { "type": "string", - "format": "date-time", - "nullable": true - }, - "durationMs": { - "type": "integer", - "minimum": 0, - "nullable": true - }, - "statusCode": { - "type": "integer", - "minimum": 100, - "maximum": 599, - "nullable": true - }, - "errorCode": { + "format": "date-time" + } + }, + "required": [ + "id", + "key", + "replayed", + "createdAt", + "expiresAt" + ] + }, + "ReplayEventInput": { + "type": "object", + "additionalProperties": false, + "properties": { + "webhookId": { "type": "string", - "nullable": true + "format": "uuid" + } + }, + "required": [ + "webhookId" + ] + }, + "RetryWebhookDeliveryInput": { + "type": "object", + "additionalProperties": false, + "maxProperties": 0 + }, + "TestWebhookInput": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "eventType": { + "type": "string", + "default": "polymorfa.test", + "pattern": "^[a-z0-9][a-z0-9_.-]{0,127}$" + } + } }, - "response": { + { "type": "object", - "nullable": true, "additionalProperties": false, - "description": "A redacted excerpt from a failed HTTP response. Successful responses and network errors do not include a response body.", "properties": { - "contentType": { + "eventType": { "type": "string", - "minLength": 1, - "maxLength": 128 + "pattern": "^[a-z0-9][a-z0-9_.-]{0,127}$" }, - "excerpt": { - "type": "string", - "x-polymorfa-max-utf8-bytes": 8192, - "description": "Redacted response text, limited to 8192 UTF-8 bytes." + "body": { + "$ref": "#/components/schemas/EncodedEventPayload" }, - "truncated": { - "type": "boolean" + "sessionId": { + "type": "string", + "format": "uuid" } }, "required": [ - "contentType", - "excerpt", - "truncated" + "body", + "sessionId" ] - }, - "metadataExpiresAt": { - "type": "string", - "format": "date-time" } - }, - "required": [ - "id", - "organizationId", - "projectId", - "deliveryId", - "number", - "status", - "startedAt", - "completedAt", - "nextRetryAt", - "durationMs", - "statusCode", - "errorCode", - "response", - "metadataExpiresAt" ] }, - "JsonValue": { - "nullable": true, - "oneOf": [ - { + "OrganizationEventReplayReceipt": { + "type": "object", + "additionalProperties": false, + "properties": { + "operationId": { "type": "string", - "nullable": true, - "enum": [ - null - ] - }, - { - "type": "string" - }, - { - "type": "number" + "format": "uuid" }, - { - "type": "boolean" + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/JsonValue" - } + "eventId": { + "type": "string", + "format": "uuid" }, - { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/JsonValue" - } + "deliveryId": { + "type": "string", + "format": "uuid" } + }, + "required": [ + "eventId", + "deliveryId", + "operationId", + "idempotency" ] }, - "OperationCapabilities": { + "ProjectEventReplayReceipt": { "type": "object", "additionalProperties": false, "properties": { - "cancellable": { - "type": "boolean" + "operationId": { + "type": "string", + "format": "uuid" }, - "watchable": { - "type": "boolean" + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" + }, + "eventId": { + "type": "string", + "format": "uuid" + }, + "deliveryId": { + "type": "string", + "format": "uuid" } }, "required": [ - "cancellable", - "watchable" + "eventId", + "deliveryId", + "operationId", + "idempotency" ] }, - "OperationProgress": { + "OrganizationWebhookDeliveryRetryReceipt": { "type": "object", "additionalProperties": false, "properties": { - "code": { - "type": "string" + "operationId": { + "type": "string", + "format": "uuid" }, - "current": { - "type": "integer", - "nullable": true + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" }, - "total": { - "type": "integer", - "nullable": true + "deliveryId": { + "type": "string", + "format": "uuid" + }, + "attemptId": { + "type": "string", + "format": "uuid" } }, "required": [ - "code", - "current", - "total" + "deliveryId", + "attemptId", + "operationId", + "idempotency" ] }, - "OperationError": { + "ProjectWebhookDeliveryRetryReceipt": { "type": "object", "additionalProperties": false, "properties": { - "code": { - "type": "string" + "operationId": { + "type": "string", + "format": "uuid" }, - "retryable": { - "type": "boolean" + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" }, - "details": { - "type": "object", - "nullable": true, - "additionalProperties": { - "$ref": "#/components/schemas/JsonValue" - } + "deliveryId": { + "type": "string", + "format": "uuid" + }, + "attemptId": { + "type": "string", + "format": "uuid" } }, "required": [ - "code", - "retryable", - "details" + "deliveryId", + "attemptId", + "operationId", + "idempotency" ] }, - "OperationActionRequired": { + "OrganizationWebhookTestReceipt": { "type": "object", "additionalProperties": false, "properties": { - "code": { - "type": "string" + "operationId": { + "type": "string", + "format": "uuid" }, - "details": { - "type": "object", - "nullable": true, - "additionalProperties": { - "$ref": "#/components/schemas/JsonValue" - } + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" + }, + "eventId": { + "type": "string", + "format": "uuid" + }, + "deliveryId": { + "type": "string", + "format": "uuid" } }, "required": [ - "code", - "details" + "eventId", + "deliveryId", + "operationId", + "idempotency" ] }, - "OrganizationOperation": { + "ProjectWebhookTestReceipt": { "type": "object", "additionalProperties": false, "properties": { - "id": { + "operationId": { "type": "string", "format": "uuid" }, - "organizationId": { + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" + }, + "eventId": { "type": "string", "format": "uuid" }, - "projectId": { + "deliveryId": { "type": "string", - "nullable": true, - "enum": [ - null - ] + "format": "uuid" + } + }, + "required": [ + "eventId", + "deliveryId", + "operationId", + "idempotency" + ] + }, + "OrganizationOperationCancellationReceipt": { + "type": "object", + "additionalProperties": false, + "properties": { + "operation": { + "$ref": "#/components/schemas/OrganizationOperation" }, - "kind": { + "operationId": { "type": "string", - "enum": [ - "auth_projection_repair", - "session_lifecycle", - "auth_session_purge", - "label_projection_purge", - "billing_reconciliation", - "auto_top_up", - "campaign", - "production_enrollment", - "retention_sweep", - "webhook_redrive" - ] + "format": "uuid" }, - "resource": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": [ - "type", - "id" - ] + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" + } + }, + "required": [ + "operation", + "operationId", + "idempotency" + ] + }, + "ProjectOperationCancellationReceipt": { + "type": "object", + "additionalProperties": false, + "properties": { + "operation": { + "$ref": "#/components/schemas/ProjectOperation" }, - "status": { + "operationId": { "type": "string", - "enum": [ - "pending", - "running", - "action_required", - "cancelling", - "succeeded", - "failed", - "cancelled" - ] - }, - "sequence": { - "type": "integer", - "format": "int64", - "minimum": 1 - }, - "capabilities": { - "$ref": "#/components/schemas/OperationCapabilities" - }, - "progress": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/OperationProgress" - } - ] + "format": "uuid" }, - "result": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/JsonValue" - } + "idempotency": { + "$ref": "#/components/schemas/IdempotencyReceipt" + } + }, + "required": [ + "operation", + "operationId", + "idempotency" + ] + }, + "OperationConflictEnvelope": { + "type": "object", + "additionalProperties": false, + "properties": { + "error": { + "type": "string", + "enum": [ + "operation_conflict" ] }, - "error": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/OperationError" - } + "code": { + "type": "string", + "enum": [ + "operation_conflict" ] }, - "actionRequired": { - "nullable": true, - "allOf": [ + "operation": { + "oneOf": [ { - "$ref": "#/components/schemas/OperationActionRequired" + "$ref": "#/components/schemas/OrganizationOperation" + }, + { + "$ref": "#/components/schemas/ProjectOperation" } ] - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - }, - "completedAt": { - "type": "string", - "format": "date-time", - "nullable": true } }, "required": [ - "id", - "organizationId", - "projectId", - "kind", - "resource", - "status", - "sequence", - "capabilities", - "progress", - "result", "error", - "actionRequired", - "createdAt", - "updatedAt", - "completedAt" + "code", + "operation" ] }, - "ProjectOperation": { + "IdempotencyConflictEnvelope": { "type": "object", "additionalProperties": false, "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "organizationId": { - "type": "string", - "format": "uuid" - }, - "projectId": { + "error": { "type": "string", - "format": "uuid" + "enum": [ + "idempotency_conflict" + ] }, - "kind": { + "code": { "type": "string", "enum": [ - "auth_projection_repair", - "session_lifecycle", - "auth_session_purge", - "label_projection_purge", - "billing_reconciliation", - "auto_top_up", - "campaign", - "production_enrollment", - "retention_sweep", - "webhook_redrive" + "idempotency_conflict" ] + } + }, + "required": [ + "error", + "code" + ] + }, + "OperationCancellationConflictEnvelope": { + "oneOf": [ + { + "$ref": "#/components/schemas/OperationConflictEnvelope" }, - "resource": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": [ - "type", - "id" - ] + { + "$ref": "#/components/schemas/IdempotencyConflictEnvelope" + } + ] + }, + "PlatformAccessSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" }, - "status": { + "message": { + "type": "string" + } + }, + "required": [ + "success" + ] + }, + "PlatformAccessClientToken": { + "type": "object", + "properties": { + "token": { "type": "string", + "description": "Client token with pmfa_ct_ prefix" + }, + "expiresAt": { + "type": "string" + } + }, + "required": [ + "token", + "expiresAt" + ] + }, + "PlatformAccessMintClientTokenResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", "enum": [ - "pending", - "running", - "action_required", - "cancelling", - "succeeded", - "failed", - "cancelled" + true ] }, - "sequence": { - "type": "integer", - "format": "int64", - "minimum": 1 + "data": { + "$ref": "#/components/schemas/PlatformAccessClientToken" + } + }, + "required": [ + "success", + "data" + ] + }, + "PlatformAccessMintClientTokenRequest": { + "type": "object", + "properties": { + "session": { + "type": "string", + "minLength": 1, + "description": "ID of the session this token can access." }, - "capabilities": { - "$ref": "#/components/schemas/OperationCapabilities" + "ephemeralId": { + "type": "string", + "minLength": 1, + "description": "Your identifier for the browser or mobile user receiving this token." }, - "progress": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/OperationProgress" - } - ] + "ttlSeconds": { + "type": "integer", + "description": "Token lifetime in seconds. Must be at least 1 and within the maximum allowed lifetime. Omit to use that maximum." + } + }, + "required": [ + "session", + "ephemeralId" + ] + }, + "PlatformAccessClientRules": { + "type": "object", + "properties": { + "actions": { + "type": "array", + "items": { + "type": "string" + } }, - "result": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/JsonValue" - } - ] + "recipientMode": { + "type": "string" }, - "error": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/OperationError" - } - ] + "verifiedJids": { + "type": "array", + "items": { + "type": "string" + } }, - "actionRequired": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/OperationActionRequired" + "rateLimits": { + "type": "object", + "properties": { + "perMinute": { + "type": "integer", + "minimum": 0 + }, + "perDay": { + "type": "integer", + "minimum": 0 } + } + } + }, + "required": [ + "actions", + "recipientMode" + ] + }, + "PlatformAccessGetClientRulesResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true ] }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - }, - "completedAt": { - "type": "string", - "format": "date-time", - "nullable": true + "data": { + "$ref": "#/components/schemas/PlatformAccessClientRules" } }, "required": [ - "id", - "organizationId", - "projectId", - "kind", - "resource", - "status", - "sequence", - "capabilities", - "progress", - "result", - "error", - "actionRequired", - "createdAt", - "updatedAt", - "completedAt" + "success", + "data" ] }, - "OperationTransition": { + "PlatformAccessSetClientRulesRequest": { "type": "object", - "additionalProperties": false, "properties": { - "operationId": { + "recipientMode": { + "type": "string" + }, + "allowedActions": { "type": "string", - "format": "uuid" + "default": "" }, - "sequence": { + "rateLimit": { "type": "integer", - "format": "int64", - "minimum": 1 + "default": 0 }, - "fromStatus": { + "maxDaily": { + "type": "integer", + "default": 0 + }, + "allowedOrigins": { "type": "string", - "enum": [ - "pending", - "running", - "action_required", - "cancelling", - "succeeded", - "failed", - "cancelled", - null + "default": "" + }, + "enabled": { + "type": "boolean" + }, + "maxConcurrency": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "allowedNumber": { + "type": "string", + "default": "" + }, + "maxSetupsPerMinute": { + "type": "integer", + "minimum": 0, + "maximum": 600, + "default": 0 + } + }, + "required": [ + "recipientMode", + "enabled" + ] + }, + "PlatformAccessSessionConfigurationView": { + "type": "object", + "properties": { + "requestedHistory": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "required": [ + "mode", + "requestFull" ], - "nullable": true + "additionalProperties": false }, - "toStatus": { + "historyConsent": { "type": "string", "enum": [ "pending", - "running", - "action_required", - "cancelling", - "succeeded", - "failed", - "cancelled" + "accepted", + "declined" ] }, - "reasonCode": { - "type": "string", - "nullable": true - }, - "occurredAt": { - "type": "string", - "format": "date-time" - }, - "snapshot": { + "effective": { "type": "object", - "additionalProperties": false, "properties": { - "progress": { - "nullable": true, - "allOf": [ + "observation": { + "type": "object", + "properties": { + "presenceMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "typingMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "labelMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache", + "project" + ] + }, + "quickReplyMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + } + }, + "required": [ + "presenceMode", + "typingMode", + "labelMode", + "quickReplyMode" + ], + "additionalProperties": false + }, + "historySync": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "required": [ + "mode", + "requestFull" + ], + "additionalProperties": false + }, + "hms": { + "anyOf": [ { - "$ref": "#/components/schemas/OperationProgress" + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + false + ] + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + true + ] + }, + "region": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" + }, + "policy": { + "type": "string", + "enum": [ + "short", + "standard", + "extended", + "compliance", + "enterprise_archive" + ] + }, + "retentionDays": { + "type": "integer", + "minimum": 1, + "maximum": 2555 + }, + "policyVersion": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "legalHold": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "region", + "policy" + ], + "additionalProperties": false } ] + } + }, + "required": [ + "observation", + "historySync", + "hms" + ], + "additionalProperties": false + }, + "overrides": { + "type": "object", + "properties": { + "historySync": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "additionalProperties": false }, - "error": { - "nullable": true, - "allOf": [ + "hms": { + "anyOf": [ { - "$ref": "#/components/schemas/OperationError" + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + false + ] + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + true + ] + }, + "region": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" + }, + "policy": { + "type": "string", + "enum": [ + "short", + "standard", + "extended", + "compliance", + "enterprise_archive" + ] + }, + "retentionDays": { + "type": "integer", + "minimum": 1, + "maximum": 2555 + }, + "policyVersion": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "legalHold": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "region", + "policy" + ], + "additionalProperties": false } ] }, - "actionRequired": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/OperationActionRequired" + "observation": { + "type": "object", + "properties": { + "presenceMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "typingMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "labelMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache", + "project" + ] + }, + "quickReplyMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "sources": { + "type": "object", + "properties": { + "observation.presenceMode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + }, + "observation.typingMode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + }, + "observation.labelMode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + }, + "observation.quickReplyMode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + }, + "historySync.mode": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session", + "consent" + ] + }, + "historySync.requestFull": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session", + "consent" + ] + }, + "hms": { + "type": "string", + "enum": [ + "platform", + "team", + "project", + "session" + ] + } + }, + "required": [ + "observation.presenceMode", + "observation.typingMode", + "observation.labelMode", + "observation.quickReplyMode", + "historySync.mode", + "historySync.requestFull", + "hms" + ], + "additionalProperties": false + }, + "revisions": { + "type": "object", + "properties": { + "team": { + "type": "integer", + "minimum": 0 + }, + "project": { + "type": "integer", + "minimum": 0 + }, + "session": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "team", + "project", + "session" + ], + "additionalProperties": false + }, + "application": { + "type": "object", + "properties": { + "desiredGeneration": { + "type": "integer", + "minimum": 0 + }, + "appliedGeneration": { + "type": "integer", + "minimum": 0 + }, + "status": { + "type": "string", + "enum": [ + "pending", + "applied" ] } }, "required": [ - "progress", - "error", - "actionRequired" - ] + "desiredGeneration", + "appliedGeneration", + "status" + ], + "additionalProperties": false } }, "required": [ - "operationId", - "sequence", - "fromStatus", - "toStatus", - "reasonCode", - "occurredAt", - "snapshot" - ] + "requestedHistory", + "effective", + "overrides", + "sources", + "revisions" + ], + "additionalProperties": false }, - "IdempotencyReceipt": { + "PlatformAccessSession": { "type": "object", - "additionalProperties": false, "properties": { - "id": { + "sessionId": { "type": "string", - "format": "uuid" + "description": "Session ID. Use it to address this session in Platform and Messaging requests." }, - "key": { + "name": { "type": "string", - "minLength": 1, - "maxLength": 255 + "description": "Display label, or the session identifier when no label is set." }, - "replayed": { - "type": "boolean" + "externalId": { + "type": "string", + "description": "Integrator correlation supplied when creating the QuickLink for this session." }, - "createdAt": { + "tenantId": { + "type": "string" + }, + "type": { "type": "string", - "format": "date-time" + "enum": [ + "linked_device", + "cloud_api" + ], + "description": "How the session connects to WhatsApp. 'linked_device' pairs as a WhatsApp linked device; 'cloud_api' uses the Meta Cloud API. Immutable after create." }, - "expiresAt": { + "testMode": { + "type": "boolean", + "description": "Whether this session was created as a simulated testing number." + }, + "status": { "type": "string", - "format": "date-time" + "description": "Current status: CONNECTING, CONNECTED, or DISCONNECTED" + }, + "statusReason": { + "type": "string" + }, + "configuration": { + "$ref": "#/components/schemas/PlatformAccessSessionConfigurationView" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" } }, "required": [ - "id", - "key", - "replayed", + "sessionId", + "name", + "tenantId", + "type", + "testMode", + "status", "createdAt", - "expiresAt" + "updatedAt" ] }, - "ReplayEventInput": { + "PlatformAccessGetSessionResponse": { "type": "object", - "additionalProperties": false, "properties": { - "webhookId": { - "type": "string", - "format": "uuid" + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/PlatformAccessSession" } }, "required": [ - "webhookId" + "success", + "data" ] }, - "RetryWebhookDeliveryInput": { + "PlatformAccessUpdateSessionResponse": { "type": "object", - "additionalProperties": false, - "maxProperties": 0 - }, - "TestWebhookInput": { - "oneOf": [ - { - "type": "object", - "additionalProperties": false, - "properties": { - "eventType": { - "type": "string", - "default": "polymorfa.test", - "pattern": "^[a-z0-9][a-z0-9_.-]{0,127}$" - } - } + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] }, - { + "data": { + "$ref": "#/components/schemas/PlatformAccessSession" + } + }, + "required": [ + "success", + "data" + ] + }, + "PlatformAccessUpdateSessionRequest": { + "type": "object", + "properties": { + "configuration": { "type": "object", - "additionalProperties": false, "properties": { - "eventType": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9_.-]{0,127}$" - }, - "body": { - "$ref": "#/components/schemas/EncodedEventPayload" + "set": { + "type": "object", + "properties": { + "historySync": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "metadata_only", + "deliver" + ] + }, + "requestFull": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "hms": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + false + ] + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "enum": [ + true + ] + }, + "region": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]{0,31}$" + }, + "policy": { + "type": "string", + "enum": [ + "short", + "standard", + "extended", + "compliance", + "enterprise_archive" + ] + }, + "retentionDays": { + "type": "integer", + "minimum": 1, + "maximum": 2555 + }, + "policyVersion": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "legalHold": { + "type": "boolean" + } + }, + "required": [ + "enabled", + "region", + "policy" + ], + "additionalProperties": false + } + ] + }, + "observation": { + "type": "object", + "properties": { + "presenceMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "typingMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + }, + "labelMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache", + "project" + ] + }, + "quickReplyMode": { + "type": "string", + "enum": [ + "off", + "events", + "cache" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false }, - "sessionId": { - "type": "string", - "format": "uuid" + "reset": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "historySync", + "historySync.mode", + "historySync.requestFull", + "hms", + "observation", + "observation.presenceMode", + "observation.typingMode", + "observation.labelMode", + "observation.quickReplyMode" + ] + } } }, - "required": [ - "body", - "sessionId" - ] + "additionalProperties": false + }, + "revision": { + "type": "integer", + "minimum": 0 } - ] + }, + "required": [ + "configuration", + "revision" + ], + "additionalProperties": false }, - "OrganizationEventReplayReceipt": { + "PlatformAccessOperationAccepted": { "type": "object", - "additionalProperties": false, "properties": { - "operationId": { - "type": "string", - "format": "uuid" - }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" + "success": { + "type": "boolean", + "enum": [ + true + ] }, - "eventId": { - "type": "string", - "format": "uuid" + "message": { + "type": "string" }, - "deliveryId": { - "type": "string", - "format": "uuid" + "immediate": { + "type": "boolean" } }, "required": [ - "eventId", - "deliveryId", - "operationId", - "idempotency" + "success", + "message" ] }, - "ProjectEventReplayReceipt": { + "PlatformAccessWhatsAppAccount": { "type": "object", - "additionalProperties": false, "properties": { - "operationId": { + "id": { "type": "string", - "format": "uuid" + "pattern": "^[1-9][0-9]{0,18}$", + "description": "Account ID when supplied by the connection.", + "example": "739182640518203" }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" + "phoneNumber": { + "type": "string" }, - "eventId": { - "type": "string", - "format": "uuid" + "bsuid": { + "type": "string" }, - "deliveryId": { - "type": "string", - "format": "uuid" + "username": { + "type": "string" + }, + "pushName": { + "type": "string" + }, + "businessName": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "profilePicUrl": { + "type": "string" + } + }, + "required": [ + "pushName" + ] + }, + "PlatformAccessGetSessionAccountResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/PlatformAccessWhatsAppAccount" } }, "required": [ - "eventId", - "deliveryId", - "operationId", - "idempotency" + "success", + "data" ] }, - "OrganizationWebhookDeliveryRetryReceipt": { + "PlatformAccessHandoffKeysResponse": { "type": "object", - "additionalProperties": false, "properties": { - "operationId": { - "type": "string", - "format": "uuid" - }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - }, - "deliveryId": { + "runnerPublicKeyB64": { "type": "string", - "format": "uuid" + "description": "Curve25519 public key the customer must seal their snapshot to. Base64 (URL-safe, no padding)." }, - "attemptId": { - "type": "string", - "format": "uuid" + "handoffId": { + "type": "string" } }, "required": [ - "deliveryId", - "attemptId", - "operationId", - "idempotency" + "runnerPublicKeyB64", + "handoffId" ] }, - "ProjectWebhookDeliveryRetryReceipt": { + "PlatformAccessHandoffKeysSuccess": { "type": "object", - "additionalProperties": false, "properties": { - "operationId": { - "type": "string", - "format": "uuid" - }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - }, - "deliveryId": { - "type": "string", - "format": "uuid" + "success": { + "type": "boolean", + "enum": [ + true + ] }, - "attemptId": { - "type": "string", - "format": "uuid" + "data": { + "$ref": "#/components/schemas/PlatformAccessHandoffKeysResponse" } }, "required": [ - "deliveryId", - "attemptId", - "operationId", - "idempotency" + "success", + "data" ] }, - "OrganizationWebhookTestReceipt": { + "PlatformAccessHandoffImportSuccess": { "type": "object", - "additionalProperties": false, "properties": { - "operationId": { - "type": "string", - "format": "uuid" - }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - }, - "eventId": { - "type": "string", - "format": "uuid" + "success": { + "type": "boolean", + "enum": [ + true + ] }, - "deliveryId": { - "type": "string", - "format": "uuid" + "data": { + "$ref": "#/components/schemas/PlatformAccessSuccessResponse" } }, "required": [ - "eventId", - "deliveryId", - "operationId", - "idempotency" + "success", + "data" ] }, - "ProjectWebhookTestReceipt": { + "PlatformAccessHandoffImportRequest": { "type": "object", - "additionalProperties": false, "properties": { - "operationId": { + "handoffId": { "type": "string", - "format": "uuid" + "description": "Round-trip id returned by POST /handoff-keys." }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" - }, - "eventId": { + "sealedEnvelopeB64": { "type": "string", - "format": "uuid" + "description": "Sealed-box envelope (ephemeral_pub(32) || ciphertext) wrapping the customer's SignalStoreSnapshot. Base64." }, - "deliveryId": { + "snapshotHashHex": { "type": "string", - "format": "uuid" + "description": "SHA-256 of the unsealed snapshot JSON, hex-encoded. The runner verifies after decrypt before flipping bartenderMode." } }, "required": [ - "eventId", - "deliveryId", - "operationId", - "idempotency" + "handoffId", + "sealedEnvelopeB64", + "snapshotHashHex" ] }, - "OrganizationOperationCancellationReceipt": { + "PlatformAccessUploadAppStateKeysSuccess": { "type": "object", - "additionalProperties": false, "properties": { - "operation": { - "$ref": "#/components/schemas/OrganizationOperation" - }, - "operationId": { - "type": "string", - "format": "uuid" + "success": { + "type": "boolean", + "enum": [ + true + ] }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" + "data": { + "$ref": "#/components/schemas/PlatformAccessSuccessResponse" } }, "required": [ - "operation", - "operationId", - "idempotency" + "success", + "data" ] }, - "ProjectOperationCancellationReceipt": { + "PlatformAccessUploadAppStateKeysRequest": { "type": "object", - "additionalProperties": false, "properties": { - "operation": { - "$ref": "#/components/schemas/ProjectOperation" + "keys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "keyId": { + "type": "string", + "description": "Base64-encoded AppStateSyncKey id (URL-safe-no-pad)." + }, + "keyB64": { + "type": "string", + "description": "Base64-encoded 32-byte symmetric key bytes." + }, + "fingerprintB64": { + "type": "string", + "description": "Base64-encoded WA fingerprint blob (optional)." + }, + "timestamp": { + "type": "number", + "description": "Unix-seconds timestamp WA stamped on the key." + } + }, + "required": [ + "keyId", + "keyB64", + "timestamp" + ] + } }, - "operationId": { + "upgradeTo": { "type": "string", - "format": "uuid" - }, - "idempotency": { - "$ref": "#/components/schemas/IdempotencyReceipt" + "enum": [ + "passthrough_plus" + ], + "description": "When set, atomically flips sessions.config.bartenderMode → passthrough_plus after the keys persist. Eliminates the window where mode is set but no keys are staged." } }, "required": [ - "operation", - "operationId", - "idempotency" + "keys" ] }, - "OperationConflictEnvelope": { + "PlatformAccessHandoffAckSuccess": { "type": "object", - "additionalProperties": false, "properties": { - "error": { - "type": "string", + "success": { + "type": "boolean", "enum": [ - "operation_conflict" + true ] }, - "code": { - "type": "string", - "enum": [ - "operation_conflict" - ] + "data": { + "$ref": "#/components/schemas/PlatformAccessSuccessResponse" + } + }, + "required": [ + "success", + "data" + ] + }, + "PlatformAccessHandoffAckRequest": { + "type": "object", + "properties": { + "handoffId": { + "type": "string" }, - "operation": { - "oneOf": [ - { - "$ref": "#/components/schemas/OrganizationOperation" - }, - { - "$ref": "#/components/schemas/ProjectOperation" - } - ] + "snapshotHashHex": { + "type": "string", + "description": "SHA-256 of the snapshot the customer received via mode.handoff_export. API verifies this matches what the runner exported before flipping bartenderMode." } }, "required": [ - "error", - "code", - "operation" + "handoffId", + "snapshotHashHex" ] }, - "IdempotencyConflictEnvelope": { + "PlatformAccessPublicError": { "type": "object", - "additionalProperties": false, "properties": { "error": { - "type": "string", - "enum": [ - "idempotency_conflict" + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "invalid_request_error", + "authentication_error", + "permission_error", + "conflict_error", + "rate_limit_error", + "api_error", + "upstream_error" + ] + }, + "code": { + "type": "string", + "enum": [ + "invalid_parameter", + "invalid_credential", + "entitlement_required", + "method_not_allowed", + "permission_denied", + "missing_scope", + "resource_not_found", + "media_fetch_failed", + "resource_gone", + "state_conflict", + "rate_limit_exceeded", + "internal_error", + "operation_not_supported", + "upstream_failure", + "service_unavailable", + "credential_verification_unavailable", + "session_not_ready", + "command_dispatch_failed", + "result_unknown" + ] + }, + "message": { + "type": "string" + }, + "param": { + "type": "string", + "nullable": true + } + }, + "required": [ + "type", + "code", + "message", + "param" ] }, - "code": { + "data": { "type": "string", + "nullable": true, "enum": [ - "idempotency_conflict" - ] + null + ], + "description": "Always null for a failed request." + }, + "docs": { + "type": "string", + "format": "uri" } }, "required": [ "error", - "code" - ] - }, - "OperationCancellationConflictEnvelope": { - "oneOf": [ - { - "$ref": "#/components/schemas/OperationConflictEnvelope" - }, - { - "$ref": "#/components/schemas/IdempotencyConflictEnvelope" - } + "data", + "docs" ] } }, @@ -28552,6 +29149,30 @@ } } }, + "BillingCreditRequired": { + "description": "The paid number could not reserve sufficient available credit. Add credit to your team before starting it.", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + }, "PropagationPending": { "description": "The change was stored, but a connected number has not confirmed it. Retry the request; the stored setting is already correct and is retried automatically.", "headers": { @@ -28577,7 +29198,7 @@ } }, "NotFound": { - "description": "The requested resource was not found inside the authenticated organization or project scope.", + "description": "The requested resource was not found inside the authenticated team or project scope.", "headers": { "X-Request-Id": { "schema": { diff --git a/contracts/source.json b/contracts/source.json index 958a46c9..96747e71 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,16 +1,16 @@ { "repository": "polymorfa/polymorfa", - "commit": "7540c0cef0d6a9552476a1240c37c072c4781033", + "commit": "1600403db9dcf7996dbbb882375b1165859829d3", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", "snapshotPath": "contracts/openapi.messaging.json", - "sha256": "92fb933891a7a954f5bf4341f0ac4a7ddec90f9d5d3b00468a62b3e33ec92bee" + "sha256": "53fca0fb8005af3a67a5cf1fccccfbe930c9dc947ae875c0794d8c32ffdf76aa" }, "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "ffa2277ce0152e1010484dfda7d382d944fd9fdf9cecd982f558d64ccf65e2e1" + "sha256": "c3c0d1107e8403c1ddc28649ca397eef35d82cd40be1674b3dc2ba5525a3590e" } } } diff --git a/packages/browser/README.md b/packages/browser/README.md index 490d0afd..e50eea08 100644 --- a/packages/browser/README.md +++ b/packages/browser/README.md @@ -27,7 +27,9 @@ Product controllers use immutable snapshots and `subscribe()`/`getSnapshot()` so `BrowserMessagingClient` binds one session and exposes only the exact client-token allowlist: message actions, presence reads/subscriptions, contact -reads, and widget start/status/pairing/handoff actions. Conversation history, +reads, and permitted widget pairing actions. Session start and status require a server +Platform credential; use QuickLink for onboarding. The browser widget start/status +helpers are removed in this breaking contract revision. Conversation history, template management and media upload remain application-owned server adapters. `createBrowserCalls` supplies client-token call lifecycle and controls. diff --git a/packages/browser/src/messaging/client.ts b/packages/browser/src/messaging/client.ts index 37dd7ea9..a597d027 100644 --- a/packages/browser/src/messaging/client.ts +++ b/packages/browser/src/messaging/client.ts @@ -12,7 +12,6 @@ import type { BrowserContactCheckResult, BrowserMessageResult, BrowserMessageReceipt, - BrowserOperationAccepted, BrowserPairingCode, BrowserPairingCodeRequest, BrowserPresenceData, @@ -22,7 +21,6 @@ import type { BrowserReactionRequest, BrowserSeenRequest, BrowserSendMessageRequest, - BrowserSessionStatus, BrowserStarRequest, BrowserSuccessEnvelope, BrowserTypingRequest, @@ -239,28 +237,6 @@ export class BrowserWidgetResource { private readonly session: string, ) {} - start( - options: BrowserActionOptions = {}, - ): Promise> { - return this.transport.request({ - method: "POST", - path: `/messaging/sessions/${encodeURIComponent(this.session)}/start`, - ...options, - }); - } - - status( - options: BrowserActionOptions = {}, - ): Promise< - BrowserActionResponse> - > { - return this.transport.request({ - method: "GET", - path: `/messaging/sessions/${encodeURIComponent(this.session)}`, - ...options, - }); - } - qr( options: BrowserActionOptions = {}, ): Promise>> { diff --git a/packages/browser/src/messaging/types.ts b/packages/browser/src/messaging/types.ts index 40a29e8d..4d8fdc20 100644 --- a/packages/browser/src/messaging/types.ts +++ b/packages/browser/src/messaging/types.ts @@ -227,27 +227,6 @@ export interface BrowserQrCode { readonly event?: string; } -export interface BrowserOperationAccepted { - readonly success: true; - readonly message: string; - readonly operationId: string; -} - -export interface BrowserSessionStatus { - readonly sessionId?: string; - readonly name: string; - readonly tenantId: string; - readonly connection: "linked_device" | "cloud_api"; - readonly testMode: boolean; - readonly status: string; - readonly statusReason?: string; - readonly runnerId?: string; - readonly proxy?: string; - readonly config: Readonly>; - readonly createdAt: string; - readonly updatedAt: string; -} - export type BrowserRequestOptions = Pick< BrowserRequest, "signal" | "timeoutMs" | "maxNetworkRetries" | "idempotencyKey" | "headers" diff --git a/packages/browser/test/client-actions.test.ts b/packages/browser/test/client-actions.test.ts index 06e5a000..d2489b34 100644 --- a/packages/browser/test/client-actions.test.ts +++ b/packages/browser/test/client-actions.test.ts @@ -144,8 +144,8 @@ describe("BrowserMessagingClient", () => { }, }); - await client.widget.start(); - await client.widget.status(); + expect(client.widget).not.toHaveProperty("start"); + expect(client.widget).not.toHaveProperty("status"); await client.widget.qr(); await client.widget.requestPairingCode({ phone: "+15550001" }); @@ -154,12 +154,10 @@ describe("BrowserMessagingClient", () => { ({ url, init }) => `${init?.method} ${new URL(url).pathname}`, ), ).toEqual([ - "POST /messaging/sessions/widget-1/start", - "GET /messaging/sessions/widget-1", "GET /messaging/widget-1/pair/qr", "POST /messaging/widget-1/pair/code", ]); - expect(requests[3]?.init?.body).toBe('{"phone":"+15550001"}'); + expect(requests[1]?.init?.body).toBe('{"phone":"+15550001"}'); }); it("adapts text composer sends while keeping uploads application-owned", async () => { diff --git a/packages/typescript/README.md b/packages/typescript/README.md index d211a155..868546b9 100644 --- a/packages/typescript/README.md +++ b/packages/typescript/README.md @@ -165,7 +165,9 @@ session rules can delegate to the browser token. ## Session connection lifecycle -Start a Linked Device session, then retrieve its connection status with +Session administration uses Platform routes and requires a server credential. +The existing `MessagingClient.sessions` method names remain available. +Start an existing Linked Device session, then retrieve its connection status with `sessions.retrieve`. The standard pairing flow is QuickLink. Direct JSON QR and phone pairing-code routes require `sessions:manage` plus an explicit organization entitlement; without it, the API returns `403` and the application must create @@ -176,7 +178,7 @@ const started = await messaging.sessions.start("support", { idempotencyKey: "start-support", }); -console.log(started.data.operationId); +console.log(started.data.data.started); console.log((await messaging.sessions.retrieve("support")).data); ``` @@ -1096,8 +1098,10 @@ used by the server client. ## Billing and usage `Client.billing` exposes the complete organization-key billing family. -Reads require `sessions:read`; updating reminder settings requires -`sessions:manage`. +Reads require `sessions:read`. Credit quantities, including fields ending in +`Cents`, support up to six decimal places. They are not cash minor units. +Team warnings follow the fixed one-day and two-hour insufficiency forecast; +notification preferences are managed in the Console. ```ts const [balance, usage, transactions, pricing] = await Promise.all([ @@ -1107,14 +1111,6 @@ const [balance, usage, transactions, pricing] = await Promise.all([ platform.billing.listPricing(), ]); -await platform.billing.updateReminderSettings( - { - lowBalanceThresholdCents: 2_500, - reminderChannels: ["email", "inApp"], - }, - { idempotencyKey: "billing-reminders-august" }, -); - console.log({ balance: balance.data.data, usage: usage.data.data, @@ -1124,6 +1120,32 @@ console.log({ }); ``` +### Change a number tier + +Create a quote, show its credit charge and effective time, then confirm its ID +only after the customer accepts. Upgrades buy a fresh 24-hour window and replace +the remaining paid time. Downgrades apply when the paid window ends. + +```ts +const reviewed = await platform.sessions.quoteTierChange(sessionId, { + tierOverride: "pro", +}); +const quote = reviewed.data.data; +console.log(quote.quote.amountCents, quote.quote.effectiveAtMs); + +// After the customer confirms this exact quote: +await platform.sessions.setTierOverride(sessionId, { quoteId: quote.id }); +const result = await platform.sessions.retrieveTierChange(sessionId, quote.id); +console.log(result.data.data.status); +``` + +A queued result has not granted the tier. Poll until it is applied or rejected. +A quote expires after ten minutes and can become invalid if the number or price +changes. Show a new quote for confirmation after a conflict; never silently +purchase a replacement. Set `tierOverride: null` when quoting to restore project +inheritance. The old `setTierOverride({tierOverride})` request and +`billing.updateReminderSettings` method are removed. + ## Organization access and security The organization view exposes key metadata, members, audit logs, session bans, @@ -1238,7 +1260,11 @@ const start = await platform.sessions.start( ``` The returned `SessionStartResult` confirms that the start request was accepted; -it does not claim that the session has connected. `sessions.stopMany` and +it does not claim that the session has connected. A paid start first reserves +credit. An HTTP 402 response throws `PolymorfaPaymentRequiredError`, preserving +the API's error code, message, and request ID. It is not automatically retried; +resolve the funding or entitlement problem before submitting another start. +The charge is committed on successful connection. `sessions.stopMany` and `deleteMany` cover the two bounded batch operations. All three require `sessions:manage`. Batch methods accept `sessionIds` plus an optional `projectId`: @@ -1272,3 +1298,67 @@ provided. The pinned handlers do not persist that header. A repeated stop can enqueue another stop command; a repeated delete reports only rows still found. QuickLink settings updates are state upserts and can safely converge on the same supplied values. + +## Session creation and configuration + +Create new sessions with `MessagingClient.quickLinks.create`. Direct +`sessions.create` and Platform `sessions.createTesting` have been removed in this +breaking contract update. Reconnect and delete still operate on existing sessions. + +```ts +const link = await messaging.quickLinks.create({ + projectId, + configuration: { + connectionPreference: "linked", + historySync: { consent: "ask" }, + }, +}); +``` + +Page text, appearance, legal links, and callbacks belong in saved +`Client.quickLinkSettings`, not individual invitations. Links report nullable +`expiresAt`; new invitations remain usable until completion or cancellation. +Free-tier real-account pairing is available only in the authenticated Console. + +Use `Client.sessionConfiguration` for team defaults and +`client.project(projectId).sessionConfiguration` for project defaults. Session +updates take `{revision, configuration: {set, reset}}`; resets remove explicit +overrides so later defaults continue to apply. Reads expose effective values, +sources, consent restrictions, and pending runtime application. + +For simulation, create a QuickLink with `configuration.testing`, including initial +`configuration` and the explicit `editable` subset delegated to the recipient. +Test access is checked independently; simulation cannot contact real accounts. + +Test history content is uploaded separately from session configuration: + +```ts +const fixture = await messaging.testing.createHistoryFixture(projectId, { + messages: [ + { + id: "example-1", + senderPhone: testPhone, + text: "Demo", + timestamp: 1, + fromMe: false, + }, + ], +}); +const invitation = await messaging.quickLinks.create({ + projectId, + configuration: { + testing: { configuration: { historyFixtureId: fixture.data.fixtureId } }, + }, +}); +``` + +Fixture senders must be existing simulated numbers in that project. Test-number +entitlements and history consent still apply; uploading a fixture does not enable +hosted message storage. + +Trusted servers continue an issued Meta Cloud API invitation with +`messaging.cloudOnboarding.advance({ quicklinkId, projectId, result })`. +`result` contains the Embedded Signup authorization code, selected WABA and phone +IDs, and Coexistence/history choices. This method does not create a session or +accept Meta app secrets. Its progress response is not proof that messaging is +ready; inspect the QuickLink status. diff --git a/packages/typescript/src/client.ts b/packages/typescript/src/client.ts index e6154f73..2fa375d1 100644 --- a/packages/typescript/src/client.ts +++ b/packages/typescript/src/client.ts @@ -1,3 +1,4 @@ +import { SessionConfigurationResource } from "./platform/session-configuration.js"; import { assertServerRuntime, validateClientCredential, @@ -49,6 +50,7 @@ export interface ClientBase { readonly events: EventsResourceFor; readonly webhooks: WebhooksResourceFor; readonly webhookDeliveries: WebhookDeliveriesResourceFor; + readonly sessionConfiguration: SessionConfigurationResource; readonly quickLinkSettings: QuickLinkSettingsResource; readonly raw: RawResourceFor; project(projectId: string): Client<"project">; @@ -86,6 +88,7 @@ class ClientImplementation implements ClientBase { readonly events: EventsResource; readonly webhooks: WebhooksResource; readonly webhookDeliveries: WebhookDeliveriesResource; + readonly sessionConfiguration: SessionConfigurationResource; readonly quickLinkSettings: QuickLinkSettingsResource; readonly raw: RawClient | ProjectScopedRawClient; readonly #transport: HttpTransport; @@ -129,6 +132,10 @@ class ClientImplementation implements ClientBase { this.#transport, prefix, ); + this.sessionConfiguration = new SessionConfigurationResource( + this.#transport, + projectId, + ); this.quickLinkSettings = new QuickLinkSettingsResource( this.#transport, projectId, diff --git a/packages/typescript/src/errors.ts b/packages/typescript/src/errors.ts index 3ce07e79..9ad044be 100644 --- a/packages/typescript/src/errors.ts +++ b/packages/typescript/src/errors.ts @@ -42,6 +42,7 @@ export class PolymorfaConfigurationError extends PolymorfaError { export class PolymorfaValidationError extends PolymorfaError {} export class PolymorfaAuthenticationError extends PolymorfaError {} export class PolymorfaAuthorizationError extends PolymorfaError {} +export class PolymorfaPaymentRequiredError extends PolymorfaError {} export class PolymorfaNotFoundError extends PolymorfaError {} export class PolymorfaConflictError extends PolymorfaError {} export class PolymorfaRateLimitError extends PolymorfaError {} diff --git a/packages/typescript/src/index.ts b/packages/typescript/src/index.ts index 99a55cfd..7f2da88b 100644 --- a/packages/typescript/src/index.ts +++ b/packages/typescript/src/index.ts @@ -29,6 +29,7 @@ export { PolymorfaCancelledError, PolymorfaConfigurationError, PolymorfaConflictError, + PolymorfaPaymentRequiredError, PolymorfaConnectionError, PolymorfaError, PolymorfaNotFoundError, @@ -233,8 +234,6 @@ export type { CreateCampaignResponse, CreateChannelRequest, CreateChannelResponse, - CreateSessionRequest, - CreateSessionResponse, CreateGroupRequest, CreateGroupResponse, CreateProjectTemplateRequest, @@ -485,8 +484,6 @@ export type { AuditLog, BillingBalance, BillingCurrency, - BillingReminderChannel, - BillingReminderSettings, BillingTransaction, BillingUsage, CreateCustomerPairingLinkRequest, @@ -509,7 +506,6 @@ export type { CustomersEnablement, CustomersStatus, CreateProjectRequest, - CreateTestingSessionRequest, DataEnvelope, ListAuditLogsParams, ListPlatformSessionsParams, @@ -543,9 +539,10 @@ export type { SessionStopResult, SessionTier, SessionTierOverrideRequest, + NumberTierChange, + NumberTierQuoteRequest, TierPricing, TransferCustomerNumberRequest, - UpdateBillingReminderSettingsRequest, UpdateCustomerRequest, } from "./platform/types.js"; export { RawClient, type ProjectScopedRawClient } from "./raw.js"; @@ -634,3 +631,16 @@ export { type WebhookEventOf, type WebhookPayloadMap, } from "./webhooks/index.js"; + +export type * from "./messaging/session-configuration.js"; + +export { SessionConfigurationResource } from "./platform/session-configuration.js"; + +export type * from "./messaging/testing-configuration.js"; + +export { + CloudOnboardingResource, + TestingResource, + type EmbeddedSignupRequest, + type EmbeddedSignupResponse, +} from "./messaging/onboarding.js"; diff --git a/packages/typescript/src/messaging/client-tokens.ts b/packages/typescript/src/messaging/client-tokens.ts index 48baa9ef..8b619a41 100644 --- a/packages/typescript/src/messaging/client-tokens.ts +++ b/packages/typescript/src/messaging/client-tokens.ts @@ -1,3 +1,5 @@ +import type { MessagingCredential } from "../credentials.js"; +import { PolymorfaConfigurationError } from "../errors.js"; import { HttpTransport } from "../transport/http.js"; import type { ApiResponse, RequestOptions } from "../transport/types.js"; import type { @@ -9,15 +11,19 @@ import type { } from "./types.js"; export class ClientTokensResource { - constructor(private readonly transport: HttpTransport) {} + constructor( + private readonly transport: HttpTransport, + private readonly credentialType: MessagingCredential["type"], + ) {} mint( body: MintClientTokenRequest, options: RequestOptions = {}, ): Promise> { + this.assertServerCredential(); return this.transport.request({ method: "POST", - path: "/messaging/client-tokens", + path: "/platform/client-tokens", body, ...options, }); @@ -27,6 +33,7 @@ export class ClientTokensResource { session: string, options: RequestOptions = {}, ): Promise> { + this.assertServerCredential(); return this.transport.request({ method: "GET", path: rulesPath(session), @@ -39,6 +46,7 @@ export class ClientTokensResource { body: SetClientRulesRequest, options: RequestOptions = {}, ): Promise> { + this.assertServerCredential(); return this.transport.request({ method: "PUT", path: rulesPath(session), @@ -51,14 +59,23 @@ export class ClientTokensResource { session: string, options: RequestOptions = {}, ): Promise> { + this.assertServerCredential(); return this.transport.request({ method: "DELETE", path: rulesPath(session), ...options, }); } + private assertServerCredential(): void { + if (this.credentialType === "clientToken") { + throw new PolymorfaConfigurationError( + "Platform session administration requires a server API key.", + "credential", + ); + } + } } function rulesPath(session: string): string { - return `/messaging/sessions/${encodeURIComponent(session)}/client-rules`; + return `/platform/sessions/${encodeURIComponent(session)}/client-rules`; } diff --git a/packages/typescript/src/messaging/client.ts b/packages/typescript/src/messaging/client.ts index 2cf19f7d..14f2d8fd 100644 --- a/packages/typescript/src/messaging/client.ts +++ b/packages/typescript/src/messaging/client.ts @@ -1,3 +1,4 @@ +import { CloudOnboardingResource, TestingResource } from "./onboarding.js"; import { assertServerRuntime, validateMessagingCredential, @@ -48,6 +49,8 @@ export class MessagingClient { readonly privacy: PrivacyResource; readonly presence: PresenceResource; readonly quickReplies: QuickRepliesResource; + readonly cloudOnboarding: CloudOnboardingResource; + readonly testing: TestingResource; readonly quickLinks: QuickLinksResource; readonly templates: TemplatesResource; readonly users: UsersResource; @@ -73,19 +76,24 @@ export class MessagingClient { this.campaigns = new MessagingCampaignsResource(transport); this.chats = new ChatsResource(transport); this.channels = new ChannelsResource(transport); - this.clientTokens = new ClientTokensResource(transport); + this.clientTokens = new ClientTokensResource(transport, credential.type); this.contacts = new ContactsResource(transport); this.groups = new GroupsResource(transport); this.labels = new LabelsResource(transport); this.identities = new IdentitiesResource(transport); this.media = new MessagingMediaResource(transport); this.observationPolicies = new ObservationPoliciesResource(transport); - this.sessions = new SessionsResource(transport); + this.sessions = new SessionsResource(transport, credential.type); this.messages = new MessagesResource(transport); this.profile = new ProfileResource(transport); this.privacy = new PrivacyResource(transport); this.presence = new PresenceResource(transport); this.quickReplies = new QuickRepliesResource(transport); + this.cloudOnboarding = new CloudOnboardingResource( + transport, + credential.type, + ); + this.testing = new TestingResource(transport, credential.type); this.quickLinks = new QuickLinksResource(transport, credential.type); this.templates = new TemplatesResource(transport); this.users = new UsersResource(transport); diff --git a/packages/typescript/src/messaging/observation-policies.ts b/packages/typescript/src/messaging/observation-policies.ts index 9892eab1..4871c3ec 100644 --- a/packages/typescript/src/messaging/observation-policies.ts +++ b/packages/typescript/src/messaging/observation-policies.ts @@ -1,11 +1,7 @@ import { HttpTransport } from "../transport/http.js"; import type { ApiResponse, RequestOptions } from "../transport/types.js"; import type { - GetProjectObservationPolicyResponse, - GetSessionObservationPolicyResponse, - UpdateProjectObservationPolicyRequest, UpdateProjectObservationPolicyResponse, - UpdateSessionObservationPolicyRequest, UpdateSessionObservationPolicyResponse, } from "./types.js"; @@ -23,19 +19,6 @@ export class ObservationPoliciesResource { }); } - updateForProject( - projectId: string, - body: UpdateProjectObservationPolicyRequest, - options: RequestOptions = {}, - ): Promise> { - return this.transport.request({ - method: "PUT", - path: projectPolicyPath(projectId), - body, - ...options, - }); - } - retrieveForSession( session: string, options: RequestOptions = {}, @@ -46,19 +29,6 @@ export class ObservationPoliciesResource { ...options, }); } - - updateForSession( - session: string, - body: UpdateSessionObservationPolicyRequest, - options: RequestOptions = {}, - ): Promise> { - return this.transport.request({ - method: "PUT", - path: sessionPolicyPath(session), - body, - ...options, - }); - } } function projectPolicyPath(projectId: string): string { diff --git a/packages/typescript/src/messaging/onboarding.ts b/packages/typescript/src/messaging/onboarding.ts new file mode 100644 index 00000000..ddae7458 --- /dev/null +++ b/packages/typescript/src/messaging/onboarding.ts @@ -0,0 +1,68 @@ +import { PolymorfaConfigurationError } from "../errors.js"; +import type { MessagingCredential } from "../credentials.js"; +import { HttpTransport } from "../transport/http.js"; +import type { ApiResponse, RequestOptions } from "../transport/types.js"; +import type { TestingHistoryMessage } from "./testing-configuration.js"; + +export interface EmbeddedSignupRequest { + quicklinkId: string; + projectId?: string; + result?: { + code: string; + wabaId: string; + phoneNumberId: string; + coexistence?: boolean; + historySync?: boolean; + }; +} +export interface EmbeddedSignupResponse { + success: true; + data: { stage: string }; +} + +/** Trusted-server continuation of an issued QuickLink; never direct session creation. */ +export class CloudOnboardingResource { + constructor( + private readonly transport: HttpTransport, + private readonly credentialType: MessagingCredential["type"], + ) {} + advance( + input: EmbeddedSignupRequest, + options: RequestOptions = {}, + ): Promise> { + assertServerCredential(this.credentialType); + return this.transport.request({ + method: "POST", + path: "/messaging/cloud-api/embedded-signup", + body: input, + ...options, + }); + } +} + +export class TestingResource { + constructor( + private readonly transport: HttpTransport, + private readonly credentialType: MessagingCredential["type"], + ) {} + createHistoryFixture( + projectId: string, + input: { messages: readonly TestingHistoryMessage[] }, + options: RequestOptions = {}, + ): Promise> { + assertServerCredential(this.credentialType); + return this.transport.request({ + method: "POST", + path: `/messaging/testing/${encodeURIComponent(projectId)}/history-fixtures`, + body: input, + ...options, + }); + } +} +function assertServerCredential(type: MessagingCredential["type"]): void { + if (type === "clientToken") + throw new PolymorfaConfigurationError( + "Onboarding management requires an organization API key or project token.", + "credential", + ); +} diff --git a/packages/typescript/src/messaging/quicklinks.ts b/packages/typescript/src/messaging/quicklinks.ts index 1b709980..f7a2e3ee 100644 --- a/packages/typescript/src/messaging/quicklinks.ts +++ b/packages/typescript/src/messaging/quicklinks.ts @@ -1,31 +1,44 @@ +import type { SessionConfigurationOverrides } from "./session-configuration.js"; import { PolymorfaConfigurationError } from "../errors.js"; import type { QuickLinkHistorySync, QuickLinkMethod, - QuickLinkTheme, } from "../platform/quicklink-settings.js"; import { HttpTransport } from "../transport/http.js"; import type { ApiResponse, RequestOptions } from "../transport/types.js"; import type { MessagingCredential } from "../credentials.js"; +export interface QuickLinkConfiguration extends SessionConfigurationOverrides { + readonly testing?: { + readonly country?: string; + readonly configuration?: import("./testing-configuration.js").TestingConfiguration; + readonly editable?: readonly import("./testing-configuration.js").TestingConfigurationField[]; + }; + readonly connectionPreference?: "cloud" | "linked" | "both"; + readonly connectionEnforcement?: "prefer" | "force"; + readonly methods?: readonly QuickLinkMethod[]; + readonly defaultMethod?: QuickLinkMethod | null; + readonly prefillPhone?: string; + readonly allowPhoneChange?: boolean; + readonly historySync?: { + readonly consent?: QuickLinkHistorySync; + readonly mode?: "metadata_only" | "deliver"; + readonly requestFull?: boolean; + }; +} + export interface CreateQuickLinkRequest { readonly projectId?: string; readonly customerId?: string; - readonly methods?: readonly QuickLinkMethod[]; - readonly businessName?: string; - readonly historySync?: QuickLinkHistorySync; - readonly callbackUrl?: string; - readonly theme?: QuickLinkTheme; - readonly accent?: string; - readonly prefillPhone?: string; - readonly expiresInSeconds?: number; + readonly externalId?: string; + readonly configuration?: QuickLinkConfiguration; } export interface QuickLink { readonly id: string; readonly url: string; readonly session: string; - readonly expiresAt: string; + readonly expiresAt: string | null; } export type QuickLinkStatusValue = @@ -35,7 +48,7 @@ export interface QuickLinkStatus { readonly id: string; readonly status: QuickLinkStatusValue; readonly session: string; - readonly expiresAt: string; + readonly expiresAt: string | null; readonly openedAt: string | null; readonly connectedAt: string | null; readonly phone: string | null; diff --git a/packages/typescript/src/messaging/session-configuration.ts b/packages/typescript/src/messaging/session-configuration.ts new file mode 100644 index 00000000..209d85b3 --- /dev/null +++ b/packages/typescript/src/messaging/session-configuration.ts @@ -0,0 +1,74 @@ +export type HistorySyncPolicy = { + mode: "metadata_only" | "deliver"; + requestFull: boolean; +}; + +/** Customer settings with implemented runtime consumers. */ +export type HmsConfiguration = + | { enabled: false } + | { + enabled: true; + region: string; + policy: + "short" | "standard" | "extended" | "compliance" | "enterprise_archive"; + retentionDays?: number; + policyVersion?: string; + legalHold?: boolean; + }; + +export interface ObservationConfiguration { + presenceMode: "off" | "events" | "cache"; + typingMode: "off" | "events" | "cache"; + labelMode: "off" | "events" | "cache" | "project"; + quickReplyMode: "off" | "events" | "cache"; +} +export interface SessionConfigurationOverrides { + observation?: Partial; + historySync?: Partial; + /** Atomic policy replacement; enabling requires separate live authorization. */ + hms?: HmsConfiguration; +} + +export interface EffectiveSessionConfiguration { + observation: ObservationConfiguration; + historySync: HistorySyncPolicy; + hms: HmsConfiguration; +} + +export type ConfigurationSource = "platform" | "team" | "project" | "session"; +export type ConfigurationValueSource = ConfigurationSource | "consent"; +export type SessionConfigurationReset = + | "historySync" + | "historySync.mode" + | "historySync.requestFull" + | "hms" + | "observation" + | `observation.${keyof ObservationConfiguration}`; +export interface SessionConfigurationPatch { + set?: SessionConfigurationOverrides; + reset?: SessionConfigurationReset[]; +} +export interface ResolvedSessionConfiguration { + effective: EffectiveSessionConfiguration; + overrides: SessionConfigurationOverrides; + sources: { + "historySync.mode": ConfigurationValueSource; + "historySync.requestFull": ConfigurationValueSource; + hms: ConfigurationSource; + "observation.presenceMode": ConfigurationSource; + "observation.typingMode": ConfigurationSource; + "observation.labelMode": ConfigurationSource; + "observation.quickReplyMode": ConfigurationSource; + }; +} + +export interface SessionConfigurationView extends ResolvedSessionConfiguration { + requestedHistory: HistorySyncPolicy; + historyConsent?: "pending" | "accepted" | "declined"; + revisions: { team: number; project: number; session: number }; + application?: { + desiredGeneration: number; + appliedGeneration: number; + status: "pending" | "applied"; + }; +} diff --git a/packages/typescript/src/messaging/sessions.ts b/packages/typescript/src/messaging/sessions.ts index af9d1a53..8cd9fab5 100644 --- a/packages/typescript/src/messaging/sessions.ts +++ b/packages/typescript/src/messaging/sessions.ts @@ -1,12 +1,11 @@ +import type { MessagingCredential } from "../credentials.js"; +import { PolymorfaConfigurationError } from "../errors.js"; import { HttpTransport } from "../transport/http.js"; import type { ApiResponse, RequestOptions } from "../transport/types.js"; import type { - CreateSessionRequest, - CreateSessionResponse, GetSessionAccountResponse, GetSessionResponse, GetQRCodeResponse, - ListSessionsResponse, OperationAccepted, PairCodeRequest, RequestPairCodeResponse, @@ -14,27 +13,27 @@ import type { UpdateSessionResponse, } from "./types.js"; +import type { + DataEnvelope, + PlatformSession, + SessionStartResult, + SessionStopResult, + SessionRemoveResult, +} from "../platform/types.js"; + export class SessionsResource { - constructor(private readonly transport: HttpTransport) {} + constructor( + private readonly transport: HttpTransport, + private readonly credentialType: MessagingCredential["type"], + ) {} list( options: RequestOptions = {}, - ): Promise> { + ): Promise>> { + this.assertServerCredential(); return this.transport.request({ method: "GET", - path: "/messaging/sessions", - ...options, - }); - } - - create( - body: CreateSessionRequest, - options: RequestOptions = {}, - ): Promise> { - return this.transport.request({ - method: "POST", - path: "/messaging/sessions", - body, + path: "/platform/sessions", ...options, }); } @@ -43,6 +42,7 @@ export class SessionsResource { session: string, options: RequestOptions = {}, ): Promise> { + this.assertServerCredential(); return this.transport.request({ method: "GET", path: sessionPath(session), @@ -55,6 +55,7 @@ export class SessionsResource { body: UpdateSessionRequest, options: RequestOptions = {}, ): Promise> { + this.assertServerCredential(); return this.transport.request({ method: "PUT", path: sessionPath(session), @@ -66,7 +67,8 @@ export class SessionsResource { delete( session: string, options: RequestOptions = {}, - ): Promise> { + ): Promise>> { + this.assertServerCredential(); return this.transport.request({ method: "DELETE", path: sessionPath(session), @@ -77,15 +79,25 @@ export class SessionsResource { start( session: string, options: RequestOptions = {}, - ): Promise> { - return this.action(session, "start", options); + ): Promise>> { + this.assertServerCredential(); + return this.transport.request({ + method: "POST", + path: `${sessionPath(session)}/start`, + ...options, + }); } stop( session: string, options: RequestOptions = {}, - ): Promise> { - return this.action(session, "stop", options); + ): Promise>> { + this.assertServerCredential(); + return this.transport.request({ + method: "POST", + path: `${sessionPath(session)}/stop`, + ...options, + }); } restart( @@ -106,6 +118,7 @@ export class SessionsResource { session: string, options: RequestOptions = {}, ): Promise> { + this.assertServerCredential(); return this.transport.request({ method: "GET", path: `${sessionPath(session)}/me`, @@ -142,17 +155,26 @@ export class SessionsResource { private action( session: string, - action: "start" | "stop" | "restart" | "logout", + action: "restart" | "logout", options: RequestOptions, ): Promise> { + this.assertServerCredential(); return this.transport.request({ method: "POST", path: `${sessionPath(session)}/${action}`, ...options, }); } + private assertServerCredential(): void { + if (this.credentialType === "clientToken") { + throw new PolymorfaConfigurationError( + "Platform session administration requires a server API key.", + "credential", + ); + } + } } function sessionPath(session: string): string { - return `/messaging/sessions/${encodeURIComponent(session)}`; + return `/platform/sessions/${encodeURIComponent(session)}`; } diff --git a/packages/typescript/src/messaging/testing-configuration.ts b/packages/typescript/src/messaging/testing-configuration.ts new file mode 100644 index 00000000..fdedbe81 --- /dev/null +++ b/packages/typescript/src/messaging/testing-configuration.ts @@ -0,0 +1,23 @@ +/** Initial identity of an isolated simulated account. No real credentials or message content. */ +export interface TestingConfiguration { + profile?: { name?: string; status?: string }; + accountType?: "personal" | "business"; + replyBehavior?: "off" | "echo"; + failureScenario?: "none" | "reject-send"; + historyFixtureId?: string; +} +export type TestingConfigurationField = + | "profile.name" + | "profile.status" + | "accountType" + | "replyBehavior" + | "failureScenario" + | "historyFixtureId"; + +export interface TestingHistoryMessage { + id: string; + senderPhone: string; + text: string; + timestamp: number; + fromMe: boolean; +} diff --git a/packages/typescript/src/messaging/types.ts b/packages/typescript/src/messaging/types.ts index 91281c5d..65e03937 100644 --- a/packages/typescript/src/messaging/types.ts +++ b/packages/typescript/src/messaging/types.ts @@ -17,36 +17,22 @@ export interface HistorySyncPolicy { } export interface Session { - readonly sessionId?: string; + readonly sessionId: string; readonly name: string; + readonly externalId?: string; readonly tenantId: string; - readonly connection: MessagingConnection; + readonly type: MessagingConnection; readonly testMode: boolean; readonly status: string; readonly statusReason?: string; - readonly runnerId?: string; - readonly proxy?: string; - readonly config: Readonly>; + readonly configuration?: import("./session-configuration.js").SessionConfigurationView; readonly createdAt: string; readonly updatedAt: string; } -export interface CreateSessionRequest { - readonly projectId: string; - readonly sessionId?: string; - readonly name?: string; - readonly start?: boolean; - readonly connection?: MessagingConnection; - readonly testMode?: boolean; - readonly bartenderMode?: BartenderMode; - readonly cloudApi?: CloudApiCredentials; - readonly config?: Readonly>; - readonly historySync?: HistorySyncPolicy; -} - export interface UpdateSessionRequest { - readonly config?: Readonly>; - readonly historySync?: HistorySyncPolicy; + readonly configuration: import("./session-configuration.js").SessionConfigurationPatch; + readonly revision: number; } export interface SessionOperation extends Session { @@ -1370,7 +1356,7 @@ export type ClientAction = | "voip_answer" | "voip_signal"; -/** Rules as returned by `GET /messaging/sessions/{session}/client-rules`. */ +/** Rules as returned by `GET /platform/sessions/{session}/client-rules`. */ export interface ClientRules { readonly recipientMode: ClientRecipientMode | ""; /** Comma-separated {@link ClientAction} list. */ @@ -1460,7 +1446,6 @@ export interface VoipAgentTokenValue { export type VoipAgentTokenResponse = SuccessEnvelope; export type ListSessionsResponse = SuccessEnvelope; -export type CreateSessionResponse = SuccessEnvelope; export type GetSessionResponse = SuccessEnvelope; export type UpdateSessionResponse = SuccessEnvelope; export type GetSessionAccountResponse = SuccessEnvelope; diff --git a/packages/typescript/src/platform/billing.ts b/packages/typescript/src/platform/billing.ts index 30fee2cd..e1f9ef81 100644 --- a/packages/typescript/src/platform/billing.ts +++ b/packages/typescript/src/platform/billing.ts @@ -2,12 +2,10 @@ import { HttpTransport } from "../transport/http.js"; import type { ApiResponse, RequestOptions } from "../transport/types.js"; import type { BillingBalance, - BillingReminderSettings, BillingTransaction, BillingUsage, DataEnvelope, TierPricing, - UpdateBillingReminderSettingsRequest, } from "./types.js"; export class BillingResource { @@ -37,18 +35,6 @@ export class BillingResource { return this.get("/pricing", options); } - updateReminderSettings( - body: UpdateBillingReminderSettingsRequest, - options: RequestOptions = {}, - ): Promise>> { - return this.transport.request({ - method: "PATCH", - path: "/platform/billing/reminders", - body, - ...options, - }); - } - private get( suffix: "" | "/usage" | "/transactions" | "/pricing", options: RequestOptions, diff --git a/packages/typescript/src/platform/session-configuration.ts b/packages/typescript/src/platform/session-configuration.ts new file mode 100644 index 00000000..b1ad527e --- /dev/null +++ b/packages/typescript/src/platform/session-configuration.ts @@ -0,0 +1,45 @@ +import { HttpTransport } from "../transport/http.js"; +import type { ApiResponse, RequestOptions } from "../transport/types.js"; +import type { + SessionConfigurationPatch, + SessionConfigurationView, +} from "../messaging/session-configuration.js"; +import { type DataEnvelope, unwrapResponse } from "./response.js"; + +/** Saved team or project defaults. Session overrides use sessions.update. */ +export class SessionConfigurationResource { + constructor( + private readonly transport: HttpTransport, + private readonly projectId: string | null, + ) {} + retrieve( + options: RequestOptions = {}, + ): Promise> { + return this.transport + .request>({ + method: "GET", + path: "/platform/session-configuration", + ...(this.projectId === null + ? {} + : { query: { projectId: this.projectId } }), + ...options, + }) + .then(unwrapResponse); + } + update( + input: { configuration: SessionConfigurationPatch; revision: number }, + options: RequestOptions = {}, + ): Promise> { + return this.transport + .request>({ + method: "PUT", + path: "/platform/session-configuration", + body: + this.projectId === null + ? input + : { ...input, projectId: this.projectId }, + ...options, + }) + .then(unwrapResponse); + } +} diff --git a/packages/typescript/src/platform/sessions.ts b/packages/typescript/src/platform/sessions.ts index 91d15e19..80269e84 100644 --- a/packages/typescript/src/platform/sessions.ts +++ b/packages/typescript/src/platform/sessions.ts @@ -1,10 +1,16 @@ import { HttpTransport } from "../transport/http.js"; +import { PolymorfaValidationError } from "../errors.js"; +import type { + GetSessionResponse, + UpdateSessionRequest, + UpdateSessionResponse, +} from "../messaging/types.js"; import type { ApiResponse, RequestOptions } from "../transport/types.js"; import type { - CreateTestingSessionRequest, DataEnvelope, ListPlatformSessionsParams, - ManagedSession, + NumberTierChange, + NumberTierQuoteRequest, PlatformSession, SessionBatchRemoveResult, SessionBatchRequest, @@ -33,6 +39,30 @@ export class PlatformSessionsResource { }); } + retrieve( + sessionId: string, + options: RequestOptions = {}, + ): Promise> { + return this.transport.request({ + method: "GET", + path: sessionPath(sessionId), + ...options, + }); + } + + update( + sessionId: string, + body: UpdateSessionRequest, + options: RequestOptions = {}, + ): Promise> { + return this.transport.request({ + method: "PUT", + path: sessionPath(sessionId), + body, + ...options, + }); + } + start( sessionId: string, body: SessionProjectContext = {}, @@ -96,26 +126,46 @@ export class PlatformSessionsResource { }); } - setTierOverride( + /** Review the returned charge and effective time before confirming this quote. */ + quoteTierChange( sessionId: string, - body: SessionTierOverrideRequest, + body: NumberTierQuoteRequest, options: RequestOptions = {}, - ): Promise>> { + ): Promise>> { return this.transport.request({ - method: "PATCH", - path: sessionPath(sessionId), + method: "POST", + path: `${sessionPath(sessionId)}/tier-quotes`, body, ...options, }); } - createTesting( - body: CreateTestingSessionRequest, + retrieveTierChange( + sessionId: string, + quoteId: string, options: RequestOptions = {}, - ): Promise>> { + ): Promise>> { return this.transport.request({ - method: "POST", - path: "/platform/sessions/testing", + method: "GET", + path: `${sessionPath(sessionId)}/tier-quotes/${encodeURIComponent(quoteId)}`, + ...options, + }); + } + + /** Confirms a reviewed quote. Poll retrieveTierChange until applied or rejected. */ + setTierOverride( + sessionId: string, + body: SessionTierOverrideRequest, + options: RequestOptions = {}, + ): Promise>> { + if (typeof body?.quoteId !== "string" || !body.quoteId.trim()) { + throw new PolymorfaValidationError( + "Review a tier quote and supply its quoteId before confirming a number tier change.", + ); + } + return this.transport.request({ + method: "PATCH", + path: sessionPath(sessionId), body, ...options, }); diff --git a/packages/typescript/src/platform/types.ts b/packages/typescript/src/platform/types.ts index 65a06f45..f59a2df2 100644 --- a/packages/typescript/src/platform/types.ts +++ b/packages/typescript/src/platform/types.ts @@ -229,18 +229,6 @@ export interface TierPricing { readonly features: readonly string[]; } -export type BillingReminderChannel = "email" | "inApp"; - -export interface UpdateBillingReminderSettingsRequest { - readonly lowBalanceThresholdCents: number; - readonly reminderChannels?: readonly BillingReminderChannel[]; -} - -export interface BillingReminderSettings { - readonly lowBalanceThresholdCents: number; - readonly reminderChannels: readonly BillingReminderChannel[]; -} - /** Metadata for an organization server API key. Raw key material is never returned. */ export interface ApiKey { readonly _id: string; @@ -431,6 +419,8 @@ export interface PlatformSession { readonly status: string; readonly messageCount: number; readonly lastActiveAt: number | null; + /** Charged rolling-window expiry in epoch milliseconds; null if never charged. */ + readonly paidUntil: number | null; } export interface ManagedSession { @@ -481,11 +471,27 @@ export interface SessionBatchRemoveResult { export interface SessionTierOverrideRequest { readonly projectId?: string; - readonly tierOverride: SessionTier | null; + readonly quoteId: string; } -export interface CreateTestingSessionRequest { - readonly projectId: string; - readonly name?: string; - readonly country?: "US" | "GB" | "BR" | "IN"; +export interface NumberTierQuoteRequest { + readonly projectId?: string; + readonly tierOverride: "free" | "standard" | "pro" | null; +} + +export interface NumberTierChange { + readonly id: string; + readonly status: "quoted" | "queued" | "applied" | "rejected"; + readonly failureReason: string | null; + readonly expiresAtMs: number; + readonly quote: { + readonly tier: string; + readonly tierOverride: string | null; + /** Credits, with up to six decimal places; this is not a cash amount. */ + readonly amountCents: number; + readonly priceVersion: string; + readonly action: "upgrade" | "downgrade" | "configure"; + readonly effectiveAtMs: number; + readonly replacesWindowId: string | null; + }; } diff --git a/packages/typescript/src/transport/http.ts b/packages/typescript/src/transport/http.ts index 01192dcc..d9eec4ea 100644 --- a/packages/typescript/src/transport/http.ts +++ b/packages/typescript/src/transport/http.ts @@ -3,6 +3,7 @@ import { PolymorfaAuthorizationError, PolymorfaCancelledError, PolymorfaConflictError, + PolymorfaPaymentRequiredError, PolymorfaConfigurationError, PolymorfaConnectionError, PolymorfaError, @@ -357,6 +358,8 @@ function apiError( return new PolymorfaAuthenticationError(message, options); if (response.status === 403) return new PolymorfaAuthorizationError(message, options); + if (response.status === 402) + return new PolymorfaPaymentRequiredError(message, options); if (response.status === 404) return new PolymorfaNotFoundError(message, options); if (response.status === 409) @@ -370,6 +373,10 @@ function apiError( function errorMessage(body: unknown, status: number): string { if (typeof body === "object" && body !== null) { const record = body as Record; + if (typeof record.error === "object" && record.error !== null) { + const message = (record.error as Record).message; + if (typeof message === "string") return message; + } if (typeof record.error === "string") return record.error; if (typeof record.message === "string") return record.message; } @@ -379,7 +386,11 @@ function errorMessage(body: unknown, status: number): string { function errorCode(body: unknown): Pick { if (typeof body === "object" && body !== null) { - const code = (body as Record).code; + const record = body as Record; + const code = + typeof record.error === "object" && record.error !== null + ? (record.error as Record).code + : record.code; if (typeof code === "string") return { code }; } return {}; diff --git a/packages/typescript/test/billing-contract.test.ts b/packages/typescript/test/billing-contract.test.ts new file mode 100644 index 00000000..5943e708 --- /dev/null +++ b/packages/typescript/test/billing-contract.test.ts @@ -0,0 +1,36 @@ +import { readFileSync } from "node:fs"; +import { expect, it } from "vitest"; + +it("keeps reviewed billing and session administration in the canonical Platform contract", () => { + const spec = JSON.parse( + readFileSync( + new URL("../../../contracts/openapi.platform.json", import.meta.url), + "utf8", + ), + ); + expect(spec.paths["/platform/billing/reminders"]).toBeUndefined(); + expect(spec.components.schemas.PlatformSession.required).toContain( + "paidUntil", + ); + expect( + spec.components.schemas.PlatformSession.properties.paidUntil, + ).toMatchObject({ type: "number", nullable: true }); + expect( + spec.paths["/platform/sessions/{sessionId}/start"].post.responses["402"], + ).toBeDefined(); + expect(spec.components.schemas.SessionTierOverrideRequest.required).toEqual([ + "quoteId", + ]); + expect(spec.components.schemas.NumberTierQuoteRequest.required).toEqual([ + "tierOverride", + ]); + expect(spec.paths["/platform/sessions/{sessionId}"].get.operationId).toBe( + "getSession", + ); + expect(spec.paths["/platform/sessions/{sessionId}"].put.operationId).toBe( + "updateSession", + ); + expect( + spec.paths["/platform/sessions/{sessionId}/tier-quotes"].post.operationId, + ).toBe("quoteSessionTierChange"); +}); diff --git a/packages/typescript/test/billing-tier-change.test.ts b/packages/typescript/test/billing-tier-change.test.ts new file mode 100644 index 00000000..a2b963b6 --- /dev/null +++ b/packages/typescript/test/billing-tier-change.test.ts @@ -0,0 +1,123 @@ +import { afterEach, expect, it } from "vitest"; +import { Client } from "../src/client.js"; +import { + PolymorfaConflictError, + PolymorfaPaymentRequiredError, + PolymorfaValidationError, +} from "../src/errors.js"; +import type { SessionTierOverrideRequest } from "../src/platform/types.js"; +import { ORGANIZATION_API_KEY } from "./support/credentials.js"; +import { startTestServer, type TestServer } from "./support/http-server.js"; + +const servers: TestServer[] = []; +afterEach(async () => { + await Promise.all(servers.splice(0).map((s) => s.close())); +}); +function client(server: TestServer) { + servers.push(server); + return new Client({ + credential: { type: "organizationApiKey", value: ORGANIZATION_API_KEY }, + baseUrl: server.url, + maxNetworkRetries: 0, + }); +} +const quote = { + id: "01994234-0000-7000-8000-000000000001", + status: "quoted", + failureReason: null, + expiresAtMs: 1800000600000, + quote: { + tier: "pro", + tierOverride: "pro", + amountCents: 25.2, + priceVersion: "catalog-1", + action: "upgrade", + effectiveAtMs: 1800000000000, + replacesWindowId: null, + }, +}; + +it("reviews, explicitly confirms, and reads the durable tier result without rounding fractional credits", async () => { + const server = await startTestServer((_, index) => ({ + body: JSON.stringify({ + data: { + ...quote, + status: ["quoted", "queued", "applied"][index], + }, + }), + })); + const sdk = client(server); + const reviewed = await sdk.sessions.quoteTierChange("number/a", { + tierOverride: "pro", + }); + expect(reviewed.data.data.quote.amountCents).toBe(25.2); + expect(server.requests).toHaveLength(1); + const queued = await sdk.sessions.setTierOverride( + "number/a", + { quoteId: reviewed.data.data.id }, + { idempotencyKey: "confirm-1" }, + ); + expect(queued.data.data.status).toBe("queued"); + const applied = await sdk.sessions.retrieveTierChange("number/a", quote.id); + expect(applied.data.data.status).toBe("applied"); + expect(server.requests.map((r) => `${r.method} ${r.path}`)).toEqual([ + "POST /platform/sessions/number%2Fa/tier-quotes", + "PATCH /platform/sessions/number%2Fa", + `GET /platform/sessions/number%2Fa/tier-quotes/${quote.id}`, + ]); + expect(server.requests[1]?.body).toBe(JSON.stringify({ quoteId: quote.id })); + expect(server.requests[1]?.headers["idempotency-key"]).toBe("confirm-1"); +}); + +it("rejects the old unquoted JavaScript call before making a request", async () => { + const server = await startTestServer(() => ({ body: "{}" })); + const sdk = client(server); + expect(() => + sdk.sessions.setTierOverride("number", { + tierOverride: "pro", + } as unknown as SessionTierOverrideRequest), + ).toThrow(PolymorfaValidationError); + expect(server.requests).toHaveLength(0); +}); + +it("preserves quote expiry conflicts without creating or confirming a replacement purchase", async () => { + const server = await startTestServer(() => ({ + status: 409, + body: JSON.stringify({ + error: { code: "conflict", message: "Tier quote expired" }, + }), + })); + const sdk = client(server); + await expect( + sdk.sessions.setTierOverride("number", { quoteId: quote.id }), + ).rejects.toBeInstanceOf(PolymorfaConflictError); + expect(server.requests).toHaveLength(1); +}); + +it("preserves credit quantities through the public billing response", async () => { + const server = await startTestServer(() => ({ + body: '{"data":{"balanceCents":17.500001,"preferredCurrency":"USD"}}', + })); + expect((await client(server).billing.retrieve()).data.data.balanceCents).toBe( + 17.500001, + ); +}); + +it("exposes insufficient funding as a payment-required error without retrying a start", async () => { + const server = await startTestServer(() => ({ + status: 402, + headers: { + "content-type": "application/json", + "x-request-id": "req_funding", + }, + body: '{"error":{"type":"permission_error","code":"entitlement_required","message":"The account needs the required entitlement for this action.","param":null},"data":null,"docs":"https://docs.polymorfa.com/api/errors#entitlement-required"}', + })); + await expect(client(server).sessions.start("number")).rejects.toMatchObject({ + name: PolymorfaPaymentRequiredError.name, + status: 402, + code: "entitlement_required", + message: "The account needs the required entitlement for this action.", + requestId: "req_funding", + }); + expect(server.requests).toHaveLength(1); +}); diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index 82013e78..b3acc18f 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -104,7 +104,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("7540c0cef0d6a9552476a1240c37c072c4781033"); + expect(source.commit).toBe("1600403db9dcf7996dbbb882375b1165859829d3"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index a7322364..43db4bea 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -174,12 +174,12 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "7540c0cef0d6a9552476a1240c37c072c4781033", - total: 456, - covered: 265, + sourceCommit: "1600403db9dcf7996dbbb882375b1165859829d3", + total: 403, + covered: 262, partial: 0, - missing: 37, - excluded: 154, + missing: 35, + excluded: 106, changed: 0, resolutions: [], }); @@ -433,10 +433,6 @@ describe("coverage checker", () => { setBusinessCoverPhoto: "MessagingClient.business.setCoverPhoto", setBusinessMerchantCompliance: "MessagingClient.business.setMerchantCompliance", - updateProjectObservationPolicy: - "MessagingClient.observationPolicies.updateForProject", - updateSessionObservationPolicy: - "MessagingClient.observationPolicies.updateForSession", setBusinessProductVisibility: "MessagingClient.business.setProductVisibility", updateBusinessCollection: "MessagingClient.business.updateCollection", diff --git a/packages/typescript/test/exports.test.ts b/packages/typescript/test/exports.test.ts index ccb1f07e..3c1f2fda 100644 --- a/packages/typescript/test/exports.test.ts +++ b/packages/typescript/test/exports.test.ts @@ -62,7 +62,7 @@ import { type CreateProjectRequest, type CreateCampaignRequest, type CreateChannelRequest, - type CreateSessionRequest, + type CreateQuickLinkRequest, type Contact, type ContactUserInfo, type DisappearingTimerRequest, @@ -107,7 +107,9 @@ import { type SendMessageRequest, type TemplateDefinition, type TierPricing, - type UpdateBillingReminderSettingsRequest, + type NumberTierChange, + type NumberTierQuoteRequest, + type SessionTierOverrideRequest, type WebhookEvent, type UserSecurityCode, type OrganizationQuickLinkSettings, @@ -182,9 +184,9 @@ describe("public exports", () => { expectTypeOf().toHaveProperty("responseRate"); expectTypeOf().toHaveProperty("name"); expectTypeOf().toHaveProperty("dailyRateCents"); - expectTypeOf().toHaveProperty( - "lowBalanceThresholdCents", - ); + expectTypeOf().toHaveProperty("quote"); + expectTypeOf().toHaveProperty("tierOverride"); + expectTypeOf().toHaveProperty("quoteId"); expectTypeOf().toHaveProperty("requestId"); expectTypeOf().toHaveProperty("signal"); expectTypeOf().toMatchTypeOf< @@ -212,7 +214,7 @@ describe("public exports", () => { expectTypeOf().toHaveProperty("resolution"); expectTypeOf().toHaveProperty("status"); expectTypeOf().toHaveProperty("sessionIds"); - expectTypeOf().toHaveProperty("projectId"); + expectTypeOf().toHaveProperty("projectId"); expectTypeOf().toHaveProperty("picture"); expectTypeOf().toHaveProperty("id"); expectTypeOf().toHaveProperty("position"); diff --git a/packages/typescript/test/messaging.test.ts b/packages/typescript/test/messaging.test.ts index 39a4d33b..06994c9a 100644 --- a/packages/typescript/test/messaging.test.ts +++ b/packages/typescript/test/messaging.test.ts @@ -41,33 +41,31 @@ async function messagingServer(): Promise<{ } describe("MessagingClient sessions", () => { - it("lists and creates sessions using the current routes", async () => { + it("lists sessions and creates new ones through QuickLink", async () => { const { client, requests } = await messagingServer(); await client.sessions.list(); - const created = await client.sessions.create( - { projectId: "project_1", sessionId: "support", start: true }, - { idempotencyKey: "session-support" }, - ); - - expect(requests[0]).toMatchObject({ - method: "GET", - path: "/messaging/sessions", - body: "", - }); - expect(requests[1]).toMatchObject({ - method: "POST", - path: "/messaging/sessions", - body: '{"projectId":"project_1","sessionId":"support","start":true}', + await client.quickLinks.create({ + projectId: "project_1", + configuration: { + connectionPreference: "linked", + historySync: { consent: "ask" }, + }, }); - expect(requests[1]?.headers["idempotency-key"]).toBe("session-support"); - expect(created.metadata.requestId).toBe("req_messaging"); + expect(requests.map(({ method, path }) => `${method} ${path}`)).toEqual([ + "GET /platform/sessions", + "POST /messaging/quicklinks", + ]); + expect(client.sessions).not.toHaveProperty("create"); }); it("encodes session identifiers for lifecycle and account operations", async () => { const { client, requests } = await messagingServer(); const id = "support/eu"; await client.sessions.retrieve(id); - await client.sessions.update(id, { config: { presence: true } }); + await client.sessions.update(id, { + revision: 0, + configuration: { set: { historySync: { mode: "deliver" } } }, + }); await client.sessions.start(id); await client.sessions.stop(id); await client.sessions.restart(id); @@ -76,16 +74,18 @@ describe("MessagingClient sessions", () => { await client.sessions.account(id); expect(requests.map(({ method, path }) => `${method} ${path}`)).toEqual([ - "GET /messaging/sessions/support%2Feu", - "PUT /messaging/sessions/support%2Feu", - "POST /messaging/sessions/support%2Feu/start", - "POST /messaging/sessions/support%2Feu/stop", - "POST /messaging/sessions/support%2Feu/restart", - "POST /messaging/sessions/support%2Feu/logout", - "DELETE /messaging/sessions/support%2Feu", - "GET /messaging/sessions/support%2Feu/me", + "GET /platform/sessions/support%2Feu", + "PUT /platform/sessions/support%2Feu", + "POST /platform/sessions/support%2Feu/start", + "POST /platform/sessions/support%2Feu/stop", + "POST /platform/sessions/support%2Feu/restart", + "POST /platform/sessions/support%2Feu/logout", + "DELETE /platform/sessions/support%2Feu", + "GET /platform/sessions/support%2Feu/me", ]); - expect(requests[1]?.body).toBe('{"config":{"presence":true}}'); + expect(requests[1]?.body).toBe( + '{"revision":0,"configuration":{"set":{"historySync":{"mode":"deliver"}}}}', + ); }); it("retrieves JSON pairing data and requests a phone pairing code", async () => { @@ -194,10 +194,10 @@ describe("MessagingClient client tokens", () => { await client.clientTokens.deleteRules("support/eu"); expect(requests.map(({ method, path }) => `${method} ${path}`)).toEqual([ - "POST /messaging/client-tokens", - "GET /messaging/sessions/support%2Feu/client-rules", - "PUT /messaging/sessions/support%2Feu/client-rules", - "DELETE /messaging/sessions/support%2Feu/client-rules", + "POST /platform/client-tokens", + "GET /platform/sessions/support%2Feu/client-rules", + "PUT /platform/sessions/support%2Feu/client-rules", + "DELETE /platform/sessions/support%2Feu/client-rules", ]); expect(requests[0]?.body).toBe( '{"session":"support/eu","ephemeralId":"user-1-tab-2","ttlSeconds":600}', @@ -436,9 +436,7 @@ describe("MessagingClient webhooks", () => { credential: { type: "clientToken", value: "pmfa_ct_widget" }, baseUrl: server.url, }); - await client.sessions.list(); - expect(server.requests[0]?.headers.authorization).toBe( - "Bearer pmfa_ct_widget", - ); + expect(() => client.sessions.list()).toThrow(/server API key/); + expect(server.requests).toHaveLength(0); }); }); diff --git a/packages/typescript/test/observation-policies.test.ts b/packages/typescript/test/observation-policies.test.ts index cc7761ef..f12dac9a 100644 --- a/packages/typescript/test/observation-policies.test.ts +++ b/packages/typescript/test/observation-policies.test.ts @@ -134,46 +134,17 @@ describe("MessagingClient observation policies", () => { expect(requests[0]?.headers["polymorfa-version"]).toBe("next"); }); - it("maps exact policy replacements and preserves idempotency", async () => { + it("replaces policy writes with unified configuration", async () => { const { client, requests } = await policiesServer(); - const options = { idempotencyKey: "policy-change" } as const; - - await client.observationPolicies.updateForProject( - "00000000-0000-4000-8000-000000000001", - { - presenceMode: "events", - typingMode: "cache", - labelMode: "project", - }, - options, - ); - await client.observationPolicies.updateForSession( - "support/eu", - { - presenceMode: "inherit", - typingMode: "off", - labelMode: "cache", - }, - options, - ); - - expect( - requests.map(({ method, path, body }) => ({ method, path, body })), - ).toEqual([ - { - method: "PUT", - path: "/messaging/projects/00000000-0000-4000-8000-000000000001/observation-policy", - body: '{"presenceMode":"events","typingMode":"cache","labelMode":"project"}', - }, - { - method: "PUT", - path: "/messaging/support%2Feu/observation-policy", - body: '{"presenceMode":"inherit","typingMode":"off","labelMode":"cache"}', - }, - ]); - expect(requests.map(({ headers }) => headers["idempotency-key"])).toEqual([ - "policy-change", - "policy-change", - ]); + expect(client.observationPolicies).not.toHaveProperty("updateForSession"); + expect(client.observationPolicies).not.toHaveProperty("updateForProject"); + await client.sessions.update("support", { + revision: 2, + configuration: { set: { observation: { presenceMode: "events" } } }, + }); + expect(JSON.parse(requests[0]!.body)).toEqual({ + revision: 2, + configuration: { set: { observation: { presenceMode: "events" } } }, + }); }); }); diff --git a/packages/typescript/test/onboarding.test.ts b/packages/typescript/test/onboarding.test.ts new file mode 100644 index 00000000..13b26a99 --- /dev/null +++ b/packages/typescript/test/onboarding.test.ts @@ -0,0 +1,63 @@ +import { afterEach, expect, it, vi } from "vitest"; +import { MessagingClient } from "../src/index.js"; +import { ORGANIZATION_API_KEY } from "./support/credentials.js"; +afterEach(() => vi.unstubAllGlobals()); +it("uploads fixture content separately and advances an existing QuickLink without session credentials", async () => { + const fetcher = vi.fn( + async () => + new Response(JSON.stringify({ fixtureId: "fixture" }), { + headers: { "content-type": "application/json" }, + }), + ); + vi.stubGlobal("fetch", fetcher); + const client = new MessagingClient({ + credential: { type: "apiKey", value: ORGANIZATION_API_KEY }, + baseUrl: "https://api.example", + }); + const input = { + messages: [ + { + id: "m1", + senderPhone: "+12025550123", + text: "Fixture", + timestamp: 1, + fromMe: false, + }, + ], + }; + await client.testing.createHistoryFixture("project-a", input); + const signup = { + quicklinkId: "ql_test", + result: { + code: "test-code", + wabaId: "1", + phoneNumberId: "2", + coexistence: true, + }, + }; + await client.cloudOnboarding.advance(signup); + expect(JSON.parse(String(fetcher.mock.calls[0]?.[1]?.body))).toEqual(input); + expect(JSON.parse(String(fetcher.mock.calls[1]?.[1]?.body))).toEqual(signup); + expect(String(fetcher.mock.calls[0]?.[0])).toContain( + "/messaging/testing/project-a/history-fixtures", + ); + expect(String(fetcher.mock.calls[1]?.[0])).toContain( + "/messaging/cloud-api/embedded-signup", + ); +}); + +it("rejects client-token fixture upload and Meta continuation before transport", () => { + const fetcher = vi.fn(); + const client = new MessagingClient({ + credential: { type: "clientToken", value: "pmfa_ct_browser" }, + baseUrl: "https://api.example", + fetch: fetcher, + }); + expect(() => + client.testing.createHistoryFixture("project-a", { messages: [] }), + ).toThrow("requires an organization API key or project token"); + expect(() => + client.cloudOnboarding.advance({ quicklinkId: "ql_test" }), + ).toThrow("requires an organization API key or project token"); + expect(fetcher).not.toHaveBeenCalled(); +}); diff --git a/packages/typescript/test/platform-automation.test.ts b/packages/typescript/test/platform-automation.test.ts index deb4ae85..2e3ee279 100644 --- a/packages/typescript/test/platform-automation.test.ts +++ b/packages/typescript/test/platform-automation.test.ts @@ -57,23 +57,10 @@ describe("Client billing", () => { expect(billing.metadata.requestId).toBe("req_platform_automation"); }); - it("updates reminder settings with an idempotency key", async () => { + it("does not expose the retired reminder mutation", async () => { const { client, requests } = await platformServer(); - - await client.billing.updateReminderSettings( - { - lowBalanceThresholdCents: 2_500, - reminderChannels: ["email", "inApp"], - }, - { idempotencyKey: "billing-reminders-1" }, - ); - - expect(requests[0]).toMatchObject({ - method: "PATCH", - path: "/platform/billing/reminders", - body: '{"lowBalanceThresholdCents":2500,"reminderChannels":["email","inApp"]}', - }); - expect(requests[0]?.headers["idempotency-key"]).toBe("billing-reminders-1"); + expect(client.billing).not.toHaveProperty("updateReminderSettings"); + expect(requests).toHaveLength(0); }); }); diff --git a/packages/typescript/test/platform.test.ts b/packages/typescript/test/platform.test.ts index 4a88c0e4..4d71e52a 100644 --- a/packages/typescript/test/platform.test.ts +++ b/packages/typescript/test/platform.test.ts @@ -134,23 +134,32 @@ describe("Client sessions", () => { await client.sessions.delete("session/a"); await client.sessions.setTierOverride("session/a", { projectId: "project_1", - tierOverride: "pro", - }); - await client.sessions.createTesting({ - projectId: "project_1", - name: "Demo", - country: "US", + quoteId: "quote_1", }); expect(requests.map(({ method, path }) => `${method} ${path}`)).toEqual([ "POST /platform/sessions/session%2Fa/stop", "DELETE /platform/sessions/session%2Fa", "PATCH /platform/sessions/session%2Fa", - "POST /platform/sessions/testing", ]); expect(requests[0]?.body).toBe('{"projectId":"project_1"}'); expect(requests[2]?.body).toBe( - '{"projectId":"project_1","tierOverride":"pro"}', + '{"projectId":"project_1","quoteId":"quote_1"}', ); }); }); + +it("reads and updates unified session configuration through Platform", async () => { + const { client, requests } = await platformServer(); + const patch = { + revision: 4, + configuration: { reset: ["historySync.mode" as const] }, + }; + await client.sessions.retrieve("support/eu"); + await client.sessions.update("support/eu", patch); + expect(requests.map(({ method, path }) => `${method} ${path}`)).toEqual([ + "GET /platform/sessions/support%2Feu", + "PUT /platform/sessions/support%2Feu", + ]); + expect(JSON.parse(requests[1]!.body)).toEqual(patch); +}); diff --git a/packages/typescript/test/quicklinks.test.ts b/packages/typescript/test/quicklinks.test.ts index 82c9798b..f1c2d7d9 100644 --- a/packages/typescript/test/quicklinks.test.ts +++ b/packages/typescript/test/quicklinks.test.ts @@ -73,14 +73,11 @@ describe("MessagingClient.quickLinks", () => { { projectId: "11111111-2222-4333-8444-555555555555", customerId: "66666666-7777-4888-8999-000000000000", - methods: ["qr", "pairing"], - businessName: "Acme", - historySync: "ask", - callbackUrl: "https://acme.example/callback", - theme: "system", - accent: "#6633ff", - prefillPhone: "+15551234567", - expiresInSeconds: 900, + configuration: { + methods: ["qr", "pairing"], + historySync: { consent: "ask" }, + prefillPhone: "+15551234567", + }, }, { idempotencyKey: "quicklink-123" }, ); @@ -104,7 +101,9 @@ describe("MessagingClient.quickLinks", () => { "GET /messaging/quicklinks/ql%2F123", "DELETE /messaging/quicklinks/ql%2F123", ]); - expect(server.requests[0]?.body).toContain('"expiresInSeconds":900'); + expect(JSON.parse(server.requests[0]!.body)).toMatchObject({ + configuration: { historySync: { consent: "ask" } }, + }); expect(server.requests[0]?.headers["idempotency-key"]).toBe( "quicklink-123", ); diff --git a/packages/typescript/test/session-configuration.test.ts b/packages/typescript/test/session-configuration.test.ts new file mode 100644 index 00000000..4c530cd6 --- /dev/null +++ b/packages/typescript/test/session-configuration.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it, vi } from "vitest"; +import { Client, MessagingClient } from "../src/index.js"; +import { ORGANIZATION_API_KEY } from "./support/credentials.js"; + +describe("session configuration", () => { + it("binds defaults to the selected project and preserves resets and revisions", async () => { + const fetch = vi.fn(async () => + Response.json({ data: { effective: {}, revisions: { project: 2 } } }), + ); + const client = new Client({ + credential: { type: "organizationApiKey", value: ORGANIZATION_API_KEY }, + fetch, + }); + await client.sessionConfiguration.retrieve(); + await client.project("project-a").sessionConfiguration.update({ + revision: 2, + configuration: { reset: ["historySync.mode"] }, + }); + expect(String(fetch.mock.calls[0]?.[0])).toContain( + "/platform/session-configuration", + ); + expect(JSON.parse(String(fetch.mock.calls[1]?.[1]?.body))).toEqual({ + projectId: "project-a", + revision: 2, + configuration: { reset: ["historySync.mode"] }, + }); + }); + it("exposes new creation only through QuickLink", () => { + const client = new MessagingClient({ + credential: { type: "apiKey", value: ORGANIZATION_API_KEY }, + }); + expect(client.sessions).not.toHaveProperty("create"); + expect(client.quickLinks.create).toBeTypeOf("function"); + }); +});