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);
+});