diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c51eca9..d291f51d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +- Breaking, pending the API route cutover: Test phone methods take a Number ID + without a project argument. `testing.triggerEvent(numberId, body)` omits + body `session` and accepts `fromNumber`; `listEventFixtures()` has no project + argument. History fixtures keep their project argument and use + `/messaging/projects/{projectId}/test-history-fixtures`. Real Numbers return + `404 resource_not_found`. Scope names remain `sandbox:read` / `sandbox:write`. + Coordinated API draft: polymorfa/polymorfa#792. This package is not published + or deployed by that change. + - Webhook phone offline start day. Platform and Messaging webhook create, update and read types gain `phoneOfflineStartDay` (7-13, default 10): the first day without the phone on which the webhook receives diff --git a/contracts/README.md b/contracts/README.md index 32a341e3..2bd732f1 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -1,5 +1,17 @@ # Contract coverage +## Number-scoped Test phone and events + +`test-number-dropin-routes.json`, `testing-phone.json` and `testing-events.json` +pin the six moved operations to API `79e62733b1b3d523618232a3794e26df64b7f486` +(see the full source commit in each JSON). The six coverage entries carry +`sourceCommit` overrides; the global baseline remains unchanged. Collision +schemas in the merged snapshot are namespaced `TestDropin*` so unrelated +operations retain their baseline fingerprints. The focused snapshots preserve +the authoritative schema names. No native language SDK packages implement +these methods in this repository. This is an unpublished breaking contract +change, coordinated with polymorfa/polymorfa#792. + ## Request log supplement `request-logs.json` pins `GET /platform/projects/{projectId}/request-logs` and @@ -407,8 +419,8 @@ reads described at the end of this document. | Total | 558 | This revision adds test event triggering -(`POST /messaging/testing/{projectId}/events`) and fixture listing -(`GET /messaging/testing/{projectId}/events/fixtures`), covered by +(`POST /messaging/{session}/test-events`) and fixture listing +(`GET /messaging/test-events/fixtures`), covered by `MessagingClient.testing.triggerEvent` (with the optional `Idempotency-Key` header through `options.idempotencyKey`) and `MessagingClient.testing.listEventFixtures`. diff --git a/contracts/coverage.json b/contracts/coverage.json index 092a92ed..31910efa 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -690,6 +690,18 @@ "method": "MessagingClient.sessions.qr" } }, + { + "family": "messaging", + "method": "GET", + "path": "/messaging/{session}/phone", + "operationId": "getTestingPhone", + "fingerprint": "c637abc2946a96ab1d7a2fc6b86941dc29dd224c402487ba6b1ba12a11f62801", + "sourceCommit": "79e62733b1b3d523618232a3794e26df64b7f486", + "typescript": { + "status": "covered", + "method": "MessagingClient.testing.getPhone" + } + }, { "family": "messaging", "method": "GET", @@ -990,13 +1002,14 @@ { "family": "messaging", "method": "GET", - "path": "/messaging/testing/{projectId}/events/fixtures", + "path": "/messaging/test-events/fixtures", "operationId": "listTestEventFixtures", - "fingerprint": "0a1482d5fd5a365c0ba2e241d1f45ed0d5c88615c871ea97af442c8f26f3d22d", + "fingerprint": "a5de9ee23e1311ae016d0a939e85e47e1e893075d168fc8934a24607a262e72c", "typescript": { "status": "covered", "method": "MessagingClient.testing.listEventFixtures" - } + }, + "sourceCommit": "79e62733b1b3d523618232a3794e26df64b7f486" }, { "family": "messaging", @@ -1638,6 +1651,30 @@ "method": "MessagingClient.sessions.requestPairingCode" } }, + { + "family": "messaging", + "method": "POST", + "path": "/messaging/{session}/phone/devices/{deviceId}/unlink", + "operationId": "unlinkTestingPhoneDevice", + "fingerprint": "e2bf4a4710e39c69e1e8fc6470463de52a6dace45590f30c2dc4743dcffe5ff0", + "sourceCommit": "79e62733b1b3d523618232a3794e26df64b7f486", + "typescript": { + "status": "covered", + "method": "MessagingClient.testing.unlinkPhoneDevice" + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/messaging/{session}/phone/messages", + "operationId": "sendTestingPhoneMessage", + "fingerprint": "beba9148848a654eefe1e4825b3ee134556f05c705219eef5361a79a867fbdfb", + "sourceCommit": "79e62733b1b3d523618232a3794e26df64b7f486", + "typescript": { + "status": "covered", + "method": "MessagingClient.testing.sendPhoneMessage" + } + }, { "family": "messaging", "method": "POST", @@ -1671,6 +1708,18 @@ "method": "MessagingClient.cloudTemplates.create" } }, + { + "family": "messaging", + "method": "POST", + "path": "/messaging/{session}/test-events", + "operationId": "triggerTestEvent", + "fingerprint": "84b132cf4d9f8838d939836d6387692eb9b5a803bad4fc992c57196bfc42d078", + "typescript": { + "status": "covered", + "method": "MessagingClient.testing.triggerEvent" + }, + "sourceCommit": "79e62733b1b3d523618232a3794e26df64b7f486" + }, { "family": "messaging", "method": "POST", @@ -1704,6 +1753,18 @@ "method": "MessagingClient.media.persist" } }, + { + "family": "messaging", + "method": "POST", + "path": "/messaging/projects/{projectId}/test-history-fixtures", + "operationId": "createTestingHistoryFixture", + "fingerprint": "b5327fa44a7c347ad90c67c25b4c2afa3cf568d835472611879c5dd0d0f4c70c", + "typescript": { + "status": "covered", + "method": "MessagingClient.testing.createHistoryFixture" + }, + "sourceCommit": "79e62733b1b3d523618232a3794e26df64b7f486" + }, { "family": "messaging", "method": "POST", @@ -1836,28 +1897,6 @@ "method": "MessagingClient.quickLinks.create" } }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/testing/{projectId}/events", - "operationId": "triggerTestEvent", - "fingerprint": "c2d5c723923084c9fcca1be246f046b23d6a613eae5b6e25b71c2e6df462ec55", - "typescript": { - "status": "covered", - "method": "MessagingClient.testing.triggerEvent" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/testing/{projectId}/history-fixtures", - "operationId": "createTestingHistoryFixture", - "fingerprint": "8941f260e0e4156976fe76ca2ce3545dbf2ff0a0a909bee938687e201cdfac65", - "typescript": { - "status": "covered", - "method": "MessagingClient.testing.createHistoryFixture" - } - }, { "family": "messaging", "method": "POST", @@ -3017,6 +3056,17 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "GET", + "path": "/console/insights/vitals", + "operationId": "getConsoleErrorVitals", + "fingerprint": "4c3c1667cc5f2feb631064100976c3e399cd917cb29e4f336b85baf81335e217", + "typescript": { + "status": "excluded", + "reason": "Dashboard-only operational error vitals require a live Console identity; server API keys do not grant this authority." + } + }, { "family": "platform", "method": "GET", @@ -3237,6 +3287,34 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/analytics", + "operationId": "getWhatsAppAnalytics", + "fingerprint": "1ac8b2438ef13263289d71f0d956fd3f7b60b44ac0999151bc96f30d31798b91", + "typescript": { + "status": "covered", + "method": "Client.analytics.get" + }, + "sourceContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", + "sourceCommit": "171a9683ff45d11a4b5d3917dc428ad2722b0d99", + "sdkContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8" + }, + { + "family": "platform", + "method": "GET", + "path": "/platform/analytics/metrics", + "operationId": "exportWhatsAppAnalyticsMetrics", + "typescript": { + "status": "covered", + "method": "Client.analytics.metrics" + }, + "fingerprint": "520ce37bc6a198e313d9495ddd968ebb54cbbe5cc84f4d6bea636c43a4ea62c5", + "sourceContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", + "sdkContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", + "sourceCommit": "171a9683ff45d11a4b5d3917dc428ad2722b0d99" + }, { "family": "platform", "method": "GET", @@ -4007,6 +4085,34 @@ "method": "Client.projects.list" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/projects/{projectId}/analytics", + "operationId": "getProjectWhatsAppAnalytics", + "fingerprint": "dc6ab3ff0e56d2e97c26fb64c2090b8b8f900a0f09c86e221f84932e9fb0a36f", + "typescript": { + "status": "covered", + "method": "Client.analytics.get" + }, + "sourceContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", + "sourceCommit": "171a9683ff45d11a4b5d3917dc428ad2722b0d99", + "sdkContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8" + }, + { + "family": "platform", + "method": "GET", + "path": "/platform/projects/{projectId}/analytics/metrics", + "operationId": "exportProjectWhatsAppAnalyticsMetrics", + "typescript": { + "status": "covered", + "method": "Client.analytics.metrics" + }, + "fingerprint": "56e2540bc5d78c3f458fbd363d63b101df49fcd273b7cc81e1fa9318d69b30b6", + "sourceContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", + "sdkContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", + "sourceCommit": "171a9683ff45d11a4b5d3917dc428ad2722b0d99" + }, { "family": "platform", "method": "GET", @@ -6335,73 +6441,6 @@ "status": "covered", "method": "Client.sessions.updateSafeMode" } - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/analytics", - "operationId": "getWhatsAppAnalytics", - "fingerprint": "1ac8b2438ef13263289d71f0d956fd3f7b60b44ac0999151bc96f30d31798b91", - "typescript": { - "status": "covered", - "method": "Client.analytics.get" - }, - "sourceContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", - "sourceCommit": "171a9683ff45d11a4b5d3917dc428ad2722b0d99", - "sdkContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8" - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/projects/{projectId}/analytics", - "operationId": "getProjectWhatsAppAnalytics", - "fingerprint": "dc6ab3ff0e56d2e97c26fb64c2090b8b8f900a0f09c86e221f84932e9fb0a36f", - "typescript": { - "status": "covered", - "method": "Client.analytics.get" - }, - "sourceContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", - "sourceCommit": "171a9683ff45d11a4b5d3917dc428ad2722b0d99", - "sdkContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8" - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/analytics/metrics", - "operationId": "exportWhatsAppAnalyticsMetrics", - "typescript": { - "status": "covered", - "method": "Client.analytics.metrics" - }, - "fingerprint": "520ce37bc6a198e313d9495ddd968ebb54cbbe5cc84f4d6bea636c43a4ea62c5", - "sourceContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", - "sdkContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", - "sourceCommit": "171a9683ff45d11a4b5d3917dc428ad2722b0d99" - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/projects/{projectId}/analytics/metrics", - "operationId": "exportProjectWhatsAppAnalyticsMetrics", - "typescript": { - "status": "covered", - "method": "Client.analytics.metrics" - }, - "fingerprint": "56e2540bc5d78c3f458fbd363d63b101df49fcd273b7cc81e1fa9318d69b30b6", - "sourceContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", - "sdkContractSha256": "71454ab4bb66d844aeedd325d9a62086cd6b6ba39a135197fd4f5ca29ef089e8", - "sourceCommit": "171a9683ff45d11a4b5d3917dc428ad2722b0d99" - }, - { - "family": "platform", - "method": "GET", - "path": "/console/insights/vitals", - "operationId": "getConsoleErrorVitals", - "fingerprint": "4c3c1667cc5f2feb631064100976c3e399cd917cb29e4f336b85baf81335e217", - "typescript": { - "status": "excluded", - "reason": "Dashboard-only operational error vitals require a live Console identity; server API keys do not grant this authority." - } } ] } diff --git a/contracts/openapi.messaging.json b/contracts/openapi.messaging.json index 95038e1b..81bf444f 100644 --- a/contracts/openapi.messaging.json +++ b/contracts/openapi.messaging.json @@ -6180,21 +6180,21 @@ "type": "number", "description": "Credit quantity this number consumed inside the window, with up to six decimal places.", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "example": 1080 }, "capCents": { "type": "number", "description": "Maximum credit quantity this claim can return, with up to six decimal places.", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "example": 1080 }, "amountCents": { "type": "number", "description": "Credit quantity this claim returns, with up to six decimal places.", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "example": 1080 }, "summary": { @@ -11495,12 +11495,12 @@ "type": "string", "enum": [ "", - "๐Ÿ‘", - "โค๏ธ", - "๐Ÿ˜‚", - "๐Ÿ˜ฎ", - "๐Ÿ˜ข", - "๐Ÿ™" + "\ud83d\udc4d", + "\u2764\ufe0f", + "\ud83d\ude02", + "\ud83d\ude2e", + "\ud83d\ude22", + "\ud83d\ude4f" ], "description": "Empty clears the previous reaction." } @@ -20544,15 +20544,172 @@ }, "additionalProperties": false }, - "TriggerTestEventRequest": { + "TestDropinPublicError": { "type": "object", "properties": { - "session": { + "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", + "cloud_template_authority_restricted", + "gate_limit_reached", + "method_not_allowed", + "permission_denied", + "missing_scope", + "resource_not_found", + "hms_not_enabled", + "media_fetch_failed", + "resource_gone", + "feature_unavailable", + "stream_cursor_invalid", + "stream_cursor_expired", + "stream_connection_limit_reached", + "state_conflict", + "team_deletion_scheduled", + "hybrid_routing_paused", + "hybrid_choice_required", + "hybrid_transition_ineligible", + "hybrid_automation_unavailable", + "hybrid_group_linked_devices_unavailable", + "hybrid_transport_unavailable", + "hybrid_target_reference_unavailable", + "send_outcome_unknown", + "idempotency_conflict", + "idempotency_in_progress", + "idempotency_completed", + "idempotency_outcome_unknown", + "call_claimed", + "call_not_ringing", + "connection_limit", + "unsupported_for_connection", + "call_permission_required", + "invalid_sip_trunk", + "calls_disabled", + "call_recipient_opted_out", + "call_destination_blocked", + "call_permission_request_limited", + "call_permission_granted", + "order_status_transition_invalid", + "order_cancellation_failed", + "call_opt_out_limit", + "number_restricted", + "sip_trunk_in_use", + "sip_trunk_revision_conflict", + "sip_trunk_limit", + "sip_unavailable", + "voice_not_enabled", + "provider_credential_invalid", + "provider_unavailable", + "asset_not_ready", + "voice_asset_in_use", + "voice_asset_revision_conflict", + "voice_unavailable", + "rate_limit_exceeded", + "internal_error", + "operation_not_supported", + "upstream_failure", + "service_unavailable", + "credential_verification_unavailable", + "session_not_ready", + "command_dispatch_failed", + "result_unknown", + "parent_bsuid_not_supported", + "recipient_not_on_whatsapp", + "conversation_window_closed", + "template_not_approved", + "media_too_large", + "whatsapp_rate_limited", + "new_chat_limit_reached", + "marketing_disabled_on_cloud_api", + "whatsapp_account_restricted", + "whatsapp_authorization_required", + "whatsapp_payment_method_required", + "whatsapp_number_not_registered", + "whatsapp_groups_ineligible", + "whatsapp_group_creation_paused", + "whatsapp_group_limit_reached", + "whatsapp_group_full", + "whatsapp_group_suspended", + "whatsapp_group_has_no_participants", + "bansafe_suspended", + "bansafe_org_suspended", + "bansafe_cold_blocked", + "bansafe_cold_held", + "bansafe_throttled", + "bansafe_daily_allowance_reached", + "bansafe_accounting_unavailable", + "bansafe_send_outcome_unknown", + "campaigns_not_entitled", + "campaign_throughput_capped", + "campaign_approval_required", + "campaign_approval_stale", + "campaign_variables_missing" + ] + }, + "message": { + "type": "string" + }, + "param": { + "type": "string", + "nullable": true + }, + "request_id": { + "type": "string", + "description": "Matches the X-Request-Id response header. Include it when you contact support." + }, + "request_log_url": { + "type": "string", + "format": "uri", + "description": "Console page that shows this request. Returned only to organization and project credentials whose request was logged." + } + }, + "required": [ + "type", + "code", + "message", + "param", + "request_id" + ] + }, + "data": { "type": "string", - "minLength": 1, - "maxLength": 128, - "description": "Test number session name in the project." + "nullable": true, + "enum": [ + null + ], + "description": "Always null for a failed request." }, + "docs": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "error", + "data", + "docs" + ] + }, + "TestDropinTriggerTestEventRequest": { + "type": "object", + "properties": { "event": { "type": "string", "enum": [ @@ -20571,7 +20728,7 @@ "overrides": { "$ref": "#/components/schemas/TestEventOverrides" }, - "fromSession": { + "fromNumber": { "type": "string", "minLength": 1, "maxLength": 128, @@ -20579,10 +20736,108 @@ } }, "required": [ - "session", "event" ], "additionalProperties": false + }, + "TestingPhoneState": { + "type": "object", + "properties": { + "session": { + "type": "string" + }, + "phone": { + "type": "string", + "description": "The Test number, in E.164 format." + }, + "online": { + "type": "boolean", + "description": "True while the simulated phone is running." + }, + "devices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "deviceId": { + "type": "integer" + } + }, + "required": [ + "deviceId" + ] + }, + "description": "Companions linked to the phone, such as Polymorfa's own session and Inspector." + } + }, + "required": [ + "session", + "phone", + "online", + "devices" + ] + }, + "TestingPhoneMessageRequest": { + "type": "object", + "properties": { + "to": { + "type": "string", + "pattern": "^\\+[1-9]\\d{7,14}$", + "description": "Another Test number in the same project, in E.164 format." + }, + "text": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + }, + "required": [ + "to", + "text" + ], + "additionalProperties": false + }, + "TestingPhoneMessage": { + "type": "object", + "properties": { + "session": { + "type": "string" + }, + "to": { + "type": "string" + }, + "messageId": { + "type": "string", + "nullable": true + } + }, + "required": [ + "session", + "to", + "messageId" + ] + }, + "TestingPhoneUnlink": { + "type": "object", + "properties": { + "session": { + "type": "string" + }, + "deviceId": { + "type": "integer" + }, + "unlinked": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "session", + "deviceId", + "unlinked" + ] } }, "headers": { @@ -21236,7 +21491,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -21460,7 +21715,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -22515,7 +22770,7 @@ "Messages" ], "summary": "Send a message", - "description": "**Protocols:** Linked Device API ยท Meta Cloud API ยท Polymorfa Hybrid Link\n\n**Required scope:** `messages:write`\n\nSend a message to a conversation. Put exactly one message kind inside content. Testing sessions send only to active testing numbers in the same project; supply the recipient phoneNumber.", + "description": "**Protocols:** Linked Device API \u00b7 Meta Cloud API \u00b7 Polymorfa Hybrid Link\n\n**Required scope:** `messages:write`\n\nSend a message to a conversation. Put exactly one message kind inside content. Testing sessions send only to active testing numbers in the same project; supply the recipient phoneNumber.", "security": [ { "BearerAuth": [] @@ -23374,7 +23629,7 @@ "Meta Cloud API", "Polymorfa Hybrid Link" ], - "description": "**Protocols:** Linked Device API ยท Meta Cloud API ยท Polymorfa Hybrid Link\n\n**Required scope:** `messages:write`" + "description": "**Protocols:** Linked Device API \u00b7 Meta Cloud API \u00b7 Polymorfa Hybrid Link\n\n**Required scope:** `messages:write`" } }, "/messaging/{session}/messages/typing": { @@ -23384,7 +23639,7 @@ "Messages" ], "summary": "Set typing indicator for a chat", - "description": "**Protocols:** Linked Device API ยท Meta Cloud API\n\n**Required scope:** `messages:write`\n\nOn a Linked Devices number, `state` sets typing, recording or paused. On an official WhatsApp Business number, send `state: \"typing\"` with the `id` of a message you received; this marks that message read and shows a typing indicator until you reply or 25 seconds pass. On a Number with both connections, this method uses the Linked Devices connection; use the Graph API status request for official typing. Other states return `501`, and a missing `id` returns `400`.", + "description": "**Protocols:** Linked Device API \u00b7 Meta Cloud API\n\n**Required scope:** `messages:write`\n\nOn a Linked Devices number, `state` sets typing, recording or paused. On an official WhatsApp Business number, send `state: \"typing\"` with the `id` of a message you received; this marks that message read and shows a typing indicator until you reply or 25 seconds pass. On a Number with both connections, this method uses the Linked Devices connection; use the Graph API status request for official typing. Other states return `501`, and a missing `id` returns `400`.", "security": [ { "BearerAuth": [] @@ -24176,7 +24431,7 @@ "Meta Cloud API", "Polymorfa Hybrid Link" ], - "description": "**Protocols:** Linked Device API ยท Meta Cloud API ยท Polymorfa Hybrid Link\n\n**Required scope:** `messages:write`" + "description": "**Protocols:** Linked Device API \u00b7 Meta Cloud API \u00b7 Polymorfa Hybrid Link\n\n**Required scope:** `messages:write`" } }, "/messaging/{session}/messages/star": { @@ -24771,7 +25026,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -25141,7 +25396,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -25643,7 +25898,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -25866,7 +26121,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -26098,7 +26353,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -26939,7 +27194,7 @@ "Chats" ], "summary": "Download stored message media", - "description": "**Protocols:** Meta Cloud API\n\n**Required scopes (all):** `messages:read` ยท `media:read`\n\nReturns the stored copy of an Official API message's media when `mediaRetrieval.state` is `stored`. Requires `messages:read` and `media:read`. Beta: requires team enrollment.", + "description": "**Protocols:** Meta Cloud API\n\n**Required scopes (all):** `messages:read` \u00b7 `media:read`\n\nReturns the stored copy of an Official API message's media when `mediaRetrieval.state` is `stored`. Requires `messages:read` and `media:read`. Beta: requires team enrollment.", "security": [ { "BearerAuth": [] @@ -27576,7 +27831,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -27823,7 +28078,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -28067,7 +28322,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -28375,7 +28630,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -28696,7 +28951,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -28856,7 +29111,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -28952,7 +29207,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -29049,7 +29304,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -29209,7 +29464,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -29273,7 +29528,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -29357,7 +29612,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -29549,7 +29804,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -29709,7 +29964,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, call authority cannot be verified, or usage limits could not be checked", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, call authority cannot be verified, or usage limits could not be checked", "content": { "application/json": { "schema": { @@ -29804,7 +30059,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -29964,7 +30219,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -30028,7 +30283,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -30123,7 +30378,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -30283,7 +30538,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -30347,7 +30602,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -30443,7 +30698,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -30571,7 +30826,7 @@ } }, "409": { - "description": "Call not ready โ€” no media session on the owning pod yet", + "description": "Call not ready \u2014 no media session on the owning pod yet", "content": { "application/json": { "schema": { @@ -30603,7 +30858,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -30667,7 +30922,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -30763,7 +31018,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -30923,7 +31178,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -30987,7 +31242,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -31083,7 +31338,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -31243,7 +31498,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -31307,7 +31562,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -31403,7 +31658,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -31563,7 +31818,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -31659,7 +31914,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -31754,7 +32009,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -31914,7 +32169,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -32010,7 +32265,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -32106,7 +32361,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -32234,7 +32489,7 @@ } }, "409": { - "description": "Call not ready โ€” no media session on the owning pod yet", + "description": "Call not ready \u2014 no media session on the owning pod yet", "content": { "application/json": { "schema": { @@ -32266,7 +32521,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -32330,7 +32585,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -32417,7 +32672,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -32545,7 +32800,7 @@ } }, "409": { - "description": "Call not ready โ€” no media session on the owning pod yet", + "description": "Call not ready \u2014 no media session on the owning pod yet", "content": { "application/json": { "schema": { @@ -32577,7 +32832,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -32641,7 +32896,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -32738,7 +32993,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -32898,7 +33153,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -33080,7 +33335,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -33208,7 +33463,7 @@ } }, "409": { - "description": "Call not ready โ€” no media session on the owning pod yet", + "description": "Call not ready \u2014 no media session on the owning pod yet", "content": { "application/json": { "schema": { @@ -33240,7 +33495,7 @@ } }, "410": { - "description": "Call ended โ€” the pod that owned its media is gone (pod_lost)", + "description": "Call ended \u2014 the pod that owned its media is gone (pod_lost)", "content": { "application/json": { "schema": { @@ -33304,7 +33559,7 @@ } }, "503": { - "description": "Call unavailable โ€” the owning pod is at capacity or draining, or call authority cannot be verified", + "description": "Call unavailable \u2014 the owning pod is at capacity or draining, or call authority cannot be verified", "content": { "application/json": { "schema": { @@ -33406,7 +33661,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -33621,7 +33876,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -33843,7 +34098,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -34067,7 +34322,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -34291,7 +34546,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -34515,7 +34770,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -34737,7 +34992,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -34961,7 +35216,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -35194,7 +35449,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -35427,7 +35682,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -35651,7 +35906,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -35875,7 +36130,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -36099,7 +36354,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -36332,7 +36587,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -36565,7 +36820,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -36798,7 +37053,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -37031,7 +37286,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -37264,7 +37519,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -37497,7 +37752,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -37730,7 +37985,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -37963,7 +38218,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -38196,7 +38451,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -41794,7 +42049,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -42029,7 +42284,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -42244,7 +42499,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -42468,7 +42723,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -42692,7 +42947,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -42916,7 +43171,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -43140,7 +43395,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -43364,7 +43619,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -43588,7 +43843,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -43812,7 +44067,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -44027,7 +44282,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -44242,7 +44497,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -44457,7 +44712,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -44680,7 +44935,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -44905,7 +45160,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -45130,7 +45385,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -45345,7 +45600,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -45568,7 +45823,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -45793,7 +46048,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -46006,7 +46261,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -46242,7 +46497,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -46466,7 +46721,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -46730,7 +46985,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -46975,7 +47230,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -47200,7 +47455,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -47434,7 +47689,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -47698,7 +47953,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -47920,7 +48175,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -48145,7 +48400,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -48379,7 +48634,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -48613,7 +48868,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -48887,7 +49142,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -49142,7 +49397,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -49415,7 +49670,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -49679,7 +49934,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -49901,7 +50156,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -50126,7 +50381,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -50360,7 +50615,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -50594,7 +50849,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -50877,7 +51132,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -51101,7 +51356,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -51316,7 +51571,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -51538,7 +51793,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -51762,7 +52017,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -51984,7 +52239,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -52261,7 +52516,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -52516,7 +52771,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -52749,7 +53004,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -53002,7 +53257,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -53273,7 +53528,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -53497,7 +53752,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -53721,7 +53976,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -53945,7 +54200,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -54169,7 +54424,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -54414,7 +54669,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -54627,7 +54882,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -54850,7 +55105,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -55094,7 +55349,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -55342,7 +55597,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -55608,7 +55863,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -55884,7 +56139,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -56147,7 +56402,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -56426,7 +56681,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -56663,7 +56918,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -56939,7 +57194,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -57161,7 +57416,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -57406,7 +57661,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -57635,7 +57890,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -57880,7 +58135,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -58094,7 +58349,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -58309,7 +58564,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -58533,7 +58788,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -58757,7 +59012,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -58981,7 +59236,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -59194,7 +59449,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -59409,7 +59664,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -59633,7 +59888,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -59877,7 +60132,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -60104,7 +60359,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -60326,7 +60581,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -60558,7 +60813,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -60811,7 +61066,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -61046,7 +61301,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -61276,7 +61531,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -61513,7 +61768,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -61722,7 +61977,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -61934,7 +62189,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -62153,7 +62408,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -62363,7 +62618,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -62582,7 +62837,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -62797,7 +63052,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -63012,7 +63267,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -63228,7 +63483,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -63525,7 +63780,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -63899,7 +64154,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -64201,7 +64456,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -64593,7 +64848,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -64918,7 +65173,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -65137,7 +65392,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -65357,7 +65612,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -65584,7 +65839,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -65802,7 +66057,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -66031,7 +66286,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -66261,7 +66516,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -66472,7 +66727,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -66703,7 +66958,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -66969,7 +67224,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -67199,7 +67454,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -67273,7 +67528,7 @@ "error": { "type": "permission_error", "code": "campaigns_not_entitled", - "message": "This numberโ€™s plan does not include Campaigns.", + "message": "This number\u2019s plan does not include Campaigns.", "param": null, "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" }, @@ -67514,7 +67769,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -67755,7 +68010,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -67829,7 +68084,7 @@ "error": { "type": "permission_error", "code": "campaigns_not_entitled", - "message": "This numberโ€™s plan does not include Campaigns.", + "message": "This number\u2019s plan does not include Campaigns.", "param": null, "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" }, @@ -68098,7 +68353,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -68377,7 +68632,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -68656,7 +68911,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -68945,7 +69200,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -69221,7 +69476,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -69479,7 +69734,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -69706,7 +69961,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -69949,7 +70204,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -70171,7 +70426,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -70383,7 +70638,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -70645,7 +70900,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -73066,7 +73321,7 @@ } }, "400": { - "description": "Bad request โ€” request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -73264,7 +73519,7 @@ "description": "**Required scope:** `quicklink:manage`" } }, - "/messaging/testing/{projectId}/history-fixtures": { + "/messaging/projects/{projectId}/test-history-fixtures": { "post": { "operationId": "createTestingHistoryFixture", "tags": [ @@ -73388,7 +73643,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73420,7 +73675,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73452,7 +73707,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73484,7 +73739,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73507,7 +73762,7 @@ "description": "**Required scope:** `sandbox:write`" } }, - "/messaging/testing/{projectId}/events/fixtures": { + "/messaging/test-events/fixtures": { "get": { "operationId": "listTestEventFixtures", "tags": [ @@ -73520,17 +73775,6 @@ ], "summary": "List test event fixtures", "x-required-scope": "sandbox:read", - "parameters": [ - { - "schema": { - "type": "string", - "format": "uuid" - }, - "required": true, - "name": "projectId", - "in": "path" - } - ], "responses": { "200": { "description": "Available fixtures and the overrides each accepts", @@ -73554,7 +73798,7 @@ } }, "403": { - "description": "Test numbers access required, or a client token was used", + "description": "Read scope required; client tokens are not permitted", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" @@ -73569,7 +73813,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73584,70 +73828,6 @@ } } } - }, - "404": { - "description": "Project not found", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/X-RateLimit-Limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/X-RateLimit-Remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/X-RateLimit-Reset" - } - }, - "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, - "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" - } - } - } - }, - "502": { - "description": "Test access could not be verified", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/X-RateLimit-Limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/X-RateLimit-Remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/X-RateLimit-Reset" - } - }, - "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, - "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" - } - } - } } }, "x-mint": { @@ -73656,7 +73836,7 @@ "description": "**Required scope:** `sandbox:read`" } }, - "/messaging/testing/{projectId}/events": { + "/messaging/{session}/test-events": { "post": { "operationId": "triggerTestEvent", "tags": [ @@ -73673,10 +73853,12 @@ { "schema": { "type": "string", - "format": "uuid" + "minLength": 1, + "maxLength": 128, + "description": "Test Number ID." }, "required": true, - "name": "projectId", + "name": "session", "in": "path" }, { @@ -73695,7 +73877,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TriggerTestEventRequest" + "$ref": "#/components/schemas/TestDropinTriggerTestEventRequest" } } } @@ -73723,7 +73905,7 @@ } }, "400": { - "description": "Invalid fixture, override, or real session", + "description": "Invalid fixture or override", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" @@ -73738,7 +73920,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73770,7 +73952,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73787,7 +73969,7 @@ } }, "404": { - "description": "Project or Test number not found", + "description": "Resource not found", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" @@ -73802,7 +73984,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73834,7 +74016,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73866,7 +74048,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/components/schemas/TestDropinPublicError" }, "example": { "error": { @@ -73884,9 +74066,591 @@ } }, "x-mint": { - "content": "sandbox:write" + "content": "Linked Device API sandbox:write" }, - "description": "**Required scope:** `sandbox:write`" + "x-protocols": [ + "Linked Device API" + ], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:write`" + } + }, + "/messaging/{session}/phone": { + "get": { + "operationId": "getTestingPhone", + "tags": [ + "Testing" + ], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Get a Test number's phone", + "x-required-scope": "sandbox:read", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "The Test number's session ID." + }, + "required": true, + "name": "session", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The simulated phone and its linked companions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestingPhoneState" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "403": { + "description": "Test numbers access required, or a client token was used", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Resource not found", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "502": { + "description": "The simulated phone could not be read. Retry later.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } + } + }, + "x-mint": { + "content": "Linked Device API sandbox:read" + }, + "x-protocols": [ + "Linked Device API" + ], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:read`" + } + }, + "/messaging/{session}/phone/messages": { + "post": { + "operationId": "sendTestingPhoneMessage", + "tags": [ + "Testing" + ], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Send a message from a Test number's phone", + "x-required-scope": "sandbox:write", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "The Test number's session ID." + }, + "required": true, + "name": "session", + "in": "path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestingPhoneMessageRequest" + } + } + } + }, + "responses": { + "202": { + "description": "The phone sent the message", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestingPhoneMessage" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "Invalid recipient, or the phone is offline", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } + }, + "403": { + "description": "Test numbers access required, or a client token was used", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Resource not found", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "429": { + "description": "Testing rate limit reached", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "rate_limit_error", + "code": "rate_limit_exceeded", + "message": "The request limit was reached. Follow Retry-After when supplied.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#rate-limit-exceeded" + } + } + } + }, + "502": { + "description": "The phone could not send the message. Retry later.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } + } + }, + "x-mint": { + "content": "Linked Device API sandbox:write" + }, + "x-protocols": [ + "Linked Device API" + ], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:write`" + } + }, + "/messaging/{session}/phone/devices/{deviceId}/unlink": { + "post": { + "operationId": "unlinkTestingPhoneDevice", + "tags": [ + "Testing" + ], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Unlink a companion from a Test number's phone", + "x-required-scope": "sandbox:write", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "The Test number's session ID." + }, + "required": true, + "name": "session", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 99, + "description": "The companion's device ID, as listed by the phone." + }, + "required": true, + "name": "deviceId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The companion was unlinked and logged out", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestingPhoneUnlink" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "The phone is offline", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } + }, + "403": { + "description": "Test numbers access required, or a client token was used", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Project or companion not found", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "502": { + "description": "The phone could not unlink the companion. Retry later.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestDropinPublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } + } + }, + "x-mint": { + "content": "Linked Device API sandbox:write" + }, + "x-protocols": [ + "Linked Device API" + ], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:write`" } } }, diff --git a/contracts/source.json b/contracts/source.json index d6a23c2f..c0d46b17 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -3,12 +3,12 @@ "commit": "171a9683ff45d11a4b5d3917dc428ad2722b0d99", "branch": "dev", "published": true, - "note": "Full Messaging and Platform snapshots pin the merged API dev revision. SDK publication and API deployment remain separate gates.", + "note": "The global source commit remains the merged API baseline. The Messaging snapshot also contains the six Number-scoped Test route overrides pinned in test-number-dropin-routes.json at 79e62733b1b3d523618232a3794e26df64b7f486. Their sourceCommit entries remain explicit in coverage.json. This mixed snapshot is not a new published API contract or staging acceptance receipt; package release requires full source reconciliation and matching environment acceptance.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", "snapshotPath": "contracts/openapi.messaging.json", - "sha256": "7f6ddab25a673e45da7a209292ddfd7354acccf30c27e2fa651113e321b66d3d" + "sha256": "fa0d463c21327de20ee04610340a0b2cbc299d4b31d5d3a0f05a8ba60bfff261" }, "platform": { "sourcePath": "apps/api/docs/openapi.management.json", diff --git a/contracts/test-number-dropin-routes.json b/contracts/test-number-dropin-routes.json new file mode 100644 index 00000000..68d9bc92 --- /dev/null +++ b/contracts/test-number-dropin-routes.json @@ -0,0 +1,1590 @@ +{ + "repository": "polymorfa/polymorfa", + "commit": "79e62733b1b3d523618232a3794e26df64b7f486", + "sourcePath": "apps/api/docs/openapi.json", + "sourceSha256": "dcbd21ba3dafe0c30e52c260ccef759eaca1c0a11d6cc092f2a3ef68bf50f4d3", + "operations": { + "getTestingPhone": { + "method": "GET", + "path": "/messaging/{session}/phone", + "operation": { + "operationId": "getTestingPhone", + "tags": ["Testing"], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Get a Test number's phone", + "x-required-scope": "sandbox:read", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "The Test number's session ID." + }, + "required": true, + "name": "session", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The simulated phone and its linked companions", + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TestingPhoneState" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "403": { + "description": "Test numbers access required, or a client token was used", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Resource not found", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "502": { + "description": "The simulated phone could not be read. Retry later.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } + } + }, + "x-mint": { + "content": "Linked Device API sandbox:read" + }, + "x-protocols": ["Linked Device API"], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:read`" + } + }, + "sendTestingPhoneMessage": { + "method": "POST", + "path": "/messaging/{session}/phone/messages", + "operation": { + "operationId": "sendTestingPhoneMessage", + "tags": ["Testing"], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Send a message from a Test number's phone", + "x-required-scope": "sandbox:write", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "The Test number's session ID." + }, + "required": true, + "name": "session", + "in": "path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TestingPhoneMessageRequest" + } + } + } + }, + "responses": { + "202": { + "description": "The phone sent the message", + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TestingPhoneMessage" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "Invalid recipient, or the phone is offline", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } + }, + "403": { + "description": "Test numbers access required, or a client token was used", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Resource not found", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "429": { + "description": "Testing rate limit reached", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "rate_limit_error", + "code": "rate_limit_exceeded", + "message": "The request limit was reached. Follow Retry-After when supplied.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#rate-limit-exceeded" + } + } + } + }, + "502": { + "description": "The phone could not send the message. Retry later.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } + } + }, + "x-mint": { + "content": "Linked Device API sandbox:write" + }, + "x-protocols": ["Linked Device API"], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:write`" + } + }, + "unlinkTestingPhoneDevice": { + "method": "POST", + "path": "/messaging/{session}/phone/devices/{deviceId}/unlink", + "operation": { + "operationId": "unlinkTestingPhoneDevice", + "tags": ["Testing"], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Unlink a companion from a Test number's phone", + "x-required-scope": "sandbox:write", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "The Test number's session ID." + }, + "required": true, + "name": "session", + "in": "path" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 99, + "description": "The companion's device ID, as listed by the phone." + }, + "required": true, + "name": "deviceId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The companion was unlinked and logged out", + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TestingPhoneUnlink" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "The phone is offline", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } + }, + "403": { + "description": "Test numbers access required, or a client token was used", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Project or companion not found", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "502": { + "description": "The phone could not unlink the companion. Retry later.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } + } + }, + "x-mint": { + "content": "Linked Device API sandbox:write" + }, + "x-protocols": ["Linked Device API"], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:write`" + } + }, + "triggerTestEvent": { + "method": "POST", + "path": "/messaging/{session}/test-events", + "operation": { + "operationId": "triggerTestEvent", + "tags": ["Testing"], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Trigger a test event for a Test number", + "x-required-scope": "sandbox:write", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Test Number ID." + }, + "required": true, + "name": "session", + "in": "path" + }, + { + "schema": { + "type": "string", + "pattern": "^[\\x21-\\x7e]{1,255}$", + "description": "Optional. Repeating a request with the same key and body reuses the same event ID, so a retry after a failed or uncertain response never creates a second event or duplicate webhook deliveries." + }, + "required": false, + "name": "idempotency-key", + "in": "header" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TriggerTestEventRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Test event accepted for delivery", + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TriggerTestEventResponse" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "Invalid fixture or override", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } + }, + "403": { + "description": "Test numbers access required, or a client token was used", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Resource not found", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "429": { + "description": "Test event rate limit reached", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "rate_limit_error", + "code": "rate_limit_exceeded", + "message": "The request limit was reached. Follow Retry-After when supplied.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#rate-limit-exceeded" + } + } + } + }, + "502": { + "description": "Test event delivery unavailable. Retry with the same Idempotency-Key to avoid a second event.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } + } + }, + "x-mint": { + "content": "Linked Device API sandbox:write" + }, + "x-protocols": ["Linked Device API"], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:write`" + } + }, + "listTestEventFixtures": { + "method": "GET", + "path": "/messaging/test-events/fixtures", + "operation": { + "operationId": "listTestEventFixtures", + "tags": ["Testing"], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "List test event fixtures", + "x-required-scope": "sandbox:read", + "responses": { + "200": { + "description": "Available fixtures and the overrides each accepts", + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TestEventFixtureList" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "403": { + "description": "Read scope required; client tokens are not permitted", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + } + }, + "x-mint": { + "content": "sandbox:read" + }, + "description": "**Required scope:** `sandbox:read`" + } + }, + "createTestingHistoryFixture": { + "method": "POST", + "path": "/messaging/projects/{projectId}/test-history-fixtures", + "operation": { + "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"] + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "Invalid fixture", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" + } + } + } + }, + "403": { + "description": "Test access required", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Project not found", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "502": { + "description": "Test runtime unavailable", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } + } + }, + "x-mint": { + "content": "sandbox:write" + }, + "description": "**Required scope:** `sandbox:write`" + } + } + }, + "schemas": { + "TestingPhoneState": { + "type": "object", + "properties": { + "session": { + "type": "string" + }, + "phone": { + "type": "string", + "description": "The Test number, in E.164 format." + }, + "online": { + "type": "boolean", + "description": "True while the simulated phone is running." + }, + "devices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "deviceId": { + "type": "integer" + } + }, + "required": ["deviceId"] + }, + "description": "Companions linked to the phone, such as Polymorfa's own session and Inspector." + } + }, + "required": ["session", "phone", "online", "devices"] + }, + "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", + "cloud_template_authority_restricted", + "gate_limit_reached", + "method_not_allowed", + "permission_denied", + "missing_scope", + "resource_not_found", + "hms_not_enabled", + "media_fetch_failed", + "resource_gone", + "feature_unavailable", + "stream_cursor_invalid", + "stream_cursor_expired", + "stream_connection_limit_reached", + "state_conflict", + "team_deletion_scheduled", + "hybrid_routing_paused", + "hybrid_choice_required", + "hybrid_transition_ineligible", + "hybrid_automation_unavailable", + "hybrid_group_linked_devices_unavailable", + "hybrid_transport_unavailable", + "hybrid_target_reference_unavailable", + "send_outcome_unknown", + "idempotency_conflict", + "idempotency_in_progress", + "idempotency_completed", + "idempotency_outcome_unknown", + "call_claimed", + "call_not_ringing", + "connection_limit", + "unsupported_for_connection", + "call_permission_required", + "invalid_sip_trunk", + "calls_disabled", + "call_recipient_opted_out", + "call_destination_blocked", + "call_permission_request_limited", + "call_permission_granted", + "order_status_transition_invalid", + "order_cancellation_failed", + "call_opt_out_limit", + "number_restricted", + "sip_trunk_in_use", + "sip_trunk_revision_conflict", + "sip_trunk_limit", + "sip_unavailable", + "voice_not_enabled", + "provider_credential_invalid", + "provider_unavailable", + "asset_not_ready", + "voice_asset_in_use", + "voice_asset_revision_conflict", + "voice_unavailable", + "rate_limit_exceeded", + "internal_error", + "operation_not_supported", + "upstream_failure", + "service_unavailable", + "credential_verification_unavailable", + "session_not_ready", + "command_dispatch_failed", + "result_unknown", + "parent_bsuid_not_supported", + "recipient_not_on_whatsapp", + "conversation_window_closed", + "template_not_approved", + "media_too_large", + "whatsapp_rate_limited", + "new_chat_limit_reached", + "marketing_disabled_on_cloud_api", + "whatsapp_account_restricted", + "whatsapp_authorization_required", + "whatsapp_payment_method_required", + "whatsapp_number_not_registered", + "whatsapp_groups_ineligible", + "whatsapp_group_creation_paused", + "whatsapp_group_limit_reached", + "whatsapp_group_full", + "whatsapp_group_suspended", + "whatsapp_group_has_no_participants", + "bansafe_suspended", + "bansafe_org_suspended", + "bansafe_cold_blocked", + "bansafe_cold_held", + "bansafe_throttled", + "bansafe_daily_allowance_reached", + "bansafe_accounting_unavailable", + "bansafe_send_outcome_unknown", + "campaigns_not_entitled", + "campaign_throughput_capped", + "campaign_approval_required", + "campaign_approval_stale", + "campaign_variables_missing" + ] + }, + "message": { + "type": "string" + }, + "param": { + "type": "string", + "nullable": true + }, + "request_id": { + "type": "string", + "description": "Matches the X-Request-Id response header. Include it when you contact support." + }, + "request_log_url": { + "type": "string", + "format": "uri", + "description": "Console page that shows this request. Returned only to organization and project credentials whose request was logged." + } + }, + "required": ["type", "code", "message", "param", "request_id"] + }, + "data": { + "type": "string", + "nullable": true, + "enum": [null], + "description": "Always null for a failed request." + }, + "docs": { + "type": "string", + "format": "uri" + } + }, + "required": ["error", "data", "docs"] + }, + "TestingPhoneMessageRequest": { + "type": "object", + "properties": { + "to": { + "type": "string", + "pattern": "^\\+[1-9]\\d{7,14}$", + "description": "Another Test number in the same project, in E.164 format." + }, + "text": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + }, + "required": ["to", "text"], + "additionalProperties": false + }, + "TestingPhoneMessage": { + "type": "object", + "properties": { + "session": { + "type": "string" + }, + "to": { + "type": "string" + }, + "messageId": { + "type": "string", + "nullable": true + } + }, + "required": ["session", "to", "messageId"] + }, + "TestingPhoneUnlink": { + "type": "object", + "properties": { + "session": { + "type": "string" + }, + "deviceId": { + "type": "integer" + }, + "unlinked": { + "type": "boolean", + "enum": [true] + } + }, + "required": ["session", "deviceId", "unlinked"] + }, + "TriggerTestEventRequest": { + "type": "object", + "properties": { + "event": { + "type": "string", + "enum": [ + "message.received", + "message.ack", + "message.failed", + "call.received", + "call.missed", + "call.ended", + "session.status", + "session.restriction_updated", + "template.status" + ], + "description": "Fixture name. Each name is the event type it produces." + }, + "overrides": { + "$ref": "#/schemas/TestEventOverrides" + }, + "fromNumber": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "message.received only. Send a simulated text message from this other connected Test number in the same project instead of generating an event." + } + }, + "required": ["event"], + "additionalProperties": false + }, + "TestEventOverrides": { + "type": "object", + "properties": { + "text": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "from": { + "type": "string", + "pattern": "^\\+[1-9]\\d{7,14}$" + }, + "pushName": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "mediaType": { + "type": "string", + "enum": ["image", "video", "audio", "document", "sticker"] + }, + "caption": { + "type": "string", + "minLength": 1, + "maxLength": 1024 + }, + "ackStatus": { + "type": "string", + "enum": ["delivered", "read", "played", "error"] + }, + "messageId": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}$" + }, + "failureReason": { + "type": "string", + "enum": [ + "invalid_recipient", + "session_not_connected", + "ack_timeout", + "send_failed", + "blocked_by_safety" + ] + }, + "video": { + "type": "boolean" + }, + "durationSeconds": { + "type": "integer", + "minimum": 0, + "maximum": 86400 + }, + "callEndReason": { + "type": "string", + "enum": [ + "user_hangup", + "timeout", + "lost_connection", + "rejected", + "call_restricted" + ] + }, + "restrictionActive": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": ["CONNECTING", "CONNECTED", "DISCONNECTED"] + }, + "statusReason": { + "type": "string", + "enum": [ + "SCAN_QR", + "AUTO_RECONNECT", + "FAILED", + "MANUAL_STOP", + "QR_TIMEOUT", + "LOGGED_OUT", + "TEMPORARY_BAN", + "STREAM_ERROR" + ] + }, + "templateName": { + "type": "string", + "pattern": "^[a-z0-9_]{1,512}$" + }, + "templateStatus": { + "type": "string", + "enum": ["APPROVED", "REJECTED"] + }, + "reason": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + }, + "additionalProperties": false + }, + "TriggerTestEventResponse": { + "type": "object", + "properties": { + "event": { + "type": "string", + "enum": [ + "message.received", + "message.ack", + "message.failed", + "call.received", + "call.missed", + "call.ended", + "session.status", + "session.restriction_updated", + "template.status" + ] + }, + "session": { + "type": "string" + }, + "delivery": { + "type": "string", + "enum": ["generated", "simulated"] + }, + "eventId": { + "type": "string", + "nullable": true, + "format": "uuid" + }, + "source": { + "type": "string", + "enum": ["test", "runtime"] + } + }, + "required": ["event", "session", "delivery", "eventId", "source"] + }, + "TestEventFixtureList": { + "type": "object", + "properties": { + "fixtures": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "enum": [ + "message.received", + "message.ack", + "message.failed", + "call.received", + "call.missed", + "call.ended", + "session.status", + "session.restriction_updated", + "template.status" + ] + }, + "description": { + "type": "string" + }, + "overrides": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["name", "description", "overrides"] + } + } + }, + "required": ["fixtures"] + } + }, + "components": { + "headers": { + "X-RateLimit-Limit": { + "description": "Team request quota per rolling minute, when the per-team request limiter applies.", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the per-team quota; zero when this limiter rejects the request.", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp in seconds when the oldest counted request leaves the rolling window, when the per-team request limiter applies. Follow Retry-After on a rejected request.", + "schema": { + "type": "integer", + "minimum": 0 + } + } + } + } +} diff --git a/contracts/testing-events.json b/contracts/testing-events.json index 270ee3d0..ecd8a005 100644 --- a/contracts/testing-events.json +++ b/contracts/testing-events.json @@ -1,56 +1,324 @@ { "repository": "polymorfa/polymorfa", - "commit": "6839976e7ad54e044c4d789fd296edc44772a907", + "commit": "79e62733b1b3d523618232a3794e26df64b7f486", "sourcePath": "apps/api/docs/openapi.json", - "sourceSha256": "79543bf719017598c3e2497004d761056e919b3200abdaae1560893542816cc7", - "schemas": { - "TestEventFixtureList": { - "type": "object", - "properties": { - "fixtures": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "enum": [ - "message.received", - "message.ack", - "message.failed", - "call.received", - "call.missed", - "call.ended", - "session.status", - "session.restriction_updated", - "template.status" - ] + "sourceSha256": "dcbd21ba3dafe0c30e52c260ccef759eaca1c0a11d6cc092f2a3ef68bf50f4d3", + "operations": { + "triggerTestEvent": { + "method": "POST", + "path": "/messaging/{session}/test-events", + "operation": { + "operationId": "triggerTestEvent", + "tags": ["Testing"], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Trigger a test event for a Test number", + "x-required-scope": "sandbox:write", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Test Number ID." + }, + "required": true, + "name": "session", + "in": "path" + }, + { + "schema": { + "type": "string", + "pattern": "^[\\x21-\\x7e]{1,255}$", + "description": "Optional. Repeating a request with the same key and body reuses the same event ID, so a retry after a failed or uncertain response never creates a second event or duplicate webhook deliveries." + }, + "required": false, + "name": "idempotency-key", + "in": "header" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TriggerTestEventRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Test event accepted for delivery", + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TriggerTestEventResponse" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" }, - "description": { - "type": "string" + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" }, - "overrides": { - "type": "array", - "items": { - "type": "string" + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "400": { + "description": "Invalid fixture or override", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "invalid_parameter", + "message": "Correct the invalid request field.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" } } + } + }, + "403": { + "description": "Test numbers access required, or a client token was used", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } }, - "required": ["name", "description", "overrides"] + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + }, + "404": { + "description": "Resource not found", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "invalid_request_error", + "code": "resource_not_found", + "message": "The resource is unavailable to this caller.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#resource-not-found" + } + } + } + }, + "429": { + "description": "Test event rate limit reached", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "rate_limit_error", + "code": "rate_limit_exceeded", + "message": "The request limit was reached. Follow Retry-After when supplied.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#rate-limit-exceeded" + } + } + } + }, + "502": { + "description": "Test event delivery unavailable. Retry with the same Idempotency-Key to avoid a second event.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "upstream_error", + "code": "upstream_failure", + "message": "The messaging provider could not complete the request.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#upstream-failure" + } + } + } } - } - }, - "required": ["fixtures"] + }, + "x-mint": { + "content": "Linked Device API sandbox:write" + }, + "x-protocols": ["Linked Device API"], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:write`" + } }, + "listTestEventFixtures": { + "method": "GET", + "path": "/messaging/test-events/fixtures", + "operation": { + "operationId": "listTestEventFixtures", + "tags": ["Testing"], + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "List test event fixtures", + "x-required-scope": "sandbox:read", + "responses": { + "200": { + "description": "Available fixtures and the overrides each accepts", + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/TestEventFixtureList" + } + } + }, + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + } + }, + "403": { + "description": "Read scope required; client tokens are not permitted", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/schemas/PublicError" + }, + "example": { + "error": { + "type": "permission_error", + "code": "permission_denied", + "message": "The credential does not have access to this resource.", + "param": null, + "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" + }, + "data": null, + "docs": "https://docs.polymorfa.com/api/errors#permission-denied" + } + } + } + } + }, + "x-mint": { + "content": "sandbox:read" + }, + "description": "**Required scope:** `sandbox:read`" + } + } + }, + "schemas": { "TriggerTestEventRequest": { "type": "object", "properties": { - "session": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "description": "Test number session name in the project." - }, "event": { "type": "string", "enum": [ @@ -69,52 +337,16 @@ "overrides": { "$ref": "#/schemas/TestEventOverrides" }, - "fromSession": { + "fromNumber": { "type": "string", "minLength": 1, "maxLength": 128, "description": "message.received only. Send a simulated text message from this other connected Test number in the same project instead of generating an event." } }, - "required": ["session", "event"], + "required": ["event"], "additionalProperties": false }, - "TriggerTestEventResponse": { - "type": "object", - "properties": { - "event": { - "type": "string", - "enum": [ - "message.received", - "message.ack", - "message.failed", - "call.received", - "call.missed", - "call.ended", - "session.status", - "session.restriction_updated", - "template.status" - ] - }, - "session": { - "type": "string" - }, - "delivery": { - "type": "string", - "enum": ["generated", "simulated"] - }, - "eventId": { - "type": "string", - "nullable": true, - "format": "uuid" - }, - "source": { - "type": "string", - "enum": ["test", "runtime"] - } - }, - "required": ["event", "session", "delivery", "eventId", "source"] - }, "TestEventOverrides": { "type": "object", "properties": { @@ -212,6 +444,256 @@ } }, "additionalProperties": false + }, + "TriggerTestEventResponse": { + "type": "object", + "properties": { + "event": { + "type": "string", + "enum": [ + "message.received", + "message.ack", + "message.failed", + "call.received", + "call.missed", + "call.ended", + "session.status", + "session.restriction_updated", + "template.status" + ] + }, + "session": { + "type": "string" + }, + "delivery": { + "type": "string", + "enum": ["generated", "simulated"] + }, + "eventId": { + "type": "string", + "nullable": true, + "format": "uuid" + }, + "source": { + "type": "string", + "enum": ["test", "runtime"] + } + }, + "required": ["event", "session", "delivery", "eventId", "source"] + }, + "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", + "cloud_template_authority_restricted", + "gate_limit_reached", + "method_not_allowed", + "permission_denied", + "missing_scope", + "resource_not_found", + "hms_not_enabled", + "media_fetch_failed", + "resource_gone", + "feature_unavailable", + "stream_cursor_invalid", + "stream_cursor_expired", + "stream_connection_limit_reached", + "state_conflict", + "team_deletion_scheduled", + "hybrid_routing_paused", + "hybrid_choice_required", + "hybrid_transition_ineligible", + "hybrid_automation_unavailable", + "hybrid_group_linked_devices_unavailable", + "hybrid_transport_unavailable", + "hybrid_target_reference_unavailable", + "send_outcome_unknown", + "idempotency_conflict", + "idempotency_in_progress", + "idempotency_completed", + "idempotency_outcome_unknown", + "call_claimed", + "call_not_ringing", + "connection_limit", + "unsupported_for_connection", + "call_permission_required", + "invalid_sip_trunk", + "calls_disabled", + "call_recipient_opted_out", + "call_destination_blocked", + "call_permission_request_limited", + "call_permission_granted", + "order_status_transition_invalid", + "order_cancellation_failed", + "call_opt_out_limit", + "number_restricted", + "sip_trunk_in_use", + "sip_trunk_revision_conflict", + "sip_trunk_limit", + "sip_unavailable", + "voice_not_enabled", + "provider_credential_invalid", + "provider_unavailable", + "asset_not_ready", + "voice_asset_in_use", + "voice_asset_revision_conflict", + "voice_unavailable", + "rate_limit_exceeded", + "internal_error", + "operation_not_supported", + "upstream_failure", + "service_unavailable", + "credential_verification_unavailable", + "session_not_ready", + "command_dispatch_failed", + "result_unknown", + "parent_bsuid_not_supported", + "recipient_not_on_whatsapp", + "conversation_window_closed", + "template_not_approved", + "media_too_large", + "whatsapp_rate_limited", + "new_chat_limit_reached", + "marketing_disabled_on_cloud_api", + "whatsapp_account_restricted", + "whatsapp_authorization_required", + "whatsapp_payment_method_required", + "whatsapp_number_not_registered", + "whatsapp_groups_ineligible", + "whatsapp_group_creation_paused", + "whatsapp_group_limit_reached", + "whatsapp_group_full", + "whatsapp_group_suspended", + "whatsapp_group_has_no_participants", + "bansafe_suspended", + "bansafe_org_suspended", + "bansafe_cold_blocked", + "bansafe_cold_held", + "bansafe_throttled", + "bansafe_daily_allowance_reached", + "bansafe_accounting_unavailable", + "bansafe_send_outcome_unknown", + "campaigns_not_entitled", + "campaign_throughput_capped", + "campaign_approval_required", + "campaign_approval_stale", + "campaign_variables_missing" + ] + }, + "message": { + "type": "string" + }, + "param": { + "type": "string", + "nullable": true + }, + "request_id": { + "type": "string", + "description": "Matches the X-Request-Id response header. Include it when you contact support." + }, + "request_log_url": { + "type": "string", + "format": "uri", + "description": "Console page that shows this request. Returned only to organization and project credentials whose request was logged." + } + }, + "required": ["type", "code", "message", "param", "request_id"] + }, + "data": { + "type": "string", + "nullable": true, + "enum": [null], + "description": "Always null for a failed request." + }, + "docs": { + "type": "string", + "format": "uri" + } + }, + "required": ["error", "data", "docs"] + }, + "TestEventFixtureList": { + "type": "object", + "properties": { + "fixtures": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "enum": [ + "message.received", + "message.ack", + "message.failed", + "call.received", + "call.missed", + "call.ended", + "session.status", + "session.restriction_updated", + "template.status" + ] + }, + "description": { + "type": "string" + }, + "overrides": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["name", "description", "overrides"] + } + } + }, + "required": ["fixtures"] + } + }, + "components": { + "headers": { + "X-RateLimit-Limit": { + "description": "Team request quota per rolling minute, when the per-team request limiter applies.", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the per-team quota; zero when this limiter rejects the request.", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp in seconds when the oldest counted request leaves the rolling window, when the per-team request limiter applies. Follow Retry-After on a rejected request.", + "schema": { + "type": "integer", + "minimum": 0 + } + } } } } diff --git a/contracts/testing-phone.json b/contracts/testing-phone.json index 81aaa5f7..fe336130 100644 --- a/contracts/testing-phone.json +++ b/contracts/testing-phone.json @@ -1,12 +1,12 @@ { "repository": "polymorfa/polymorfa", - "commit": "2f1510f901aca1d68398a6dc9dbf243348c672c0", + "commit": "79e62733b1b3d523618232a3794e26df64b7f486", "sourcePath": "apps/api/docs/openapi.json", - "sourceSha256": "ea165cf5da67985a8f3c944d4771c6e30a77bb3d081396895454db22b66d9221", + "sourceSha256": "dcbd21ba3dafe0c30e52c260ccef759eaca1c0a11d6cc092f2a3ef68bf50f4d3", "operations": { "getTestingPhone": { "method": "GET", - "path": "/messaging/testing/{projectId}/numbers/{session}/phone", + "path": "/messaging/{session}/phone", "operation": { "operationId": "getTestingPhone", "tags": ["Testing"], @@ -18,15 +18,6 @@ "summary": "Get a Test number's phone", "x-required-scope": "sandbox:read", "parameters": [ - { - "schema": { - "type": "string", - "format": "uuid" - }, - "required": true, - "name": "projectId", - "in": "path" - }, { "schema": { "type": "string", @@ -45,7 +36,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestingPhoneState" + "$ref": "#/schemas/TestingPhoneState" } } }, @@ -61,38 +52,6 @@ } } }, - "400": { - "description": "The session is not a Test number in the project", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/X-RateLimit-Limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/X-RateLimit-Remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/X-RateLimit-Reset" - } - }, - "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, - "request_id": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30" - }, - "data": null, - "docs": "https://docs.polymorfa.com/api/errors#invalid-parameter" - } - } - } - }, "403": { "description": "Test numbers access required, or a client token was used", "headers": { @@ -109,7 +68,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -126,7 +85,7 @@ } }, "404": { - "description": "Project not found", + "description": "Resource not found", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" @@ -141,7 +100,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -173,7 +132,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -191,14 +150,15 @@ } }, "x-mint": { - "content": "sandbox:read" + "content": "Linked Device API sandbox:read" }, - "description": "**Required scope:** `sandbox:read`" + "x-protocols": ["Linked Device API"], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:read`" } }, "sendTestingPhoneMessage": { "method": "POST", - "path": "/messaging/testing/{projectId}/numbers/{session}/phone/messages", + "path": "/messaging/{session}/phone/messages", "operation": { "operationId": "sendTestingPhoneMessage", "tags": ["Testing"], @@ -210,15 +170,6 @@ "summary": "Send a message from a Test number's phone", "x-required-scope": "sandbox:write", "parameters": [ - { - "schema": { - "type": "string", - "format": "uuid" - }, - "required": true, - "name": "projectId", - "in": "path" - }, { "schema": { "type": "string", @@ -236,7 +187,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestingPhoneMessageRequest" + "$ref": "#/schemas/TestingPhoneMessageRequest" } } } @@ -247,7 +198,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestingPhoneMessage" + "$ref": "#/schemas/TestingPhoneMessage" } } }, @@ -264,7 +215,7 @@ } }, "400": { - "description": "The session or recipient is not a Test number in the project, or the phone is offline", + "description": "Invalid recipient, or the phone is offline", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" @@ -279,7 +230,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -311,7 +262,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -328,7 +279,7 @@ } }, "404": { - "description": "Project not found", + "description": "Resource not found", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" @@ -343,7 +294,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -375,7 +326,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -407,7 +358,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -425,14 +376,15 @@ } }, "x-mint": { - "content": "sandbox:write" + "content": "Linked Device API sandbox:write" }, - "description": "**Required scope:** `sandbox:write`" + "x-protocols": ["Linked Device API"], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:write`" } }, "unlinkTestingPhoneDevice": { "method": "POST", - "path": "/messaging/testing/{projectId}/numbers/{session}/phone/devices/{deviceId}/unlink", + "path": "/messaging/{session}/phone/devices/{deviceId}/unlink", "operation": { "operationId": "unlinkTestingPhoneDevice", "tags": ["Testing"], @@ -444,15 +396,6 @@ "summary": "Unlink a companion from a Test number's phone", "x-required-scope": "sandbox:write", "parameters": [ - { - "schema": { - "type": "string", - "format": "uuid" - }, - "required": true, - "name": "projectId", - "in": "path" - }, { "schema": { "type": "string", @@ -482,7 +425,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestingPhoneUnlink" + "$ref": "#/schemas/TestingPhoneUnlink" } } }, @@ -499,7 +442,7 @@ } }, "400": { - "description": "The session is not a Test number in the project, or the phone is offline", + "description": "The phone is offline", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" @@ -514,7 +457,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -546,7 +489,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -578,7 +521,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -610,7 +553,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicError" + "$ref": "#/schemas/PublicError" }, "example": { "error": { @@ -628,9 +571,10 @@ } }, "x-mint": { - "content": "sandbox:write" + "content": "Linked Device API sandbox:write" }, - "description": "**Required scope:** `sandbox:write`" + "x-protocols": ["Linked Device API"], + "description": "**Protocols:** Linked Device API\n\n**Required scope:** `sandbox:write`" } } }, @@ -703,10 +647,12 @@ "stream_cursor_expired", "stream_connection_limit_reached", "state_conflict", + "team_deletion_scheduled", "hybrid_routing_paused", "hybrid_choice_required", "hybrid_transition_ineligible", "hybrid_automation_unavailable", + "hybrid_group_linked_devices_unavailable", "hybrid_transport_unavailable", "hybrid_target_reference_unavailable", "send_outcome_unknown", @@ -749,13 +695,18 @@ "session_not_ready", "command_dispatch_failed", "result_unknown", + "parent_bsuid_not_supported", "recipient_not_on_whatsapp", "conversation_window_closed", "template_not_approved", "media_too_large", "whatsapp_rate_limited", "new_chat_limit_reached", + "marketing_disabled_on_cloud_api", "whatsapp_account_restricted", + "whatsapp_authorization_required", + "whatsapp_payment_method_required", + "whatsapp_number_not_registered", "whatsapp_groups_ineligible", "whatsapp_group_creation_paused", "whatsapp_group_limit_reached", @@ -858,5 +809,30 @@ }, "required": ["session", "deviceId", "unlinked"] } + }, + "components": { + "headers": { + "X-RateLimit-Limit": { + "description": "Team request quota per rolling minute, when the per-team request limiter applies.", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the per-team quota; zero when this limiter rejects the request.", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp in seconds when the oldest counted request leaves the rolling window, when the per-team request limiter applies. Follow Retry-After on a rejected request.", + "schema": { + "type": "integer", + "minimum": 0 + } + } + } } } diff --git a/packages/typescript/README.md b/packages/typescript/README.md index ac1f42ff..7ab07fd8 100644 --- a/packages/typescript/README.md +++ b/packages/typescript/README.md @@ -2890,28 +2890,27 @@ project can trigger 30 test events per minute (`PolymorfaRateLimitError`). ```ts import { TEST_EVENT_FIXTURES } from "@polymorfa/sdk"; -const result = await messaging.testing.triggerEvent(projectId, { - session: "my-test-number", +const result = await messaging.testing.triggerEvent("my-test-number", { event: "message.received", // one of TEST_EVENT_FIXTURES overrides: { text: "hi", from: "+15550100001" }, }); console.log(result.data.eventId); // Rare events: failed delivery, ban warning, incoming call, template rejection. -await messaging.testing.triggerEvent(projectId, { - session: "my-test-number", +await messaging.testing.triggerEvent("my-test-number", { event: "template.status", overrides: { templateStatus: "REJECTED", reason: "INVALID_FORMAT" }, }); -const { data } = await messaging.testing.listEventFixtures(projectId); +const { data } = await messaging.testing.listEventFixtures(); ``` -Set `fromSession` on a `message.received` request to send a simulated text +Set `fromNumber` on a `message.received` request to send a simulated text from another connected Test number in the same project instead; the response has `delivery: "simulated"` and the event arrives as ordinary Test number activity. Both methods require an organization API key or project token with -`sandbox:write` (trigger) or `sandbox:read` (list) and Test numbers access. +`sandbox:write` (trigger) or `sandbox:read` (list). Triggering requires Test +numbers access; the static fixture catalog has no project or feature lookup. Use `session.restriction_updated` with `{ restrictionActive: false }` to test a restriction ending, or `true` to test one starting. The `call.ended` @@ -2930,26 +2929,25 @@ to see which companions are linked, to send a message as the phone, or to unlink a companion as a person would from Linked devices. ```ts -const { data: phone } = await messaging.testing.getPhone( - projectId, - "my-test-number", -); +const { data: phone } = await messaging.testing.getPhone("my-test-number"); console.log(phone.online, phone.devices); // [{ deviceId: 33 }, ...] // Sent over the simulated network to another connected Test number in the // same project. The sender's companions receive the sent copy. -await messaging.testing.sendPhoneMessage(projectId, "my-test-number", { +await messaging.testing.sendPhoneMessage("my-test-number", { to: "+15550100002", text: "hello from the phone", }); -await messaging.testing.unlinkPhoneDevice(projectId, "my-test-number", 74); +await messaging.testing.unlinkPhoneDevice("my-test-number", 74); ``` `getPhone` needs `sandbox:read`; `sendPhoneMessage` and `unlinkPhoneDevice` need `sandbox:write`. All three need an organization API key or project token -and Test numbers access. Real numbers, an offline phone and recipients outside -the project are refused with a `PolymorfaValidationError`. Phone messages share +and Test numbers access. Real Numbers return `404 resource_not_found` +(`PolymorfaNotFoundError`). An offline phone and invalid recipients return +`400` (`PolymorfaValidationError`); a Test key hides unknown, real, and +other-project recipients with `404 resource_not_found`. Phone messages share the 30-per-minute project limit with test events. Device 0 is the phone and cannot be unlinked. diff --git a/packages/typescript/src/messaging/onboarding.ts b/packages/typescript/src/messaging/onboarding.ts index 015d2746..14d38e06 100644 --- a/packages/typescript/src/messaging/onboarding.ts +++ b/packages/typescript/src/messaging/onboarding.ts @@ -65,7 +65,7 @@ export class TestingResource { assertServerCredential(this.credentialType); return this.transport.request({ method: "POST", - path: `/messaging/testing/${encodeURIComponent(projectId)}/history-fixtures`, + path: `/messaging/projects/${encodeURIComponent(projectId)}/test-history-fixtures`, body: input, ...options, }); @@ -73,22 +73,22 @@ export class TestingResource { /** * Fire a named, signed test event for a Test number. Generated events reach - * webhooks and event history with `source: "test"`. With `fromSession`, a + * webhooks and event history with `source: "test"`. With `fromNumber`, a * `message.received` request sends a simulated message from another Test - * number instead. Real numbers are refused with a 400 error. Pass + * number instead. Real numbers are refused with a 404 resource_not_found error. Pass * `options.idempotencyKey` to make retries safe: repeating the request with * the same key and body reuses the same event ID, and the SDK then retries * network and 5xx failures. */ triggerEvent( - projectId: string, + session: string, input: TriggerTestEventRequest, options: RequestOptions = {}, ): Promise> { assertServerCredential(this.credentialType); return this.transport.request({ method: "POST", - path: `/messaging/testing/${encodeURIComponent(projectId)}/events`, + path: `/messaging/${encodeURIComponent(session)}/test-events`, body: input, ...options, }); @@ -96,30 +96,28 @@ export class TestingResource { /** List test-event fixtures and the overrides each accepts. */ listEventFixtures( - projectId: string, options: RequestOptions = {}, ): Promise> { assertServerCredential(this.credentialType); return this.transport.request({ method: "GET", - path: `/messaging/testing/${encodeURIComponent(projectId)}/events/fixtures`, + path: `/messaging/test-events/fixtures`, ...options, }); } /** * Read a Test number's simulated phone: whether it is online and which - * companions are linked to it. Real numbers are refused with a 400 error. + * companions are linked to it. Real numbers are refused with a 404 resource_not_found error. */ getPhone( - projectId: string, session: string, options: RequestOptions = {}, ): Promise> { assertServerCredential(this.credentialType); return this.transport.request({ method: "GET", - path: phonePath(projectId, session), + path: phonePath(session), ...options, }); } @@ -131,7 +129,6 @@ export class TestingResource { * normal inbound message. Counts toward the Testing rate limit. */ sendPhoneMessage( - projectId: string, session: string, input: SendTestingPhoneMessageRequest, options: RequestOptions = {}, @@ -139,7 +136,7 @@ export class TestingResource { assertServerCredential(this.credentialType); return this.transport.request({ method: "POST", - path: `${phonePath(projectId, session)}/messages`, + path: `${phonePath(session)}/messages`, body: input, ...options, }); @@ -151,7 +148,6 @@ export class TestingResource { * cannot be unlinked. */ unlinkPhoneDevice( - projectId: string, session: string, deviceId: number, options: RequestOptions = {}, @@ -163,13 +159,13 @@ export class TestingResource { ); return this.transport.request({ method: "POST", - path: `${phonePath(projectId, session)}/devices/${deviceId}/unlink`, + path: `${phonePath(session)}/devices/${deviceId}/unlink`, ...options, }); } } -function phonePath(projectId: string, session: string): string { - return `/messaging/testing/${encodeURIComponent(projectId)}/numbers/${encodeURIComponent(session)}/phone`; +function phonePath(session: string): string { + return `/messaging/${encodeURIComponent(session)}/phone`; } function assertServerCredential(type: MessagingCredential["type"]): void { if (type === "clientToken") diff --git a/packages/typescript/src/messaging/testing-configuration.ts b/packages/typescript/src/messaging/testing-configuration.ts index 167da4bf..9234ef82 100644 --- a/packages/typescript/src/messaging/testing-configuration.ts +++ b/packages/typescript/src/messaging/testing-configuration.ts @@ -95,15 +95,13 @@ export interface TestEventOverrides { } export interface TriggerTestEventRequest { - /** Test number session name in the project. */ - session: string; event: TestEventFixture; overrides?: TestEventOverrides; /** * message.received only: deliver a real simulated message from this other * Test number in the same project instead of a generated event. */ - fromSession?: string; + fromNumber?: string; } export interface TriggerTestEventResponse { diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index fbb83a13..97f2e8f6 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -211,6 +211,31 @@ describe("reconciled coverage evidence", () => { } }); + it("keeps the six Test-route overrides bound to their unpublished source", () => { + const pin = JSON.parse( + readFileSync( + repositoryFile("contracts/test-number-dropin-routes.json"), + "utf8", + ), + ); + expect(pin.repository).toBe("polymorfa/polymorfa"); + expect(pin.commit).toBe("79e62733b1b3d523618232a3794e26df64b7f486"); + const overrides = ledger.operations.filter( + (operation) => operation.sourceCommit === pin.commit, + ); + expect(overrides.map((operation) => operation.operationId).sort()).toEqual([ + "createTestingHistoryFixture", + "getTestingPhone", + "listTestEventFixtures", + "sendTestingPhoneMessage", + "triggerTestEvent", + "unlinkTestingPhoneDevice", + ]); + expect( + overrides.every((operation) => operation.typescript.status === "covered"), + ).toBe(true); + }); + it("pins Analytics to the same full Platform source and snapshot hash", () => { const source = JSON.parse( readFileSync(repositoryFile("contracts/source.json"), "utf8"), diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index 87eb3b5b..749227aa 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -174,8 +174,8 @@ describe("coverage checker", () => { expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ sourceCommit: "171a9683ff45d11a4b5d3917dc428ad2722b0d99", - total: 579, - covered: 439, + total: 582, + covered: 442, partial: 0, missing: 0, excluded: 140, diff --git a/packages/typescript/test/onboarding.test.ts b/packages/typescript/test/onboarding.test.ts index 13b26a99..ebe48aec 100644 --- a/packages/typescript/test/onboarding.test.ts +++ b/packages/typescript/test/onboarding.test.ts @@ -39,7 +39,7 @@ it("uploads fixture content separately and advances an existing QuickLink withou 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", + "/messaging/projects/project-a/test-history-fixtures", ); expect(String(fetcher.mock.calls[1]?.[0])).toContain( "/messaging/cloud-api/embedded-signup", diff --git a/packages/typescript/test/testing-events.test.ts b/packages/typescript/test/testing-events.test.ts index 5a2a9704..6d99654e 100644 --- a/packages/typescript/test/testing-events.test.ts +++ b/packages/typescript/test/testing-events.test.ts @@ -3,7 +3,7 @@ import { afterEach, expect, it, vi } from "vitest"; import { MessagingClient, PolymorfaRateLimitError, - PolymorfaValidationError, + PolymorfaNotFoundError, TEST_EVENT_FIXTURES, type TriggerTestEventRequest, } from "../src/index.js"; @@ -50,16 +50,13 @@ it("triggers a typed test event with overrides", async () => { }; const { client: messaging, fetcher } = client(json(accepted, 202)); const input: TriggerTestEventRequest = { - session: "test-a", event: "message.received", overrides: { text: "hi", from: "+15550100001" }, }; - const result = await messaging.testing.triggerEvent("project a", input); + const result = await messaging.testing.triggerEvent("test-a", input); expect(result.data).toEqual(accepted); const [url, init] = fetcher.mock.calls[0]!; - expect(String(url)).toBe( - "https://api.example/messaging/testing/project%20a/events", - ); + expect(String(url)).toBe("https://api.example/messaging/test-a/test-events"); expect(init?.method).toBe("POST"); expect(JSON.parse(String(init?.body))).toEqual(input); }); @@ -71,12 +68,12 @@ it("lists fixtures", async () => { overrides: [], })); const { client: messaging, fetcher } = client(json({ fixtures })); - const result = await messaging.testing.listEventFixtures("project-a"); + const result = await messaging.testing.listEventFixtures(); expect(result.data.fixtures.map((f) => f.name)).toEqual([ ...TEST_EVENT_FIXTURES, ]); expect(String(fetcher.mock.calls[0]![0])).toBe( - "https://api.example/messaging/testing/project-a/events/fixtures", + "https://api.example/messaging/test-events/fixtures", ); expect(fetcher.mock.calls[0]![1]?.method).toBe("GET"); }); @@ -121,12 +118,11 @@ it.each([true, false])( }; const { client: messaging, fetcher } = client(json(accepted, 202)); const input: TriggerTestEventRequest = { - session: "test-a", event: "session.restriction_updated", overrides: { restrictionActive }, }; expect( - (await messaging.testing.triggerEvent("project-a", input)).data, + (await messaging.testing.triggerEvent("test-a", input)).data, ).toEqual(accepted); expect(JSON.parse(String(fetcher.mock.calls[0]![1]?.body))).toEqual(input); }, @@ -137,11 +133,10 @@ it("sends a call restriction outcome supported by the API fixture", async () => json({ event: "call.ended" }, 202), ); const input: TriggerTestEventRequest = { - session: "test-a", event: "call.ended", overrides: { callEndReason: "call_restricted" }, }; - await messaging.testing.triggerEvent("project-a", input); + await messaging.testing.triggerEvent("test-a", input); expect(JSON.parse(String(fetcher.mock.calls[0]![1]?.body))).toEqual(input); }); @@ -151,22 +146,20 @@ it("surfaces a real-session refusal as an invalid request", async () => { { error: { type: "invalid_request_error", - code: "invalid_parameter", - message: - "Test events are available only for Test numbers. This session is not a Test number in the project, or it does not exist.", + code: "resource_not_found", + message: "Resource not found", param: null, }, data: null, }, - 400, + 404, ), ); await expect( - messaging.testing.triggerEvent("project-a", { - session: "real", + messaging.testing.triggerEvent("real", { event: "call.received", }), - ).rejects.toBeInstanceOf(PolymorfaValidationError); + ).rejects.toBeInstanceOf(PolymorfaNotFoundError); }); it("rejects client tokens before transport", () => { @@ -177,12 +170,11 @@ it("rejects client tokens before transport", () => { fetch: fetcher, }); expect(() => - messaging.testing.triggerEvent("project-a", { - session: "test-a", + messaging.testing.triggerEvent("test-a", { event: "session.status", }), ).toThrow("requires an organization API key or project token"); - expect(() => messaging.testing.listEventFixtures("project-a")).toThrow( + expect(() => messaging.testing.listEventFixtures()).toThrow( "requires an organization API key or project token", ); expect(fetcher).not.toHaveBeenCalled(); @@ -203,8 +195,8 @@ it("surfaces the per-project rate limit", async () => { ); await expect( messaging.testing.triggerEvent( - "project-a", - { session: "test-a", event: "call.missed" }, + "test-a", + { event: "call.missed" }, { maxNetworkRetries: 0 }, ), ).rejects.toBeInstanceOf(PolymorfaRateLimitError); @@ -231,8 +223,8 @@ it("sends an optional Idempotency-Key and retries safely with it", async () => { fetch: fetcher, }); const result = await messaging.testing.triggerEvent( - "project-a", - { session: "test-a", event: "message.received" }, + "test-a", + { event: "message.received" }, { idempotencyKey: "trigger-1", maxNetworkRetries: 1 }, ); expect(result.data).toEqual(accepted); @@ -255,8 +247,7 @@ it("omits Idempotency-Key when none is given", async () => { 202, ), ); - await messaging.testing.triggerEvent("project-a", { - session: "test-a", + await messaging.testing.triggerEvent("test-a", { event: "session.status", }); const init = fetcher.mock.calls[0]![1]; @@ -266,12 +257,10 @@ it("omits Idempotency-Key when none is given", async () => { it("serializes restriction fixtures and restricted call endings", async () => { for (const input of [ { - session: "test-a", event: "session.restriction_updated", overrides: { restrictionActive: true }, }, { - session: "test-a", event: "call.ended", overrides: { callEndReason: "call_restricted" }, }, @@ -280,7 +269,7 @@ it("serializes restriction fixtures and restricted call endings", async () => { json( { event: input.event, - session: input.session, + session: "test-a", delivery: "generated", eventId: null, source: "test", @@ -288,7 +277,7 @@ it("serializes restriction fixtures and restricted call endings", async () => { 202, ), ); - await messaging.testing.triggerEvent("project-a", input); + await messaging.testing.triggerEvent("test-a", input); expect(JSON.parse(String(fetcher.mock.calls[0]![1]?.body))).toEqual(input); } }); diff --git a/packages/typescript/test/testing-phone.test.ts b/packages/typescript/test/testing-phone.test.ts index c0170c70..5576994c 100644 --- a/packages/typescript/test/testing-phone.test.ts +++ b/packages/typescript/test/testing-phone.test.ts @@ -4,6 +4,7 @@ import { MessagingClient, PolymorfaConfigurationError, PolymorfaValidationError, + PolymorfaNotFoundError, type TestingPhone, } from "../src/index.js"; import { ORGANIZATION_API_KEY } from "./support/credentials.js"; @@ -39,12 +40,10 @@ it("reads a Test number's phone", async () => { devices: [{ deviceId: 33 }, { deviceId: 74 }], }; const { client: messaging, fetcher } = client(json(phone)); - const result = await messaging.testing.getPhone("project-a", "test a"); + const result = await messaging.testing.getPhone("test a"); expect(result.data).toEqual(phone); const [url, init] = fetcher.mock.calls[0]!; - expect(String(url)).toBe( - "https://api.example/messaging/testing/project-a/numbers/test%20a/phone", - ); + expect(String(url)).toBe("https://api.example/messaging/test%20a/phone"); expect(init?.method).toBe("GET"); }); @@ -52,15 +51,11 @@ it("sends a message from the phone", async () => { const sent = { session: "test-a", to: "+15550100002", messageId: "3EB0AA" }; const { client: messaging, fetcher } = client(json(sent, 202)); const input = { to: "+15550100002", text: "hello" }; - const result = await messaging.testing.sendPhoneMessage( - "project-a", - "test-a", - input, - ); + const result = await messaging.testing.sendPhoneMessage("test-a", input); expect(result.data).toEqual(sent); const [url, init] = fetcher.mock.calls[0]!; expect(String(url)).toBe( - "https://api.example/messaging/testing/project-a/numbers/test-a/phone/messages", + "https://api.example/messaging/test-a/phone/messages", ); expect(init?.method).toBe("POST"); expect(JSON.parse(String(init?.body))).toEqual(input); @@ -69,30 +64,26 @@ it("sends a message from the phone", async () => { it("unlinks a companion", async () => { const unlinked = { session: "test-a", deviceId: 74, unlinked: true }; const { client: messaging, fetcher } = client(json(unlinked)); - const result = await messaging.testing.unlinkPhoneDevice( - "project-a", - "test-a", - 74, - ); + const result = await messaging.testing.unlinkPhoneDevice("test-a", 74); expect(result.data).toEqual(unlinked); const [url, init] = fetcher.mock.calls[0]!; expect(String(url)).toBe( - "https://api.example/messaging/testing/project-a/numbers/test-a/phone/devices/74/unlink", + "https://api.example/messaging/test-a/phone/devices/74/unlink", ); expect(init?.method).toBe("POST"); }); it.each([0, 100, 1.5])("refuses device ID %s before sending", (deviceId) => { const { client: messaging, fetcher } = client(json({})); - expect(() => - messaging.testing.unlinkPhoneDevice("project-a", "test-a", deviceId), - ).toThrow(PolymorfaValidationError); + expect(() => messaging.testing.unlinkPhoneDevice("test-a", deviceId)).toThrow( + PolymorfaValidationError, + ); expect(fetcher).not.toHaveBeenCalled(); }); it("refuses client tokens", () => { const { client: messaging, fetcher } = client(json({}), "clientToken"); - expect(() => messaging.testing.getPhone("project-a", "test-a")).toThrow( + expect(() => messaging.testing.getPhone("test-a")).toThrow( PolymorfaConfigurationError, ); expect(fetcher).not.toHaveBeenCalled(); @@ -110,7 +101,7 @@ it("surfaces an offline phone as a validation error", async () => { ), ); await expect( - messaging.testing.sendPhoneMessage("project-a", "test-a", { + messaging.testing.sendPhoneMessage("test-a", { to: "+15550100002", text: "hi", }), @@ -134,15 +125,15 @@ it("matches the pinned Testing phone contract", () => { const route = (operationId: string) => contract.operations[operationId]!; expect(route("getTestingPhone")).toMatchObject({ method: "GET", - path: "/messaging/testing/{projectId}/numbers/{session}/phone", + path: "/messaging/{session}/phone", }); expect(route("sendTestingPhoneMessage")).toMatchObject({ method: "POST", - path: "/messaging/testing/{projectId}/numbers/{session}/phone/messages", + path: "/messaging/{session}/phone/messages", }); expect(route("unlinkTestingPhoneDevice")).toMatchObject({ method: "POST", - path: "/messaging/testing/{projectId}/numbers/{session}/phone/devices/{deviceId}/unlink", + path: "/messaging/{session}/phone/devices/{deviceId}/unlink", }); const fields = (name: string) => Object.keys(contract.schemas[name]!.properties).sort(); @@ -161,3 +152,15 @@ it("matches the pinned Testing phone contract", () => { "unlinked", ]); }); + +it("hides a real Number with resource_not_found", async () => { + const { client: messaging } = client( + json( + { error: { code: "resource_not_found", message: "Resource not found" } }, + 404, + ), + ); + await expect( + messaging.testing.getPhone("real-number"), + ).rejects.toBeInstanceOf(PolymorfaNotFoundError); +});