From 095bfdd1bd45acd89f1c02773611e35eda416885 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 11:24:37 +0300 Subject: [PATCH 01/11] feat(billing): expose limits and project and number priorities --- contracts/README.md | 24 +- contracts/coverage.json | 57 +- contracts/openapi.platform.json | 804 +++++++++++++++++- contracts/source.json | 10 +- packages/typescript/README.md | 30 +- packages/typescript/src/index.ts | 2 + .../src/platform/billing-controls.ts | 87 ++ packages/typescript/src/platform/billing.ts | 110 +++ .../typescript/test/billing-controls.test.ts | 110 +++ .../test/coverage-reconciliation.test.ts | 2 +- packages/typescript/test/coverage.test.ts | 6 +- 11 files changed, 1190 insertions(+), 52 deletions(-) create mode 100644 packages/typescript/src/platform/billing-controls.ts create mode 100644 packages/typescript/test/billing-controls.test.ts diff --git a/contracts/README.md b/contracts/README.md index 2a0d2ecf..af1b4037 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -41,18 +41,18 @@ published SDK package before updating their pinned dependency. ## Full snapshots -The current snapshots are byte-identical copies of API number capabilities -PR #402 at merged `dev` commit -`ee885ec4bf62af36b9b85df4310c505f38a224db`. It adds `Client.sessions.getCapabilities` and the -capability response types. The API requires `sessions:read`, beta access and -live Number authority. Before configuration is synced, values are unknown. - -The pin also includes merged Hybrid Link tier transitions and new-chat capping. -The two Hybrid Link error codes now appear in the public error enum; the SDK -already recognizes both. Template submission has a more precise response -schema; its SDK response remains an object. Hosted Flow endpoint and encryption -key operations remain explicitly excluded from typed SDK coverage. Package -publication and deployed feature access are separate gates. +The current snapshots are byte-identical copies of Messaging and Platform +contracts at API implementation commit `2cf44217655e8f4659ff9bbfa869d6499f4926ce` on +`t3code/project-billing-dashboard`. This branch is unmerged. The five new +operations are `Client.billing.getLimits`, `setLimit`, `getPriorities`, +`setPriority` and `reorderPriorities`. Existing operation fingerprints are +unchanged. Reads require organization credentials with `billing:read`; writes +require `billing:manage`, live team authority and Pay-As-You-Go. Existing +credential masks do not gain these permissions. Paid service continues through +its paid window when a limit is reached. The older Graph and test-event +supplements retain their independent source pins; these operations do not +change those contracts. API deployment, SDK publication and CLI publication are +separate release gates. The preceding snapshots were described as follows. diff --git a/contracts/coverage.json b/contracts/coverage.json index bcb6f214..cb9ba8e2 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "ee885ec4bf62af36b9b85df4310c505f38a224db", + "sourceCommit": "2cf44217655e8f4659ff9bbfa869d6499f4926ce", "excludedContracts": [ { "family": "graph", @@ -6159,6 +6159,61 @@ "status": "covered", "method": "Client.sessions.getCapabilities" } + }, + { + "family": "platform", + "method": "GET", + "path": "/platform/billing/limits", + "operationId": "getBillingLimits", + "fingerprint": "09f303846c1ce0044c0b15357d11108cb4da1d379caf2c02b9b65ccefb9c792b", + "typescript": { + "status": "covered", + "method": "Client.billing.getLimits" + } + }, + { + "family": "platform", + "method": "GET", + "path": "/platform/billing/priorities", + "operationId": "getBillingPriorities", + "fingerprint": "7c5b91e3412d7a1fdbfb7af700c142a0d751eeedce3e2b98d63f48f440c01039", + "typescript": { + "status": "covered", + "method": "Client.billing.getPriorities" + } + }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/billing/limits/{scope}/{resourceId}", + "operationId": "setBillingLimit", + "fingerprint": "cd84f8690610148f325a379712233e148844cbe9da093154fc3281b18184f69a", + "typescript": { + "status": "covered", + "method": "Client.billing.setLimit" + } + }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/billing/priorities", + "operationId": "reorderBillingPriorities", + "fingerprint": "e618e279c18d8b53715f15ae6e277a9464aadf0420cbebc0cfe9127e9f475f5e", + "typescript": { + "status": "covered", + "method": "Client.billing.reorderPriorities" + } + }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/billing/priorities/{scope}/{resourceId}", + "operationId": "setBillingPriority", + "fingerprint": "d5b33799f33d73775906061fcf94322af949ae722d5896807cdfe998f0da92a8", + "typescript": { + "status": "covered", + "method": "Client.billing.setPriority" + } } ] } diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index e06025d8..a2defd2c 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -3,7 +3,7 @@ "info": { "title": "Polymorfa Console & Management API", "version": "1.0.0", - "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (236 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (101 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." + "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (241 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (101 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." }, "servers": [ { @@ -16097,7 +16097,7 @@ "sessions" ], "summary": "Start a session", - "description": "Starts a stopped session or retries a session whose runtime failed. A hosted paid number must reserve credit before connecting; insufficient available credit returns 402. A new paid window is charged only after the messaging connection succeeds. The identifier may be the session UUID or stable session slug.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", + "description": "Starts a stopped session or retries a session whose runtime failed. A hosted paid number must reserve credit before connecting; insufficient available credit returns 402. A new paid window is charged only after the messaging connection succeeds. Monthly project and number credit limits also apply: a new charge that would exceed either limit returns 409 state_conflict. Already-paid service remains available until its paid window ends. The identifier may be the session UUID or stable session slug.\n\nRequires `sessions:manage`. Project tokens only access resources in their own project.", "x-audience": "public", "x-polymorfa-resource-boundary": "session", "x-excluded-from-public": false, @@ -24416,11 +24416,11 @@ "billing" ], "summary": "Get billing", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "description": "Public Management API. Requires a team API key with `billing:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "billing:read", "security": [ { "ApiKeyAuth": [] @@ -24473,11 +24473,396 @@ "billing" ], "summary": "List usage meters", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "description": "Public Management API. Requires a team API key with `billing:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "billing:read", + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ConsoleSession": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataEnvelope" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "501": { + "$ref": "#/components/responses/NotImplemented" + } + } + } + }, + "/platform/billing/transactions": { + "get": { + "operationId": "listBillingTransactions", + "tags": [ + "billing" + ], + "summary": "List billing transactions", + "description": "Public Management API. Requires a team API key with `billing:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "billing:read", + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ConsoleSession": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataEnvelope" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "501": { + "$ref": "#/components/responses/NotImplemented" + } + } + } + }, + "/platform/billing/pricing": { + "get": { + "operationId": "listTierPricing", + "tags": [ + "billing" + ], + "summary": "List tier pricing", + "description": "Public Management API. Requires a team API key with `billing:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "billing:read", + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ConsoleSession": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataEnvelope" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "501": { + "$ref": "#/components/responses/NotImplemented" + } + } + } + }, + "/platform/billing/limits": { + "get": { + "operationId": "getBillingLimits", + "tags": [ + "billing" + ], + "summary": "Read monthly spending limits", + "description": "Reads attributed charges and active reservations in Polymorfa credits. Months reset at 00:00 UTC on the first day. Omit projectId for project limits across the team; pass it to include that project's number limits. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "billing:read", + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/BillingLimits" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" + } + } + } + }, + "/platform/billing/limits/{scope}/{resourceId}": { + "put": { + "operationId": "setBillingLimit", + "tags": [ + "billing" + ], + "summary": "Set a monthly spending limit", + "description": "Zero blocks new charges; null removes the limit. Existing paid service continues to its paid-window end. Reservations count toward admission. Supply the revision from the limit read; a stale revision returns 409. Retrying the same successful update is harmless. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:manage`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "billing:manage", + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "scope", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "resourceId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "limitCredits": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001, + "maximum": 1000000, + "nullable": true + }, + "expectedRevision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + } + }, + "required": [ + "limitCredits", + "expectedRevision" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "object", + "additionalProperties": false, + "properties": { + "saved": { + "type": "boolean" + } + }, + "required": [ + "saved" + ] + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" + } + } + } + }, + "/platform/billing/priorities": { + "get": { + "operationId": "getBillingPriorities", + "tags": [ + "billing" + ], + "summary": "Read funding priorities", + "description": "Lists active projects and real numbers. Higher project priority takes precedence; higher number priority breaks ties before renewal due time and stable number identity. Numbers are ranked within a project. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "billing:read", "security": [ { "ApiKeyAuth": [] @@ -24506,7 +24891,16 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/BillingPriorities" + } + }, + "required": [ + "data" + ] } } } @@ -24517,24 +24911,22 @@ "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "503": { + "$ref": "#/components/responses/PropagationPending" } } - } - }, - "/platform/billing/transactions": { - "get": { - "operationId": "listBillingTransactions", + }, + "put": { + "operationId": "reorderBillingPriorities", "tags": [ "billing" ], - "summary": "List billing transactions", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "Reorder funding priorities", + "description": "resourceIds is the complete list in highest-first order. Project order includes every active project; number order includes every real number in projectId. Saves the whole order atomically. Concurrent changes or an incomplete list return 409. Retrying the same successful order is harmless. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "billing:manage", "security": [ { "ApiKeyAuth": [] @@ -24543,6 +24935,84 @@ "ConsoleSession": [] } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "scope": { + "type": "string", + "enum": [ + "project" + ] + }, + "resourceIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true, + "maxItems": 1000000 + }, + "expectedRevision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + } + }, + "required": [ + "scope", + "resourceIds", + "expectedRevision" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "scope": { + "type": "string", + "enum": [ + "number" + ] + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "resourceIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "uniqueItems": true, + "maxItems": 1000000 + }, + "expectedRevision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + } + }, + "required": [ + "scope", + "projectId", + "resourceIds", + "expectedRevision" + ] + } + ] + } + } + } + }, "responses": { "200": { "description": "Success", @@ -24563,35 +25033,53 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/BillingPriorities" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" } } } }, - "/platform/billing/pricing": { - "get": { - "operationId": "listTierPricing", + "/platform/billing/priorities/{scope}/{resourceId}": { + "put": { + "operationId": "setBillingPriority", "tags": [ "billing" ], - "summary": "List tier pricing", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", + "summary": "Set a funding priority", + "description": "Changes future funding protection and recovery order without shortening paid service. Supply the team-wide revision from the priorities read. A stale revision returns 409. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, - "x-required-scope": "sessions:read", + "x-required-scope": "billing:manage", "security": [ { "ApiKeyAuth": [] @@ -24600,6 +25088,52 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "scope", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "resourceId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + }, + "expectedRevision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + } + }, + "required": [ + "priority", + "expectedRevision" + ] + } + } + } + }, "responses": { "200": { "description": "Success", @@ -24620,19 +25154,37 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/BillingPriorities" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" } } } @@ -37685,6 +38237,200 @@ "ok" ] }, + "BillingPriorities": { + "type": "object", + "additionalProperties": false, + "properties": { + "revision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + }, + "projects": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + } + }, + "required": [ + "id", + "name", + "priority" + ] + } + }, + "numbers": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + } + }, + "required": [ + "id", + "projectId", + "name", + "priority" + ] + } + } + }, + "required": [ + "revision", + "projects", + "numbers" + ] + }, + "BillingLimit": { + "type": "object", + "additionalProperties": false, + "properties": { + "scope": { + "type": "string", + "enum": [ + "project", + "number" + ] + }, + "resourceId": { + "type": "string", + "format": "uuid" + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "limitCredits": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001, + "maximum": 1000000, + "nullable": true + }, + "spentCredits": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001 + }, + "reservedCredits": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001 + }, + "revision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + } + }, + "required": [ + "scope", + "resourceId", + "projectId", + "name", + "limitCredits", + "spentCredits", + "reservedCredits", + "revision" + ] + }, + "BillingLimits": { + "type": "object", + "additionalProperties": false, + "properties": { + "checkedAt": { + "type": "string", + "format": "date-time" + }, + "periodStart": { + "type": "string", + "format": "date-time" + }, + "periodEnd": { + "type": "string", + "format": "date-time" + }, + "todayCredits": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001 + }, + "monthCredits": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001 + }, + "daily": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "date": { + "type": "string", + "format": "date" + }, + "credits": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001 + } + }, + "required": [ + "date", + "credits" + ] + } + }, + "budgets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BillingLimit" + } + } + }, + "required": [ + "checkedAt", + "periodStart", + "periodEnd", + "todayCredits", + "monthCredits", + "daily", + "budgets" + ] + }, "DashboardUnauthorizedError": { "type": "object", "additionalProperties": false, diff --git a/contracts/source.json b/contracts/source.json index 9d023a15..94c69f80 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,9 +1,9 @@ { "repository": "polymorfa/polymorfa", - "commit": "ee885ec4bf62af36b9b85df4310c505f38a224db", - "branch": "dev", - "published": true, - "note": "API number capabilities PR #402, reconciled with Hybrid Link tier transitions and hosted Flow operations. Both snapshots are byte-identical to this source commit. SDK publication, CLI registry pinning and deployed beta access remain separate gates.", + "commit": "2cf44217655e8f4659ff9bbfa869d6499f4926ce", + "branch": "t3code/project-billing-dashboard", + "published": false, + "note": "Limits and project/number funding priorities on an unmerged implementation branch. Both snapshots are byte-identical to this API source commit. API deployment and SDK/CLI package publication remain separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", @@ -13,7 +13,7 @@ "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "d831f87eb4027c5f43ec4454bdd23e6972a4beb715fa080947545b7bff28d6b7" + "sha256": "82451703efb451fd847d0f664ca3d03101428ac98300fe7f7ecfd26e7dca58f9" } } } diff --git a/packages/typescript/README.md b/packages/typescript/README.md index 005ae6ca..3b317641 100644 --- a/packages/typescript/README.md +++ b/packages/typescript/README.md @@ -2392,7 +2392,10 @@ does not change prices, modes or customer access. Package publication and live a ## Billing and usage `Client.billing` exposes the complete organization-key billing family. -Reads require `sessions:read`. Credit quantities, including fields ending in +Balance, usage, transaction and pricing reads require `sessions:read`. +Spending limits and priority reads require `billing:read`; changes require +`billing:manage` and Pay-As-You-Go. Project and client credentials cannot use +these financial controls. Credit quantities, including fields ending in `Cents`, support up to six decimal places. They are not cash minor units. Team warnings follow the fixed one-day and two-hour insufficiency forecast; notification preferences are managed in the Console. @@ -2865,3 +2868,28 @@ without the original response. An `unknown` outcome can mean an external effect occurred; reconcile it before choosing a new key. Request/response bodies and customer log output are not retained. List responses contain `items` and `nextCursor`; pass that cursor as `before` to read the next page. + +## Spending limits and funding order + +```ts +const limits = await platform.billing.getLimits({ projectId }); +const number = limits.data.data.budgets.find(b => b.scope === "number")!; +await platform.billing.setLimit("number", number.resourceId, { + limitCredits: 1000, + expectedRevision: number.revision, +}); +const priorities = await platform.billing.getPriorities(); +await platform.billing.reorderPriorities({ + scope: "project", + resourceIds: priorities.data.data.projects.map(p => p.id).reverse(), + expectedRevision: priorities.data.data.revision, +}); +``` + +`setPriority(scope, resourceId, {priority, expectedRevision})` changes one rank. +For number order use `scope: "number"`, `projectId` and all its real number UUIDs. +Orders are complete and highest first; saves are atomic and stale revisions +return a conflict. Limits reset monthly at 00:00 UTC on the first day. Null +removes a cap; zero blocks new charges. Paid windows remain intact. These +methods do not grant permissions, paid access or deployed availability. +Publication of this source revision is separate from API deployment. diff --git a/packages/typescript/src/index.ts b/packages/typescript/src/index.ts index 6df09348..5eb44489 100644 --- a/packages/typescript/src/index.ts +++ b/packages/typescript/src/index.ts @@ -1241,3 +1241,5 @@ export { type FlowProviderOperation, type FlowProviderResult, } from "./platform/flows.js"; + +export type { BillingScope, BillingLimit, BillingLimits, BillingPriority, BillingPriorities, ReorderBillingPrioritiesInput } from "./platform/billing-controls.js"; diff --git a/packages/typescript/src/platform/billing-controls.ts b/packages/typescript/src/platform/billing-controls.ts new file mode 100644 index 00000000..9ccbdf89 --- /dev/null +++ b/packages/typescript/src/platform/billing-controls.ts @@ -0,0 +1,87 @@ +import { PolymorfaValidationError } from "../errors.js"; + +export type BillingScope = "project" | "number"; +export interface BillingPriority { + readonly id: string; + readonly name: string; + readonly priority: number; +} +export interface BillingPriorities { + readonly revision: number; + readonly projects: readonly BillingPriority[]; + readonly numbers: readonly (BillingPriority & { + readonly projectId: string; + })[]; +} +export interface BillingLimit { + readonly scope: BillingScope; + readonly resourceId: string; + readonly projectId: string; + readonly name: string; + readonly limitCredits: number | null; + readonly spentCredits: number; + readonly reservedCredits: number; + readonly revision: number; +} +export interface BillingLimits { + readonly checkedAt: string; + readonly periodStart: string; + readonly periodEnd: string; + readonly todayCredits: number; + readonly monthCredits: number; + readonly daily: readonly { + readonly date: string; + readonly credits: number; + }[]; + readonly budgets: readonly BillingLimit[]; +} +export type ReorderBillingPrioritiesInput = + | { + readonly scope: "project"; + readonly resourceIds: readonly string[]; + readonly expectedRevision: number; + } + | { + readonly scope: "number"; + readonly projectId: string; + readonly resourceIds: readonly string[]; + readonly expectedRevision: number; + }; +export function billingId(value: string): string { + if ( + !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( + value, + ) + ) + invalid("resource must be a UUID"); + return value.toLowerCase(); +} +export function billingRevision(value: number): void { + if (!Number.isSafeInteger(value) || value < 0 || value > 2147483646) + invalid("expectedRevision must be a nonnegative integer"); +} +export function billingScope(value: BillingScope): void { + if (value !== "project" && value !== "number") + invalid("scope must be project or number"); +} +export function billingPriority(value: number): void { + if (!Number.isInteger(value) || value < 0 || value > 1000000) + invalid("priority must be an integer from 0 to 1000000"); +} +export function billingLimit(value: number | null): void { + if ( + value !== null && + (!Number.isFinite(value) || + value < 0 || + value > 1000000 || + Math.abs(value * 1000000 - Math.round(value * 1000000)) > 0.00001) + ) + invalid( + "limitCredits must be null or 0–1000000 credits with at most six decimal places", + ); +} +function invalid(message: string): never { + throw new PolymorfaValidationError(message, { + code: "invalid_billing_control", + }); +} diff --git a/packages/typescript/src/platform/billing.ts b/packages/typescript/src/platform/billing.ts index e1f9ef81..4ece4600 100644 --- a/packages/typescript/src/platform/billing.ts +++ b/packages/typescript/src/platform/billing.ts @@ -1,3 +1,15 @@ +import { PolymorfaValidationError } from "../errors.js"; +import { + billingId, + billingRevision, + billingScope, + billingPriority, + billingLimit, + type BillingScope, + type BillingLimits, + type BillingPriorities, + type ReorderBillingPrioritiesInput, +} from "./billing-controls.js"; import { HttpTransport } from "../transport/http.js"; import type { ApiResponse, RequestOptions } from "../transport/types.js"; import type { @@ -35,6 +47,104 @@ export class BillingResource { return this.get("/pricing", options); } + /** Monthly credit limits. Organization credentials with billing:read only. */ + getLimits( + params: { readonly projectId?: string } = {}, + options: RequestOptions = {}, + ): Promise>> { + return this.transport.request({ + method: "GET", + path: "/platform/billing/limits", + query: + params.projectId === undefined + ? {} + : { projectId: billingId(params.projectId) }, + ...options, + }); + } + setLimit( + scope: BillingScope, + resourceId: string, + input: { + readonly limitCredits: number | null; + readonly expectedRevision: number; + }, + options: RequestOptions = {}, + ): Promise>> { + billingScope(scope); + billingRevision(input.expectedRevision); + billingLimit(input.limitCredits); + return this.transport.request({ + method: "PUT", + path: `/platform/billing/limits/${scope}/${billingId(resourceId)}`, + body: { + limitCredits: input.limitCredits, + expectedRevision: input.expectedRevision, + }, + ...options, + }); + } + getPriorities( + options: RequestOptions = {}, + ): Promise>> { + return this.transport.request({ + method: "GET", + path: "/platform/billing/priorities", + ...options, + }); + } + setPriority( + scope: BillingScope, + resourceId: string, + input: { readonly priority: number; readonly expectedRevision: number }, + options: RequestOptions = {}, + ): Promise>> { + billingScope(scope); + billingRevision(input.expectedRevision); + billingPriority(input.priority); + return this.transport.request({ + method: "PUT", + path: `/platform/billing/priorities/${scope}/${billingId(resourceId)}`, + body: { + priority: input.priority, + expectedRevision: input.expectedRevision, + }, + ...options, + }); + } + reorderPriorities( + input: ReorderBillingPrioritiesInput, + options: RequestOptions = {}, + ): Promise>> { + billingScope(input.scope); + billingRevision(input.expectedRevision); + const resourceIds = input.resourceIds.map(billingId); + if (new Set(resourceIds).size !== resourceIds.length) + throw new PolymorfaValidationError( + "resourceIds must not contain duplicates", + { code: "invalid_billing_control" }, + ); + const body = + input.scope === "number" + ? { + scope: input.scope, + projectId: billingId(input.projectId), + resourceIds, + expectedRevision: input.expectedRevision, + } + : { + scope: input.scope, + resourceIds, + expectedRevision: input.expectedRevision, + }; + return this.transport.request({ + method: "PUT", + path: "/platform/billing/priorities", + body, + ...options, + }); + } + private get( suffix: "" | "/usage" | "/transactions" | "/pricing", options: RequestOptions, diff --git a/packages/typescript/test/billing-controls.test.ts b/packages/typescript/test/billing-controls.test.ts new file mode 100644 index 00000000..a514856d --- /dev/null +++ b/packages/typescript/test/billing-controls.test.ts @@ -0,0 +1,110 @@ +import { afterEach, expect, it } from "vitest"; +import { + Client, + PolymorfaValidationError, + PolymorfaConflictError, +} from "../src/index.js"; +import { ORGANIZATION_API_KEY } from "./support/credentials.js"; +import { startTestServer, type TestServer } from "./support/http-server.js"; +const id = "11111111-1111-4111-8111-111111111111", + other = "22222222-2222-4222-8222-222222222222"; +let server: TestServer; +afterEach(async () => { + await server?.close(); +}); +it("serializes limits and single/reordered project and number priorities using revision guards", async () => { + server = await startTestServer(() => ({ + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + success: true, + data: { revision: 1, projects: [], numbers: [] }, + }), + })); + const client = new Client({ + baseUrl: server.url, + credential: { type: "organizationApiKey", value: ORGANIZATION_API_KEY }, + maxNetworkRetries: 0, + }); + await client.billing.getLimits({ projectId: id }); + await client.billing.setLimit("number", id, { + limitCredits: null, + expectedRevision: 0, + }); + await client.billing.getPriorities(); + await client.billing.setPriority("number", id, { + priority: 12, + expectedRevision: 1, + }); + await client.billing.reorderPriorities({ + scope: "project", + resourceIds: [other, id], + expectedRevision: 2, + }); + await client.billing.reorderPriorities({ + scope: "number", + projectId: other, + resourceIds: [id], + expectedRevision: 3, + }); + expect(server.requests.map((r) => [r.method, r.path])).toEqual([ + ["GET", `/platform/billing/limits?projectId=${id}`], + ["PUT", `/platform/billing/limits/number/${id}`], + ["GET", "/platform/billing/priorities"], + ["PUT", `/platform/billing/priorities/number/${id}`], + ["PUT", "/platform/billing/priorities"], + ["PUT", "/platform/billing/priorities"], + ]); + expect(JSON.parse(server.requests[4]!.body)).toEqual({ + scope: "project", + resourceIds: [other, id], + expectedRevision: 2, + }); + expect(JSON.parse(server.requests[5]!.body)).toEqual({ + scope: "number", + projectId: other, + resourceIds: [id], + expectedRevision: 3, + }); + expect(() => + client.billing.setLimit("project", id, { + limitCredits: -1, + expectedRevision: 0, + }), + ).toThrow(PolymorfaValidationError); + expect(() => + client.billing.reorderPriorities({ + scope: "project", + resourceIds: [id, id], + expectedRevision: 0, + }), + ).toThrow(PolymorfaValidationError); + expect(() => + client.billing.setPriority("number", id, { + priority: 1, + expectedRevision: -1, + }), + ).toThrow(PolymorfaValidationError); + expect(server.requests).toHaveLength(6); +}); +it("preserves a stale revision conflict without retrying or rewriting the request", async () => { + server = await startTestServer(() => ({ + status: 409, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + success: false, + error: { code: "conflict", message: "priority changed; reload" }, + }), + })); + const client = new Client({ + baseUrl: server.url, + credential: { type: "organizationApiKey", value: ORGANIZATION_API_KEY }, + maxNetworkRetries: 0, + }); + await expect( + client.billing.setPriority("project", id, { + priority: 4, + expectedRevision: 0, + }), + ).rejects.toBeInstanceOf(PolymorfaConflictError); + expect(server.requests).toHaveLength(1); +}); diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index f976bb84..67696a23 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -196,7 +196,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("ee885ec4bf62af36b9b85df4310c505f38a224db"); + expect(source.commit).toBe("2cf44217655e8f4659ff9bbfa869d6499f4926ce"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index 03d56793..c9d84f2c 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -173,9 +173,9 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "ee885ec4bf62af36b9b85df4310c505f38a224db", - total: 558, - covered: 422, + sourceCommit: "2cf44217655e8f4659ff9bbfa869d6499f4926ce", + total: 563, + covered: 427, partial: 0, missing: 0, excluded: 136, From 6c082b161ff6e03659b681cd6010fc35f8f1e812 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 11:31:10 +0300 Subject: [PATCH 02/11] test(billing): use the canonical financial response envelope --- packages/typescript/test/billing-controls.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/typescript/test/billing-controls.test.ts b/packages/typescript/test/billing-controls.test.ts index a514856d..4d18a06b 100644 --- a/packages/typescript/test/billing-controls.test.ts +++ b/packages/typescript/test/billing-controls.test.ts @@ -16,7 +16,6 @@ it("serializes limits and single/reordered project and number priorities using r server = await startTestServer(() => ({ headers: { "content-type": "application/json" }, body: JSON.stringify({ - success: true, data: { revision: 1, projects: [], numbers: [] }, }), })); From 1cb9af44a338151fa240a79662146f99eccd6c03 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 13:41:15 +0300 Subject: [PATCH 03/11] Support customer and single-resource financial controls and initial number budgets --- contracts/README.md | 24 +- contracts/coverage.json | 2569 +++++++++-------- contracts/openapi.messaging.json | 22 + contracts/openapi.platform.json | 1190 +++++++- contracts/source.json | 8 +- packages/typescript/README.md | 22 + packages/typescript/src/index.ts | 10 +- .../typescript/src/messaging/quicklinks.ts | 14 + .../src/platform/billing-controls.ts | 19 +- packages/typescript/src/platform/billing.ts | 54 +- .../typescript/test/billing-controls.test.ts | 69 +- .../typescript/test/contract-shapes.test.ts | 1 + .../test/coverage-reconciliation.test.ts | 2 +- packages/typescript/test/coverage.test.ts | 8 +- packages/typescript/test/quicklinks.test.ts | 42 + 15 files changed, 2693 insertions(+), 1361 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index af1b4037..8e491140 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -42,17 +42,19 @@ published SDK package before updating their pinned dependency. ## Full snapshots The current snapshots are byte-identical copies of Messaging and Platform -contracts at API implementation commit `2cf44217655e8f4659ff9bbfa869d6499f4926ce` on -`t3code/project-billing-dashboard`. This branch is unmerged. The five new -operations are `Client.billing.getLimits`, `setLimit`, `getPriorities`, -`setPriority` and `reorderPriorities`. Existing operation fingerprints are -unchanged. Reads require organization credentials with `billing:read`; writes -require `billing:manage`, live team authority and Pay-As-You-Go. Existing -credential masks do not gain these permissions. Paid service continues through -its paid window when a limit is reached. The older Graph and test-event -supplements retain their independent source pins; these operations do not -change those contracts. API deployment, SDK publication and CLI publication are -separate release gates. +contracts at API implementation commit `ec5f287953fe983ffa43e98dacbff4672c98c8ce` on +`t3code/project-billing-dashboard`. This branch is unmerged. Seven financial +operations cover limits, priorities, complete ordering and one-resource combined +read/save. Project, customer and number scopes use the confirmed funding hierarchy. +Initial real QuickLink creation accepts optional billing controls before admission. +Six existing operation fingerprints changed for those fields and read filters; +three new Console-only setup operations are explicitly excluded from SDK authority. +Reads require organization credentials with `billing:read`; writes require +`billing:manage`, live team authority and Pay-As-You-Go. Existing credential masks +do not gain these permissions. Paid service continues through its window when a +limit is reached. The older Graph and test-event supplements keep their independent +pins; these financial operations do not change them. API deployment, SDK and CLI +publication remain separate gates. The preceding snapshots were described as follows. diff --git a/contracts/coverage.json b/contracts/coverage.json index cb9ba8e2..42c1f644 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "2cf44217655e8f4659ff9bbfa869d6499f4926ce", + "sourceCommit": "ec5f287953fe983ffa43e98dacbff4672c98c8ce", "excludedContracts": [ { "family": "graph", @@ -8,50 +8,6 @@ } ], "operations": [ - { - "family": "messaging", - "method": "DELETE", - "path": "/messaging/projects/{projectSlug}/templates/{id}", - "operationId": "deleteProjectTemplate", - "fingerprint": "704bce35cceeeed3ef569e90f00f45ff2449bb77add7e78e8080461c065ffa8f", - "typescript": { - "status": "covered", - "method": "MessagingClient.templates.delete" - } - }, - { - "family": "messaging", - "method": "DELETE", - "path": "/messaging/quicklinks/{id}", - "operationId": "cancelQuickLink", - "fingerprint": "6659150484dacf39c85dc13f9917849692a46c2c848455b4cf0d570166f3915c", - "typescript": { - "status": "covered", - "method": "MessagingClient.quickLinks.cancel" - } - }, - { - "family": "messaging", - "method": "DELETE", - "path": "/messaging/voip/calls/{id}", - "operationId": "voipTeardown", - "fingerprint": "b2cc21a922b05a0728de995dc768b84aefbf6d6c8183c0762de08908db66d1e5", - "typescript": { - "status": "covered", - "method": "MessagingClient.voip.end" - } - }, - { - "family": "messaging", - "method": "DELETE", - "path": "/messaging/webhooks/{id}", - "operationId": "deleteWebhook", - "fingerprint": "8df161c0e20b57296b5a391ce37c5077da306f82d9c193780eb4f9da50bf233c", - "typescript": { - "status": "covered", - "method": "MessagingClient.webhooks.delete" - } - }, { "family": "messaging", "method": "DELETE", @@ -140,6 +96,17 @@ "method": "MessagingClient.labels.delete" } }, + { + "family": "messaging", + "method": "DELETE", + "path": "/messaging/{session}/official-groups/{group}", + "operationId": "deleteOfficialGroup", + "fingerprint": "cf321af37b6b755b2f8e9b97f8d06836ea46b9d7dea639e77b01f40b52e0c4bc", + "typescript": { + "status": "covered", + "method": "MessagingClient.officialGroups.delete" + } + }, { "family": "messaging", "method": "DELETE", @@ -153,1888 +120,2020 @@ }, { "family": "messaging", - "method": "GET", - "path": "/health", - "operationId": "getHealth", - "fingerprint": "0b86ad088cd96031eba501690dbab1fb881f25a53197d293c9cc015d1fc41146", + "method": "DELETE", + "path": "/messaging/{session}/templates/{name}", + "operationId": "deleteTemplate", + "fingerprint": "f126914a631b1452bee4061cddb07dd8b066790966bb58622d880600ebaba35a", "typescript": { "status": "covered", - "method": "SystemClient.health" + "method": "MessagingClient.cloudTemplates.delete" } }, { "family": "messaging", - "method": "GET", - "path": "/messaging/media/{id}", - "operationId": "downloadMedia", - "fingerprint": "f8e8d7ce8e3cc19032648ce4a70b4f2358b838a4c315422ec4764a11b4213f10", + "method": "DELETE", + "path": "/messaging/projects/{projectSlug}/templates/{id}", + "operationId": "deleteProjectTemplate", + "fingerprint": "704bce35cceeeed3ef569e90f00f45ff2449bb77add7e78e8080461c065ffa8f", "typescript": { "status": "covered", - "method": "MessagingClient.media.download" + "method": "MessagingClient.templates.delete" } }, { "family": "messaging", - "method": "GET", - "path": "/messaging/media/{id}/info", - "operationId": "getMediaInfo", - "fingerprint": "d38029111369d54dab8dd47bbf16492990c03cfcb6a0064d13f7420353a3fc02", + "method": "DELETE", + "path": "/messaging/quicklinks/{id}", + "operationId": "cancelQuickLink", + "fingerprint": "6659150484dacf39c85dc13f9917849692a46c2c848455b4cf0d570166f3915c", "typescript": { "status": "covered", - "method": "MessagingClient.media.retrieve" + "method": "MessagingClient.quickLinks.cancel" } }, { "family": "messaging", - "method": "GET", - "path": "/messaging/projects/{projectId}/health-policy", - "operationId": "getProjectHealthPolicy", - "fingerprint": "09403a53455245d5f437992b9ef66cac84d67e714438af046f32bd3073e770b9", + "method": "DELETE", + "path": "/messaging/voip/calls/{id}", + "operationId": "voipTeardown", + "fingerprint": "b2cc21a922b05a0728de995dc768b84aefbf6d6c8183c0762de08908db66d1e5", "typescript": { "status": "covered", - "method": "MessagingClient.banSafe.getProjectHealthPolicy" + "method": "MessagingClient.voip.end" } }, { "family": "messaging", - "method": "GET", - "path": "/messaging/projects/{projectId}/insurance-evidence", - "operationId": "getProjectInsuranceEvidence", - "fingerprint": "d8b6fc052820cf1fcd28199521885fc1abac34a0c8ca0dd7c0f0cbf189e7f6e4", + "method": "DELETE", + "path": "/messaging/webhooks/{id}", + "operationId": "deleteWebhook", + "fingerprint": "8df161c0e20b57296b5a391ce37c5077da306f82d9c193780eb4f9da50bf233c", "typescript": { "status": "covered", - "method": "MessagingClient.banSafe.getProjectInsuranceEvidence" + "method": "MessagingClient.webhooks.delete" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/projects/{projectId}/observation-policy", - "operationId": "getProjectObservationPolicy", - "fingerprint": "06a6a9caeebd3348042b03f922fe331fbbc4259d83f8efe73dd4bb7c41d1c209", + "path": "/health", + "operationId": "getHealth", + "fingerprint": "0b86ad088cd96031eba501690dbab1fb881f25a53197d293c9cc015d1fc41146", "typescript": { "status": "covered", - "method": "MessagingClient.observationPolicies.retrieveForProject" + "method": "SystemClient.health" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/projects/{projectId}/safe-mode", - "operationId": "getProjectSafeMode", - "fingerprint": "bf0004d1e3b9302eb36e49d9577567f33706f8b3dc73a963ab954f4118806373", + "path": "/messaging/{session}/business/catalog", + "operationId": "getBusinessCatalog", + "fingerprint": "61353b3e02238dffbfbadb1f807b7f1f665ce4cadb7173bf5f70a260ddf83412", "typescript": { "status": "covered", - "method": "MessagingClient.banSafe.getProjectSafeMode" + "method": "MessagingClient.business.getCatalog" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/projects/{projectId}/warmup-plan", - "operationId": "getProjectWarmupPlan", - "fingerprint": "2701cb4333d69a88e589c09aa31e1c3b298e14d5d4f17d4868357b494e1abc76", + "path": "/messaging/{session}/business/collections", + "operationId": "getBusinessCollections", + "fingerprint": "a559be874b3729dc2be4482bd421dda8e7605e037486629fbb4077884fea2624", "typescript": { "status": "covered", - "method": "MessagingClient.banSafe.getProjectWarmupPlan" + "method": "MessagingClient.business.listCollections" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/projects/{projectSlug}/campaigns", - "operationId": "listCampaigns", - "fingerprint": "4c543ff1f4cdf20e32b8b20b99f1972f4b1839d0ec89cb295005ca13d621fa22", + "path": "/messaging/{session}/business/collections/{collectionId}", + "operationId": "getBusinessCollection", + "fingerprint": "07ac8ab6620478384a25755409db026795d7a1bdd99aeb536af70fa10b1e4423", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.list" + "method": "MessagingClient.business.getCollection" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}", - "operationId": "getCampaign", - "fingerprint": "78faec60176771c3a7041f3de3bc45b4e8451667ace06307cc00e9106ec95b4b", + "path": "/messaging/{session}/business/compliance", + "operationId": "getBusinessMerchantCompliance", + "fingerprint": "43299ef8e48266bceb741d79f5fb31e434f2bdee27ab80c2c02a5b52cdeca422", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.retrieve" + "method": "MessagingClient.business.getMerchantCompliance" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}/analytics", - "operationId": "getCampaignAnalytics", - "fingerprint": "f380410e7583fc5a4d91841f4e88bc754156a0e9df562fe49e9d8aeffbb38ca6", + "path": "/messaging/{session}/business/eligibility", + "operationId": "getBusinessEligibility", + "fingerprint": "58a34fe81e25b98d63755711d26c2a13a2483055b2a43a9f3bed7823363cb35d", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.analytics" + "method": "MessagingClient.business.getEligibility" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}/recipients", - "operationId": "listProjectCampaignRecipients", - "fingerprint": "bfb53bd0dc8e274dec1586beeba8859b5bf7daad7b0450ee69ca2aa661b3b8d3", + "path": "/messaging/{session}/business/linked-accounts", + "operationId": "getBusinessLinkedAccounts", + "fingerprint": "c37b2ae05ca3fa0f17b2e586a63356cde5643d9a21c6c1e8e9439efd0a8188b8", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.listRecipients" + "method": "MessagingClient.business.getLinkedAccounts" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/projects/{projectSlug}/templates", - "operationId": "listProjectTemplates", - "fingerprint": "e355d7ab7e1e9aed12a6a7c9f81ee32ee9e9af4e6854b1c89fd1e9fb2d7840a8", + "path": "/messaging/{session}/business/products/{productId}", + "operationId": "getBusinessProduct", + "fingerprint": "c2615cb4674863469a4b575fbe7f25be004f03a769224fcf156ca041f0081cca", "typescript": { "status": "covered", - "method": "MessagingClient.templates.list" + "method": "MessagingClient.business.getProduct" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/projects/{projectSlug}/templates/{id}", - "operationId": "getProjectTemplate", - "fingerprint": "753536a07ceb93d431f5342cd14b284a79bfdfc68d4a6556a91ef0b8b2ff0a30", + "path": "/messaging/{session}/business/profile", + "operationId": "getOwnBusinessProfile", + "fingerprint": "ca265b54e6a78db9486cbdd480b420dc01d03532e2fa3c1d6b4fe22a31fc4dc7", "typescript": { "status": "covered", - "method": "MessagingClient.templates.retrieve" + "method": "MessagingClient.business.getProfile" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/quicklinks/availability", - "operationId": "getHybridQuickLinkAvailability", - "fingerprint": "f1c93e10f24d5f84d40eec50ae2f39f32111a358d38b8fd22934255cb4f847f3", + "path": "/messaging/{session}/business/quick-replies", + "operationId": "listBusinessQuickReplies", + "fingerprint": "422315cbc297c4e15c864fa0bf1c9d9e9ddee92813157b77303a91e672e55ffa", "typescript": { "status": "covered", - "method": "MessagingClient.quickLinks.availability" + "method": "MessagingClient.quickReplies.list" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/quicklinks/{id}", - "operationId": "getQuickLink", - "fingerprint": "9cadb618dfcd05cdcacd98cac632a93994736b81f91d797d21abbf64b1409ebd", + "path": "/messaging/{session}/call-permissions/{to}", + "operationId": "getCallPermission", + "fingerprint": "7876167581d7ddebb7ef3304a2358c49ab8a3b752b32d64d18b1f8c769461f49", "typescript": { "status": "covered", - "method": "MessagingClient.quickLinks.retrieve" + "method": "MessagingClient.voip.retrieveCallPermission" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/routing/hybrid", - "operationId": "getHybridRoutingPolicy", - "fingerprint": "51bf271a39c7d4761667bf8aa67c2e8fc70472794991e687c86bce6a2c16f82d", + "path": "/messaging/{session}/channels", + "operationId": "listChannels", + "fingerprint": "4a12ef2dd0eb0e611edf0597eac809f4b06ada6b6ba422271bc39e845c7a281d", "typescript": { "status": "covered", - "method": "MessagingClient.hybridLink.getPolicy" + "method": "MessagingClient.channels.list" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/testing/{projectId}/events/fixtures", - "operationId": "listTestEventFixtures", - "fingerprint": "0a1482d5fd5a365c0ba2e241d1f45ed0d5c88615c871ea97af442c8f26f3d22d", + "path": "/messaging/{session}/channels/{id}", + "operationId": "getChannel", + "fingerprint": "31c1ab9612c17e647f5673883bb3bd6447b75c38afc6953803f7678bf6171a88", "typescript": { "status": "covered", - "method": "MessagingClient.testing.listEventFixtures" + "method": "MessagingClient.channels.retrieve" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/voip/calls/{id}/candidates", - "operationId": "voipCandidates", - "fingerprint": "a8172560f38575c0badb5a6a1cbefe456ce5a72c40f59a8316df68ad4c822cb9", + "path": "/messaging/{session}/channels/{id}/message-updates", + "operationId": "listChannelMessageUpdates", + "fingerprint": "9ab2ecea4f34564037bebc0a9a16fe68bd395bd01ea732dccfeeb1e152740e96", "typescript": { - "status": "excluded", - "reason": "used internally by the Calls SDK; not part of the public API" + "status": "covered", + "method": "MessagingClient.channels.listMessageUpdates" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/webhooks", - "operationId": "listWebhooks", - "fingerprint": "9e0a2548335101f19d3a002389b7b3dc6c91716d84e6ecf8961b4390645df9a5", + "path": "/messaging/{session}/channels/{id}/messages", + "operationId": "listChannelMessages", + "fingerprint": "860282caf0da0577921adf684e0c061b031ab164e36ea427171f654be4517571", "typescript": { "status": "covered", - "method": "MessagingClient.webhooks.list" + "method": "MessagingClient.channels.listMessages" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/webhooks/{id}", - "operationId": "getWebhook", - "fingerprint": "9907bf3ee126a5be3bc6faa4354c6ebffbdca6cf9d7e5d8c2f678a03dd5ef96c", + "path": "/messaging/{session}/chats", + "operationId": "listChats", + "fingerprint": "24cb458e3a601af15c1f8c99cc14c92c1b59b0f9e62498f5ef553e5c12e77f5e", "typescript": { "status": "covered", - "method": "MessagingClient.webhooks.retrieve" + "method": "MessagingClient.chats.list" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/business/catalog", - "operationId": "getBusinessCatalog", - "fingerprint": "61353b3e02238dffbfbadb1f807b7f1f665ce4cadb7173bf5f70a260ddf83412", + "path": "/messaging/{session}/chats/{conversation}", + "operationId": "getChat", + "fingerprint": "79a9f6a120c3c1cc4fafd8cda520cccf177eff1c22bdef559138e267ccf34291", "typescript": { "status": "covered", - "method": "MessagingClient.business.getCatalog" + "method": "MessagingClient.chats.retrieve" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/business/collections", - "operationId": "getBusinessCollections", - "fingerprint": "a559be874b3729dc2be4482bd421dda8e7605e037486629fbb4077884fea2624", + "path": "/messaging/{session}/chats/{conversation}/messages", + "operationId": "listChatMessages", + "fingerprint": "cb68eddf87e23ff586c27fea6943925342c0103377a018709e1c48e9ccf7c617", "typescript": { "status": "covered", - "method": "MessagingClient.business.listCollections" + "method": "MessagingClient.chats.listMessages" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/business/collections/{collectionId}", - "operationId": "getBusinessCollection", - "fingerprint": "07ac8ab6620478384a25755409db026795d7a1bdd99aeb536af70fa10b1e4423", - "typescript": { - "status": "covered", - "method": "MessagingClient.business.getCollection" - } - }, - { - "family": "messaging", - "method": "GET", - "path": "/messaging/{session}/business/compliance", - "operationId": "getBusinessMerchantCompliance", - "fingerprint": "43299ef8e48266bceb741d79f5fb31e434f2bdee27ab80c2c02a5b52cdeca422", + "path": "/messaging/{session}/chats/{conversation}/messages/{messageId}", + "operationId": "getChatMessage", + "fingerprint": "d6990507463cd04385c4d1600b2187d4e462510028c10b9bb67ac81c9c5997ca", "typescript": { "status": "covered", - "method": "MessagingClient.business.getMerchantCompliance" + "method": "MessagingClient.chats.retrieveMessage" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/business/eligibility", - "operationId": "getBusinessEligibility", - "fingerprint": "58a34fe81e25b98d63755711d26c2a13a2483055b2a43a9f3bed7823363cb35d", + "path": "/messaging/{session}/chats/{conversation}/messages/{messageId}/media", + "operationId": "getChatMessageMedia", + "fingerprint": "2698c3134022f5b62c1675b73c5094e1cfcdb87d68861db0503a25bf0f095681", "typescript": { "status": "covered", - "method": "MessagingClient.business.getEligibility" + "method": "MessagingClient.chats.downloadMessageMedia" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/business/linked-accounts", - "operationId": "getBusinessLinkedAccounts", - "fingerprint": "c37b2ae05ca3fa0f17b2e586a63356cde5643d9a21c6c1e8e9439efd0a8188b8", + "path": "/messaging/{session}/chats/{conversation}/service-window", + "operationId": "getCustomerServiceWindow", + "fingerprint": "1925b1f86922a014d0e5e3dac34cd1dc175e8bf3113e592d98161aeade1db5a4", "typescript": { "status": "covered", - "method": "MessagingClient.business.getLinkedAccounts" + "method": "MessagingClient.chats.getServiceWindow" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/business/products/{productId}", - "operationId": "getBusinessProduct", - "fingerprint": "c2615cb4674863469a4b575fbe7f25be004f03a769224fcf156ca041f0081cca", + "path": "/messaging/{session}/cloud-credentials", + "operationId": "getCloudCredentialHealth", + "fingerprint": "37016f82da78fb55253002ed0876a8aa25d1ae3b307cc7a568d43fda772b83a0", "typescript": { "status": "covered", - "method": "MessagingClient.business.getProduct" + "method": "MessagingClient.sessions.getCloudCredentialHealth" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/business/profile", - "operationId": "getOwnBusinessProfile", - "fingerprint": "ca265b54e6a78db9486cbdd480b420dc01d03532e2fa3c1d6b4fe22a31fc4dc7", + "path": "/messaging/{session}/contacts", + "operationId": "listContacts", + "fingerprint": "8992b360a7718afe78f048253f21d2ee5c3f62df9e6a70631fccf5935d89c157", "typescript": { "status": "covered", - "method": "MessagingClient.business.getProfile" + "method": "MessagingClient.contacts.list" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/business/quick-replies", - "operationId": "listBusinessQuickReplies", - "fingerprint": "422315cbc297c4e15c864fa0bf1c9d9e9ddee92813157b77303a91e672e55ffa", + "path": "/messaging/{session}/contacts/{contactId}", + "operationId": "getContact", + "fingerprint": "c46624838bbc3bc588ea7816c7c16963ac4fbd4bd7ecd8ad4ed76e321f2ac566", "typescript": { "status": "covered", - "method": "MessagingClient.quickReplies.list" + "method": "MessagingClient.contacts.retrieve" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/call-permissions/{to}", - "operationId": "getCallPermission", - "fingerprint": "7876167581d7ddebb7ef3304a2358c49ab8a3b752b32d64d18b1f8c769461f49", + "path": "/messaging/{session}/contacts/{contactId}/business-profile", + "operationId": "getBusinessProfile", + "fingerprint": "2fa75ea100b92613eb601f851a015584d1e8f3b1fd83baaee7525f4a11acb185", "typescript": { "status": "covered", - "method": "MessagingClient.voip.retrieveCallPermission" + "method": "MessagingClient.contacts.businessProfile" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/channels", - "operationId": "listChannels", - "fingerprint": "4a12ef2dd0eb0e611edf0597eac809f4b06ada6b6ba422271bc39e845c7a281d", + "path": "/messaging/{session}/contacts/{contactId}/devices", + "operationId": "getUserDevices", + "fingerprint": "ff38cd6124eadb8ec94c7d35538780d6b59c5545b65db2487b80cba48e3e2960", "typescript": { "status": "covered", - "method": "MessagingClient.channels.list" + "method": "MessagingClient.contacts.devices" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/channels/{id}", - "operationId": "getChannel", - "fingerprint": "31c1ab9612c17e647f5673883bb3bd6447b75c38afc6953803f7678bf6171a88", + "path": "/messaging/{session}/contacts/{contactId}/info", + "operationId": "getUserInfo", + "fingerprint": "172850e24c2fca3e37c5554cc786382ac827312a7b49ab8bd1909ade53ca010e", "typescript": { "status": "covered", - "method": "MessagingClient.channels.retrieve" + "method": "MessagingClient.contacts.info" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/channels/{id}/message-updates", - "operationId": "listChannelMessageUpdates", - "fingerprint": "9ab2ecea4f34564037bebc0a9a16fe68bd395bd01ea732dccfeeb1e152740e96", + "path": "/messaging/{session}/contacts/{contactId}/picture", + "operationId": "getContactPicture", + "fingerprint": "7f09316bb55fad3a4e89dba88fecc0eb149308ea8308c02888770e8958afcecd", "typescript": { "status": "covered", - "method": "MessagingClient.channels.listMessageUpdates" + "method": "MessagingClient.contacts.picture" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/channels/{id}/messages", - "operationId": "listChannelMessages", - "fingerprint": "860282caf0da0577921adf684e0c061b031ab164e36ea427171f654be4517571", + "path": "/messaging/{session}/contacts/blocked", + "operationId": "getBlocklist", + "fingerprint": "ad230658cd3b095e03d7a25218a85736b0b0fa19ead1798b318f56802b3e6aee", "typescript": { "status": "covered", - "method": "MessagingClient.channels.listMessages" + "method": "MessagingClient.contacts.blocklist" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/chats", - "operationId": "listChats", - "fingerprint": "24cb458e3a601af15c1f8c99cc14c92c1b59b0f9e62498f5ef553e5c12e77f5e", + "path": "/messaging/{session}/contacts/check", + "operationId": "checkContacts", + "fingerprint": "e03d0cedb8a8348eff1eabf4c84f3ca38bc03028f75486d7c8ae52f29d05461b", "typescript": { "status": "covered", - "method": "MessagingClient.chats.list" + "method": "MessagingClient.contacts.check" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/chats/{conversation}", - "operationId": "getChat", - "fingerprint": "79a9f6a120c3c1cc4fafd8cda520cccf177eff1c22bdef559138e267ccf34291", + "path": "/messaging/{session}/groups", + "operationId": "listGroups", + "fingerprint": "516ffa538b16c318cb80f90bc9cfb1c2e3d8f5e25ee96d0a3ef924b536e5e9d5", "typescript": { "status": "covered", - "method": "MessagingClient.chats.retrieve" + "method": "MessagingClient.groups.list" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/chats/{conversation}/messages", - "operationId": "listChatMessages", - "fingerprint": "cb68eddf87e23ff586c27fea6943925342c0103377a018709e1c48e9ccf7c617", + "path": "/messaging/{session}/groups/{id}", + "operationId": "getGroup", + "fingerprint": "f2eed086eda1f21eed08a32b84e6c931c4e29cdedc403b8a7b9db8a6fc542646", "typescript": { "status": "covered", - "method": "MessagingClient.chats.listMessages" + "method": "MessagingClient.groups.retrieve" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/chats/{conversation}/messages/{messageId}", - "operationId": "getChatMessage", - "fingerprint": "d6990507463cd04385c4d1600b2187d4e462510028c10b9bb67ac81c9c5997ca", + "path": "/messaging/{session}/groups/{id}/invite-code", + "operationId": "getGroupInviteCode", + "fingerprint": "612a3c63b52090bab7ee5abd3f760daa8f30cb505096b1dcf59721f83875ccbe", "typescript": { "status": "covered", - "method": "MessagingClient.chats.retrieveMessage" + "method": "MessagingClient.groups.getInviteCode" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/chats/{conversation}/messages/{messageId}/media", - "operationId": "getChatMessageMedia", - "fingerprint": "2698c3134022f5b62c1675b73c5094e1cfcdb87d68861db0503a25bf0f095681", + "path": "/messaging/{session}/groups/{id}/participants", + "operationId": "getGroupParticipants", + "fingerprint": "fd4bffbcfe104b9b1db025eb7478007832c1df8f787ad428bebc94860a895e2f", "typescript": { "status": "covered", - "method": "MessagingClient.chats.downloadMessageMedia" + "method": "MessagingClient.groups.listParticipants" } }, { "family": "messaging", - "method": "DELETE", - "path": "/messaging/{session}/templates/{name}", - "operationId": "deleteTemplate", - "fingerprint": "f126914a631b1452bee4061cddb07dd8b066790966bb58622d880600ebaba35a", + "method": "GET", + "path": "/messaging/{session}/groups/join-info", + "operationId": "getGroupJoinInfo", + "fingerprint": "60050c74d55b1e480df9c0fd9b07a91c529766d8d67cbebac082405133b8f563", "typescript": { "status": "covered", - "method": "MessagingClient.cloudTemplates.delete" + "method": "MessagingClient.groups.getJoinInfo" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/chats/{conversation}/service-window", - "operationId": "getCustomerServiceWindow", - "fingerprint": "1925b1f86922a014d0e5e3dac34cd1dc175e8bf3113e592d98161aeade1db5a4", + "path": "/messaging/{session}/hybrid-link", + "operationId": "getHybridLinkState", + "fingerprint": "7559a732219294265940aa7f92af2fe0eb4fdc6171190e4bdcba2c4c87dced8e", "typescript": { "status": "covered", - "method": "MessagingClient.chats.getServiceWindow" + "method": "MessagingClient.hybridLink.state" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/cloud-credentials", - "operationId": "getCloudCredentialHealth", - "fingerprint": "37016f82da78fb55253002ed0876a8aa25d1ae3b307cc7a568d43fda772b83a0", + "path": "/messaging/{session}/identities/resolve", + "operationId": "resolveIdentity", + "fingerprint": "6320c34ec37c89c4bb09ef9795df453b9f40ce965390be10053c9940ab085cbb", "typescript": { "status": "covered", - "method": "MessagingClient.sessions.getCloudCredentialHealth" + "method": "MessagingClient.identities.resolve" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/bridge/route", - "operationId": "getBridgeRoute", - "fingerprint": "2526818126016374f49ee7d2d2ab45bb9c264f7974fe06e2099b6d60514b9951", + "path": "/messaging/{session}/labels", + "operationId": "listLabels", + "fingerprint": "fe990086178e817a235319b6f19309142a4a564f16783793109af4e37fd28449", "typescript": { "status": "covered", - "method": "BridgeClient.routes.resolve" + "method": "MessagingClient.labels.list" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/info/status", - "operationId": "getStatus", - "fingerprint": "23338dd706e18d3d253e5c2306a188a4228b546fe8166c9c5baf252077c7162b", + "path": "/messaging/{session}/labels/chats/{conversation}", + "operationId": "getChatLabels", + "fingerprint": "70a8e528f146a096441feaa12659c0ad5e6459ed071ea8eb140d59ad8e5d6273", "typescript": { "status": "covered", - "method": "SystemClient.status" + "method": "MessagingClient.labels.listForChat" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/info/version", - "operationId": "getVersion", - "fingerprint": "cc8bde27daeb5d13ca1b720781867dd26c8bc148e29ad8f8136f04cf8545fe1d", + "path": "/messaging/{session}/meta-pricing", + "operationId": "getMetaPricingSummary", + "fingerprint": "8c41ab014eaaf276173fc16f5613e799f276d5d0a22e586e4e530fac13e44c23", "typescript": { "status": "covered", - "method": "SystemClient.version" + "method": "MessagingClient.sessions.getMetaPricing" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/contacts", - "operationId": "listContacts", - "fingerprint": "8992b360a7718afe78f048253f21d2ee5c3f62df9e6a70631fccf5935d89c157", + "path": "/messaging/{session}/observation-policy", + "operationId": "getSessionObservationPolicy", + "fingerprint": "fca0afe7eaee93aaba79a633299797aba9185b19109cb0488338a4d99a00848e", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.list" + "method": "MessagingClient.observationPolicies.retrieveForSession" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/contacts/blocked", - "operationId": "getBlocklist", - "fingerprint": "ad230658cd3b095e03d7a25218a85736b0b0fa19ead1798b318f56802b3e6aee", + "path": "/messaging/{session}/official-groups", + "operationId": "listOfficialGroups", + "fingerprint": "96e3e0f5fbca8eb4c811700876172ea478faea7ae45a75b765168cc981cd8062", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.blocklist" + "method": "MessagingClient.officialGroups.list" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/contacts/check", - "operationId": "checkContacts", - "fingerprint": "e03d0cedb8a8348eff1eabf4c84f3ca38bc03028f75486d7c8ae52f29d05461b", + "path": "/messaging/{session}/official-groups/{group}", + "operationId": "getOfficialGroup", + "fingerprint": "bb03e30f20c0ba55966715b1866556c87fba1a4af36d68e1becb89ae10724c8b", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.check" + "method": "MessagingClient.officialGroups.retrieve" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/contacts/{contactId}", - "operationId": "getContact", - "fingerprint": "c46624838bbc3bc588ea7816c7c16963ac4fbd4bd7ecd8ad4ed76e321f2ac566", + "path": "/messaging/{session}/official-groups/{group}/invite-link", + "operationId": "getOfficialGroupInviteLink", + "fingerprint": "f5835a10fd91fab374cc379c6b46dc3f5fb8cd66436db23a1277a0b616940e4d", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.retrieve" + "method": "MessagingClient.officialGroups.getInviteLink" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/contacts/{contactId}/business-profile", - "operationId": "getBusinessProfile", - "fingerprint": "2fa75ea100b92613eb601f851a015584d1e8f3b1fd83baaee7525f4a11acb185", + "path": "/messaging/{session}/official-groups/{group}/join-requests", + "operationId": "listOfficialGroupJoinRequests", + "fingerprint": "6ac3d11480f9402bc8efcbaaf63343afa40a79be00ea6772f3dc2dcafdf30faf", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.businessProfile" + "method": "MessagingClient.officialGroups.listJoinRequests" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/contacts/{contactId}/devices", - "operationId": "getUserDevices", - "fingerprint": "ff38cd6124eadb8ec94c7d35538780d6b59c5545b65db2487b80cba48e3e2960", + "path": "/messaging/{session}/operations/{operationId}", + "operationId": "getMessageOperation", + "fingerprint": "8665cacfcf9a290b69437c5332522697b6dd915ef82a98cb2f2adb6d3f1a14c0", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.devices" + "method": "MessagingClient.messages.operationStatus" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/contacts/{contactId}/info", - "operationId": "getUserInfo", - "fingerprint": "172850e24c2fca3e37c5554cc786382ac827312a7b49ab8bd1909ade53ca010e", + "path": "/messaging/{session}/pair/qr", + "operationId": "getQRCode", + "fingerprint": "9ad6ae17fa639b77cd95050fe68755bd4e694f7ff0264846e447c9add442465e", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.info" + "method": "MessagingClient.sessions.qr" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/contacts/{contactId}/picture", - "operationId": "getContactPicture", - "fingerprint": "7f09316bb55fad3a4e89dba88fecc0eb149308ea8308c02888770e8958afcecd", + "path": "/messaging/{session}/presence", + "operationId": "getPresence", + "fingerprint": "ee4ea717ae0016d6a6dfd53f6cd373786e144d5d943831f588eee948a73156ff", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.picture" + "method": "MessagingClient.presence.get" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/groups", - "operationId": "listGroups", - "fingerprint": "516ffa538b16c318cb80f90bc9cfb1c2e3d8f5e25ee96d0a3ef924b536e5e9d5", + "path": "/messaging/{session}/presence/{conversation}", + "operationId": "getChatPresence", + "fingerprint": "8b29bd3310c9a386039d129943ad6dba191a524670039cfc9c44b51a05513dd8", "typescript": { "status": "covered", - "method": "MessagingClient.groups.list" + "method": "MessagingClient.presence.getForChat" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/groups/join-info", - "operationId": "getGroupJoinInfo", - "fingerprint": "60050c74d55b1e480df9c0fd9b07a91c529766d8d67cbebac082405133b8f563", + "path": "/messaging/{session}/privacy", + "operationId": "getPrivacySettings", + "fingerprint": "f4b28b111b1063d02e5b16f5a82b03bd8bc3081632164a9b96d25fc1436a8045", "typescript": { "status": "covered", - "method": "MessagingClient.groups.getJoinInfo" + "method": "MessagingClient.privacy.get" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/groups/{id}", - "operationId": "getGroup", - "fingerprint": "f2eed086eda1f21eed08a32b84e6c931c4e29cdedc403b8a7b9db8a6fc542646", + "path": "/messaging/{session}/profile", + "operationId": "getProfile", + "fingerprint": "f522db991a70d1d6a6045611259a82f3d794c3152a4e15e212292ae74422dd7e", "typescript": { "status": "covered", - "method": "MessagingClient.groups.retrieve" + "method": "MessagingClient.profile.get" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/groups/{id}/invite-code", - "operationId": "getGroupInviteCode", - "fingerprint": "612a3c63b52090bab7ee5abd3f760daa8f30cb505096b1dcf59721f83875ccbe", + "path": "/messaging/{session}/safe-mode", + "operationId": "getSessionSafeMode", + "fingerprint": "b182e4b2869075671afbd0e9b859bcee39d6c7f50471ea0ca43b504f75eb1232", "typescript": { "status": "covered", - "method": "MessagingClient.groups.getInviteCode" + "method": "MessagingClient.banSafe.getSessionSafeMode" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/groups/{id}/participants", - "operationId": "getGroupParticipants", - "fingerprint": "fd4bffbcfe104b9b1db025eb7478007832c1df8f787ad428bebc94860a895e2f", + "path": "/messaging/{session}/templates", + "operationId": "listTemplates", + "fingerprint": "5df7cd21bd821e127a4cccfab21711592c7f63bf6fb1b6cd73e20b2fb9d407db", "typescript": { "status": "covered", - "method": "MessagingClient.groups.listParticipants" + "method": "MessagingClient.cloudTemplates.list" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/hybrid-link", - "operationId": "getHybridLinkState", - "fingerprint": "7559a732219294265940aa7f92af2fe0eb4fdc6171190e4bdcba2c4c87dced8e", + "path": "/messaging/{session}/templates/{name}", + "operationId": "getTemplate", + "fingerprint": "3eb4b6d52b52b011a1c87c42e36289960e137930b28cd4b6564c9e196f42f6cc", "typescript": { "status": "covered", - "method": "MessagingClient.hybridLink.state" + "method": "MessagingClient.cloudTemplates.retrieve" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/identities/resolve", - "operationId": "resolveIdentity", - "fingerprint": "6320c34ec37c89c4bb09ef9795df453b9f40ce965390be10053c9940ab085cbb", + "path": "/messaging/{session}/users/{id}/security-code", + "operationId": "getUserSecurityCode", + "fingerprint": "69f03a5c99714e7e5c6adc4d77c656596ccccedc1a937e5b603751167ca058c1", "typescript": { "status": "covered", - "method": "MessagingClient.identities.resolve" + "method": "MessagingClient.users.getSecurityCode" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/labels", - "operationId": "listLabels", - "fingerprint": "fe990086178e817a235319b6f19309142a4a564f16783793109af4e37fd28449", + "path": "/messaging/bridge/route", + "operationId": "getBridgeRoute", + "fingerprint": "2526818126016374f49ee7d2d2ab45bb9c264f7974fe06e2099b6d60514b9951", "typescript": { "status": "covered", - "method": "MessagingClient.labels.list" + "method": "BridgeClient.routes.resolve" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/labels/chats/{conversation}", - "operationId": "getChatLabels", - "fingerprint": "70a8e528f146a096441feaa12659c0ad5e6459ed071ea8eb140d59ad8e5d6273", + "path": "/messaging/info/status", + "operationId": "getStatus", + "fingerprint": "23338dd706e18d3d253e5c2306a188a4228b546fe8166c9c5baf252077c7162b", "typescript": { "status": "covered", - "method": "MessagingClient.labels.listForChat" + "method": "SystemClient.status" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/meta-pricing", - "operationId": "getMetaPricingSummary", - "fingerprint": "8c41ab014eaaf276173fc16f5613e799f276d5d0a22e586e4e530fac13e44c23", + "path": "/messaging/info/version", + "operationId": "getVersion", + "fingerprint": "cc8bde27daeb5d13ca1b720781867dd26c8bc148e29ad8f8136f04cf8545fe1d", "typescript": { "status": "covered", - "method": "MessagingClient.sessions.getMetaPricing" + "method": "SystemClient.version" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/observation-policy", - "operationId": "getSessionObservationPolicy", - "fingerprint": "fca0afe7eaee93aaba79a633299797aba9185b19109cb0488338a4d99a00848e", + "path": "/messaging/media/{id}", + "operationId": "downloadMedia", + "fingerprint": "f8e8d7ce8e3cc19032648ce4a70b4f2358b838a4c315422ec4764a11b4213f10", "typescript": { "status": "covered", - "method": "MessagingClient.observationPolicies.retrieveForSession" + "method": "MessagingClient.media.download" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/operations/{operationId}", - "operationId": "getMessageOperation", - "fingerprint": "8665cacfcf9a290b69437c5332522697b6dd915ef82a98cb2f2adb6d3f1a14c0", + "path": "/messaging/media/{id}/info", + "operationId": "getMediaInfo", + "fingerprint": "d38029111369d54dab8dd47bbf16492990c03cfcb6a0064d13f7420353a3fc02", "typescript": { "status": "covered", - "method": "MessagingClient.messages.operationStatus" + "method": "MessagingClient.media.retrieve" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/pair/qr", - "operationId": "getQRCode", - "fingerprint": "9ad6ae17fa639b77cd95050fe68755bd4e694f7ff0264846e447c9add442465e", + "path": "/messaging/projects/{projectId}/health-policy", + "operationId": "getProjectHealthPolicy", + "fingerprint": "09403a53455245d5f437992b9ef66cac84d67e714438af046f32bd3073e770b9", "typescript": { "status": "covered", - "method": "MessagingClient.sessions.qr" + "method": "MessagingClient.banSafe.getProjectHealthPolicy" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/presence", - "operationId": "getPresence", - "fingerprint": "ee4ea717ae0016d6a6dfd53f6cd373786e144d5d943831f588eee948a73156ff", + "path": "/messaging/projects/{projectId}/insurance-evidence", + "operationId": "getProjectInsuranceEvidence", + "fingerprint": "d8b6fc052820cf1fcd28199521885fc1abac34a0c8ca0dd7c0f0cbf189e7f6e4", "typescript": { "status": "covered", - "method": "MessagingClient.presence.get" + "method": "MessagingClient.banSafe.getProjectInsuranceEvidence" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/presence/{conversation}", - "operationId": "getChatPresence", - "fingerprint": "8b29bd3310c9a386039d129943ad6dba191a524670039cfc9c44b51a05513dd8", + "path": "/messaging/projects/{projectId}/observation-policy", + "operationId": "getProjectObservationPolicy", + "fingerprint": "06a6a9caeebd3348042b03f922fe331fbbc4259d83f8efe73dd4bb7c41d1c209", "typescript": { "status": "covered", - "method": "MessagingClient.presence.getForChat" + "method": "MessagingClient.observationPolicies.retrieveForProject" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/privacy", - "operationId": "getPrivacySettings", - "fingerprint": "f4b28b111b1063d02e5b16f5a82b03bd8bc3081632164a9b96d25fc1436a8045", + "path": "/messaging/projects/{projectId}/safe-mode", + "operationId": "getProjectSafeMode", + "fingerprint": "bf0004d1e3b9302eb36e49d9577567f33706f8b3dc73a963ab954f4118806373", "typescript": { "status": "covered", - "method": "MessagingClient.privacy.get" + "method": "MessagingClient.banSafe.getProjectSafeMode" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/profile", - "operationId": "getProfile", - "fingerprint": "f522db991a70d1d6a6045611259a82f3d794c3152a4e15e212292ae74422dd7e", + "path": "/messaging/projects/{projectId}/warmup-plan", + "operationId": "getProjectWarmupPlan", + "fingerprint": "2701cb4333d69a88e589c09aa31e1c3b298e14d5d4f17d4868357b494e1abc76", "typescript": { "status": "covered", - "method": "MessagingClient.profile.get" + "method": "MessagingClient.banSafe.getProjectWarmupPlan" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/safe-mode", - "operationId": "getSessionSafeMode", - "fingerprint": "b182e4b2869075671afbd0e9b859bcee39d6c7f50471ea0ca43b504f75eb1232", + "path": "/messaging/projects/{projectSlug}/campaigns", + "operationId": "listCampaigns", + "fingerprint": "4c543ff1f4cdf20e32b8b20b99f1972f4b1839d0ec89cb295005ca13d621fa22", "typescript": { "status": "covered", - "method": "MessagingClient.banSafe.getSessionSafeMode" + "method": "MessagingClient.campaigns.list" } }, { "family": "messaging", "method": "GET", - "path": "/messaging/{session}/users/{id}/security-code", - "operationId": "getUserSecurityCode", - "fingerprint": "69f03a5c99714e7e5c6adc4d77c656596ccccedc1a937e5b603751167ca058c1", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}", + "operationId": "getCampaign", + "fingerprint": "78faec60176771c3a7041f3de3bc45b4e8451667ace06307cc00e9106ec95b4b", "typescript": { "status": "covered", - "method": "MessagingClient.users.getSecurityCode" + "method": "MessagingClient.campaigns.retrieve" } }, { "family": "messaging", "method": "GET", - "path": "/ping", - "operationId": "ping", - "fingerprint": "21f04b986970402813fe2eac2315a9a7b5266814cbbbc621319638ccb4b97513", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}/analytics", + "operationId": "getCampaignAnalytics", + "fingerprint": "f380410e7583fc5a4d91841f4e88bc754156a0e9df562fe49e9d8aeffbb38ca6", "typescript": { "status": "covered", - "method": "SystemClient.ping" + "method": "MessagingClient.campaigns.analytics" } }, { "family": "messaging", "method": "GET", - "path": "/quicklink/{token}", - "operationId": "getQuickLinkState", - "fingerprint": "274c13e56fcc665f60ef60c49da60c4a7e13e17b438fb61067f70a095d0cf13a", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}/recipients", + "operationId": "listProjectCampaignRecipients", + "fingerprint": "bfb53bd0dc8e274dec1586beeba8859b5bf7daad7b0450ee69ca2aa661b3b8d3", "typescript": { - "status": "excluded", - "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", - "milestone": "not-server-sdk" + "status": "covered", + "method": "MessagingClient.campaigns.listRecipients" } }, { "family": "messaging", "method": "GET", - "path": "/quicklink/{token}/logo", - "operationId": "getQuickLinkLogo", - "fingerprint": "c7306496e68ec5885a449967f205c0aecec7b3d7e2beb22bf5ef807bcb82f6a6", + "path": "/messaging/projects/{projectSlug}/templates", + "operationId": "listProjectTemplates", + "fingerprint": "e355d7ab7e1e9aed12a6a7c9f81ee32ee9e9af4e6854b1c89fd1e9fb2d7840a8", "typescript": { - "status": "excluded", - "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", - "milestone": "not-server-sdk" + "status": "covered", + "method": "MessagingClient.templates.list" } }, { "family": "messaging", - "method": "PATCH", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}", - "operationId": "updateProjectCampaign", - "fingerprint": "4e4c02f896824deabf5c7742e1633ed4c6aa5615c9cbef502edbd02510e983ac", + "method": "GET", + "path": "/messaging/projects/{projectSlug}/templates/{id}", + "operationId": "getProjectTemplate", + "fingerprint": "753536a07ceb93d431f5342cd14b284a79bfdfc68d4a6556a91ef0b8b2ff0a30", "typescript": { - "status": "excluded", - "reason": "Messaging campaign update is owned by SDK PR #307." + "status": "covered", + "method": "MessagingClient.templates.retrieve" } }, { "family": "messaging", - "method": "PATCH", - "path": "/messaging/projects/{projectSlug}/templates/{id}", - "operationId": "updateProjectTemplate", - "fingerprint": "dec8ec069480a537d9f42a34242dd79fee1c6c1f6534581f8ab3b03446a4ec03", + "method": "GET", + "path": "/messaging/quicklinks/{id}", + "operationId": "getQuickLink", + "fingerprint": "9cadb618dfcd05cdcacd98cac632a93994736b81f91d797d21abbf64b1409ebd", "typescript": { "status": "covered", - "method": "MessagingClient.templates.update" + "method": "MessagingClient.quickLinks.retrieve" } }, { "family": "messaging", - "method": "PATCH", - "path": "/messaging/{session}/business/catalog/cart", - "operationId": "setBusinessCartEnabled", - "fingerprint": "1e0622e371cdf651de2ab16e104f4ed14fda01c5563b1dc74393c5a5f9d8d48a", + "method": "GET", + "path": "/messaging/quicklinks/availability", + "operationId": "getHybridQuickLinkAvailability", + "fingerprint": "f1c93e10f24d5f84d40eec50ae2f39f32111a358d38b8fd22934255cb4f847f3", "typescript": { "status": "covered", - "method": "MessagingClient.business.setCartEnabled" + "method": "MessagingClient.quickLinks.availability" } }, { "family": "messaging", - "method": "PATCH", - "path": "/messaging/{session}/business/collections/{collectionId}", - "operationId": "updateBusinessCollection", - "fingerprint": "502dc1290e1165ad6ec4785adb9829c65feac60477fe84469fb1883ecae3bacf", + "method": "GET", + "path": "/messaging/routing/hybrid", + "operationId": "getHybridRoutingPolicy", + "fingerprint": "51bf271a39c7d4761667bf8aa67c2e8fc70472794991e687c86bce6a2c16f82d", "typescript": { "status": "covered", - "method": "MessagingClient.business.updateCollection" + "method": "MessagingClient.hybridLink.getPolicy" } }, { "family": "messaging", - "method": "PATCH", - "path": "/messaging/{session}/business/products/{productId}/visibility", - "operationId": "setBusinessProductVisibility", - "fingerprint": "c75d9fbd26f4ad2368e553098e1e190b7035b49cf3933344cba1fe9d3d878957", + "method": "GET", + "path": "/messaging/testing/{projectId}/events/fixtures", + "operationId": "listTestEventFixtures", + "fingerprint": "0a1482d5fd5a365c0ba2e241d1f45ed0d5c88615c871ea97af442c8f26f3d22d", "typescript": { "status": "covered", - "method": "MessagingClient.business.setProductVisibility" + "method": "MessagingClient.testing.listEventFixtures" } }, { "family": "messaging", - "method": "PATCH", - "path": "/messaging/{session}/business/profile", - "operationId": "updateBusinessProfile", - "fingerprint": "ea224ccf2dc82b633f7796a9b8a758b1ffecfc99ad649cdaf53cd6e3443b714d", + "method": "GET", + "path": "/messaging/voip/calls/{id}/candidates", + "operationId": "voipCandidates", + "fingerprint": "a8172560f38575c0badb5a6a1cbefe456ce5a72c40f59a8316df68ad4c822cb9", "typescript": { - "status": "covered", - "method": "MessagingClient.business.updateProfile" + "status": "excluded", + "reason": "used internally by the Calls SDK; not part of the public API" } }, { "family": "messaging", - "method": "PATCH", - "path": "/messaging/{session}/templates/{name}", - "operationId": "editTemplate", - "fingerprint": "5dddb5485dcca177e19563dc214045f07729cfd2b44a3e4aff5a458b11d68da9", + "method": "GET", + "path": "/messaging/webhooks", + "operationId": "listWebhooks", + "fingerprint": "9e0a2548335101f19d3a002389b7b3dc6c91716d84e6ecf8961b4390645df9a5", "typescript": { "status": "covered", - "method": "MessagingClient.cloudTemplates.update" + "method": "MessagingClient.webhooks.list" } }, { "family": "messaging", - "method": "PATCH", - "path": "/quicklink/{token}/testing", - "operationId": "customizeQuickLinkTesting", - "fingerprint": "ac8d711fe5e8615e72c66c57c2a0d3ceb6bdaa4f2010e422b95cd7d87893ebeb", + "method": "GET", + "path": "/messaging/webhooks/{id}", + "operationId": "getWebhook", + "fingerprint": "9907bf3ee126a5be3bc6faa4354c6ebffbdca6cf9d7e5d8c2f678a03dd5ef96c", "typescript": { - "status": "excluded", - "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." + "status": "covered", + "method": "MessagingClient.webhooks.retrieve" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/cloud-api/embedded-signup", - "operationId": "embeddedSignup", - "fingerprint": "8fa663a365c745cf62d7c6bdce11dfe5a9d3a6f93a13494bd5fb44a2fde3cd29", + "method": "GET", + "path": "/ping", + "operationId": "ping", + "fingerprint": "21f04b986970402813fe2eac2315a9a7b5266814cbbbc621319638ccb4b97513", "typescript": { "status": "covered", - "method": "MessagingClient.cloudOnboarding.advance" + "method": "SystemClient.ping" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/features/evaluate", - "operationId": "evaluateFeatures", - "fingerprint": "ce87686ff3267c65f17fb77d389f7aa1d6464a030965704256a946e586a94d12", + "method": "GET", + "path": "/quicklink/{token}", + "operationId": "getQuickLinkState", + "fingerprint": "274c13e56fcc665f60ef60c49da60c4a7e13e17b438fb61067f70a095d0cf13a", "typescript": { "status": "excluded", - "reason": "Host-runtime feature evaluation is not a customer messaging operation in this SDK." + "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", + "milestone": "not-server-sdk" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/media/{id}/download-and-save", - "operationId": "downloadAndSaveMedia", - "fingerprint": "018c0346629ae122dfdae54a6c5521eb0616ad5368bb1555b634edfa7caf79e5", + "method": "GET", + "path": "/quicklink/{token}/logo", + "operationId": "getQuickLinkLogo", + "fingerprint": "c7306496e68ec5885a449967f205c0aecec7b3d7e2beb22bf5ef807bcb82f6a6", + "typescript": { + "status": "excluded", + "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", + "milestone": "not-server-sdk" + } + }, + { + "family": "messaging", + "method": "PATCH", + "path": "/messaging/{session}/business/catalog/cart", + "operationId": "setBusinessCartEnabled", + "fingerprint": "1e0622e371cdf651de2ab16e104f4ed14fda01c5563b1dc74393c5a5f9d8d48a", "typescript": { "status": "covered", - "method": "MessagingClient.media.persist" + "method": "MessagingClient.business.setCartEnabled" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/projects/{projectSlug}/campaigns", - "operationId": "createCampaign", - "fingerprint": "cd375472bffd91aef28f0a5be4e96892edb2eae2369e1fe1a75d4e4ff1972225", + "method": "PATCH", + "path": "/messaging/{session}/business/collections/{collectionId}", + "operationId": "updateBusinessCollection", + "fingerprint": "502dc1290e1165ad6ec4785adb9829c65feac60477fe84469fb1883ecae3bacf", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.create" + "method": "MessagingClient.business.updateCollection" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}/launch", - "operationId": "launchCampaign", - "fingerprint": "58b2e466972439bedb0af24c559b0f0db025819cb2a807313156159b8e4a59b0", + "method": "PATCH", + "path": "/messaging/{session}/business/products/{productId}/visibility", + "operationId": "setBusinessProductVisibility", + "fingerprint": "c75d9fbd26f4ad2368e553098e1e190b7035b49cf3933344cba1fe9d3d878957", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.launch" + "method": "MessagingClient.business.setProductVisibility" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}/pause", - "operationId": "pauseCampaign", - "fingerprint": "b3000027edea590f0ebd2a50ede679659867ceba58edbe6be0624c9cc566ce20", + "method": "PATCH", + "path": "/messaging/{session}/business/profile", + "operationId": "updateBusinessProfile", + "fingerprint": "ea224ccf2dc82b633f7796a9b8a758b1ffecfc99ad649cdaf53cd6e3443b714d", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.pause" + "method": "MessagingClient.business.updateProfile" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}/recipients", - "operationId": "addProjectCampaignRecipients", - "fingerprint": "b01f480367a45722d420ef6d31ba0bc712e00696fe4f4fc802d94840b2f7aadf", + "method": "PATCH", + "path": "/messaging/{session}/official-groups/{group}", + "operationId": "updateOfficialGroup", + "fingerprint": "a4fb6d47150a50b2ed2280b3315d232d1d09676eef6624ea042e17e3a3104b7d", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.addRecipients" + "method": "MessagingClient.officialGroups.update" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}/requeue", - "operationId": "requeueCampaign", - "fingerprint": "c6e1f0278f461a0107f24263d9a6a73a698313b8dcacb9f7ebbbcba78f7f2cb5", + "method": "PATCH", + "path": "/messaging/{session}/templates/{name}", + "operationId": "editTemplate", + "fingerprint": "5dddb5485dcca177e19563dc214045f07729cfd2b44a3e4aff5a458b11d68da9", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.requeue" + "method": "MessagingClient.cloudTemplates.update" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}/reschedule", - "operationId": "rescheduleCampaign", - "fingerprint": "9b96f4321f98a3758c1b00b87a897bf5d52a48e78df99f40fab9324f6ec148ea", + "method": "PATCH", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}", + "operationId": "updateProjectCampaign", + "fingerprint": "4e4c02f896824deabf5c7742e1633ed4c6aa5615c9cbef502edbd02510e983ac", "typescript": { "status": "excluded", - "reason": "Campaign reschedule is owned by SDK PR #310 and will be covered when that PR merges." + "reason": "Messaging campaign update is owned by SDK PR #307." } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}/resume", - "operationId": "resumeCampaign", - "fingerprint": "0bcbd931ce0857076821b1de9ae28cd55964ee549ddd448082d2bb972c8595c6", + "method": "PATCH", + "path": "/messaging/projects/{projectSlug}/templates/{id}", + "operationId": "updateProjectTemplate", + "fingerprint": "dec8ec069480a537d9f42a34242dd79fee1c6c1f6534581f8ab3b03446a4ec03", "typescript": { "status": "covered", - "method": "MessagingClient.campaigns.resume" + "method": "MessagingClient.templates.update" } }, { "family": "messaging", - "method": "POST", - "path": "/messaging/projects/{projectSlug}/campaigns/{id}/stop", - "operationId": "stopCampaign", - "fingerprint": "a0d01a317ec9af4c35a3c0376ca5cfb2711fee86395c0b51d792bb3fdaf171d9", + "method": "PATCH", + "path": "/quicklink/{token}/testing", + "operationId": "customizeQuickLinkTesting", + "fingerprint": "ac8d711fe5e8615e72c66c57c2a0d3ceb6bdaa4f2010e422b95cd7d87893ebeb", "typescript": { - "status": "covered", - "method": "MessagingClient.campaigns.stop" + "status": "excluded", + "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." } }, { "family": "messaging", "method": "POST", - "path": "/messaging/projects/{projectSlug}/templates", - "operationId": "createProjectTemplate", - "fingerprint": "936c6df8cfafdd96f4a330f7fbd889c11df79bb5d14e631da7ee43700bb078f0", + "path": "/messaging/{session}/business/catalog", + "operationId": "createBusinessCatalog", + "fingerprint": "492ce22ee2cd5795b31e5dc60eeec4b6cb86ba2a5ba404e211398f6b111a7f8d", "typescript": { "status": "covered", - "method": "MessagingClient.templates.create" + "method": "MessagingClient.business.createCatalog" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/projects/{projectSlug}/templates/{id}/preview", - "operationId": "previewProjectTemplate", - "fingerprint": "9e7d6a15b5d27ac8493101c2e3a2adca77a50e35b11eda618a3035d2b98aadd4", + "path": "/messaging/{session}/business/collections", + "operationId": "createBusinessCollection", + "fingerprint": "34a287fc77e6cf3a9eb5eb9d5610e1f8bfce6aedf0543e9b3e5414d0940db285", "typescript": { "status": "covered", - "method": "MessagingClient.templates.preview" + "method": "MessagingClient.business.createCollection" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/projects/{projectSlug}/templates/{id}/submit", - "operationId": "submitProjectTemplateToMeta", - "fingerprint": "00cd9c4e7eeac56d0d6cd86ea3efc1c88fb83a571ddae795beb9304b0f7ce9e8", + "path": "/messaging/{session}/business/collections/{collectionId}/appeal", + "operationId": "appealBusinessCollection", + "fingerprint": "4c9971ad30477d18faec7f3572b92e8a3df0ba90a3e76674434311d41a2211ce", "typescript": { "status": "covered", - "method": "MessagingClient.templates.submit" + "method": "MessagingClient.business.appealCollection" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/quicklinks", - "operationId": "createQuickLink", - "fingerprint": "94c4aa0a9990e515ccc3d5f76569a9499d540bfd721583d39c2802a41acbd8af", + "path": "/messaging/{session}/business/collections/reorder", + "operationId": "reorderBusinessCollections", + "fingerprint": "a8bd61e7f927a2425095b292c09b0fa21da590d6d77d15d4cc3ed7e8ccb8e6ec", "typescript": { "status": "covered", - "method": "MessagingClient.quickLinks.create" + "method": "MessagingClient.business.reorderCollections" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/testing/{projectId}/events", - "operationId": "triggerTestEvent", - "fingerprint": "c2d5c723923084c9fcca1be246f046b23d6a613eae5b6e25b71c2e6df462ec55", + "path": "/messaging/{session}/business/orders/{orderId}/lookup", + "operationId": "getBusinessOrder", + "fingerprint": "106d41246053f153fcd0f3eac03c9e5ebf7dfaba4289bd493f281bf4e933d8ea", "typescript": { "status": "covered", - "method": "MessagingClient.testing.triggerEvent" + "method": "MessagingClient.business.getOrder" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/testing/{projectId}/history-fixtures", - "operationId": "createTestingHistoryFixture", - "fingerprint": "8941f260e0e4156976fe76ca2ce3545dbf2ff0a0a909bee938687e201cdfac65", + "path": "/messaging/{session}/business/products", + "operationId": "createBusinessProduct", + "fingerprint": "22eb1b9ce7e50f80648ca2c11d344a72feb41d58d9e34153b3cea2524203578e", "typescript": { "status": "covered", - "method": "MessagingClient.testing.createHistoryFixture" + "method": "MessagingClient.business.createProduct" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/call-links", - "operationId": "voipCreateCallLink", - "fingerprint": "04411c20a11706c5b46412e10e01b45ed232ee74c8f4eec04fc9bc0ec1da59f0", + "path": "/messaging/{session}/business/products/{productId}/appeal", + "operationId": "appealBusinessProduct", + "fingerprint": "7c4e0e5de3092be7127ea5bd181d1a54f20f4999f095c79c8824d5402e2d2383", "typescript": { "status": "covered", - "method": "MessagingClient.voip.createCallLink" + "method": "MessagingClient.business.appealProduct" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/call-links/preview", - "operationId": "voipPreviewCallLink", - "fingerprint": "2190c289fd89e5ff99ad1510dd7e1d22daa4d2516dbb89620d5adfe6e74b103b", + "path": "/messaging/{session}/business/quick-replies", + "operationId": "createBusinessQuickReply", + "fingerprint": "41f7a94afd0cf6d62ef0a51fee5c5ae1c66eba3d81f4a41c435faba2424518ea", "typescript": { "status": "covered", - "method": "MessagingClient.voip.previewCallLink" + "method": "MessagingClient.quickReplies.create" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls", - "operationId": "voipPlaceCall", - "fingerprint": "ac30de149318e3cc2b62e5a348ff47379dfe896043993beb2225a14203be7ac0", + "path": "/messaging/{session}/calls/{callId}/reject", + "operationId": "rejectCall", + "fingerprint": "5efab538bb81e17b53f9511eee7408fc32e29573b0d743a03072138398362718", "typescript": { "status": "covered", - "method": "MessagingClient.voip.place" + "method": "MessagingClient.calls.reject" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/check", - "operationId": "checkCall", - "fingerprint": "6e8a94f6264018360e5dc83c606bc62ec70945ec15e2ab1665977b7172be21ca", + "path": "/messaging/{session}/channels", + "operationId": "createChannel", + "fingerprint": "4b8c8380a142c78d9638628f896bdc5bd39ccb4bcd9324471bc1bda320271401", "typescript": { "status": "covered", - "method": "MessagingClient.voip.check" + "method": "MessagingClient.channels.create" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/accept", - "operationId": "voipAcceptCall", - "fingerprint": "130015311b8f5ebe5761358a94873dd7a59bd621f1b819e5ea11b093ce8dfe16", + "path": "/messaging/{session}/channels/{id}/follow", + "operationId": "followChannel", + "fingerprint": "5c6813284149fb816f01e4afd77a0c2f15d40c77ca83697c951c17edea738ff2", "typescript": { "status": "covered", - "method": "MessagingClient.voip.accept" + "method": "MessagingClient.channels.follow" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/candidate", - "operationId": "voipCandidate", - "fingerprint": "b62c06c6f2a2b7205f64971689ac5465e13e9d67a98991a11ac45c023aedab14", + "path": "/messaging/{session}/channels/{id}/live-updates", + "operationId": "subscribeChannelLiveUpdates", + "fingerprint": "073cab21c20c49e1d87aaf4ce6e2783c2933861e3bb41c513e379fbc161da81a", "typescript": { - "status": "excluded", - "reason": "used internally by the Calls SDK; not part of the public API" + "status": "covered", + "method": "MessagingClient.channels.subscribeToLiveUpdates" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/hand", - "operationId": "setCallHandRaised", - "fingerprint": "7424eaf9d824d7ef4e45da083bc9920ff48b5d38046c81d71212038321d45b00", + "path": "/messaging/{session}/channels/{id}/messages/{messageId}/reaction", + "operationId": "reactToChannelMessage", + "fingerprint": "5fecaa876e052c7425c467ba1eaabb4a63276bf074404ab39cef1c3f2f38d3f3", "typescript": { "status": "covered", - "method": "MessagingClient.voip.setHandRaised" + "method": "MessagingClient.channels.reactToMessage" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/leave", - "operationId": "voipLeaveCall", - "fingerprint": "9ffa810ac8871a72e020a7d89a27d79e1149bfe9f29004c2ec6122bc21c367b5", + "path": "/messaging/{session}/channels/{id}/messages/{messageId}/viewed", + "operationId": "markChannelMessageViewed", + "fingerprint": "72b0f69a45de94468e68e94113862af7306ab5807839df3f81bd06bef60d7ce9", "typescript": { "status": "covered", - "method": "MessagingClient.voip.leave" + "method": "MessagingClient.channels.markMessageViewed" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/offer", - "operationId": "voipOffer", - "fingerprint": "bba631d552cf007e61d8319678e18e6bd59bb71a2db0ea08b37db228d4ac6535", + "path": "/messaging/{session}/channels/{id}/mute", + "operationId": "muteChannel", + "fingerprint": "e63daebd213dccf2a45a5e4439c448593f1c7da06622571302a18b08cfa77d20", "typescript": { - "status": "excluded", - "reason": "used internally by the Calls SDK; not part of the public API" + "status": "covered", + "method": "MessagingClient.channels.mute" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/participants", - "operationId": "voipAddParticipant", - "fingerprint": "96e4a107ae55dcaf974cf248adb2c5f80c27cf19b01bd4472ec5fa465bcb3027", - "typescript": { + "path": "/messaging/{session}/channels/{id}/unfollow", + "operationId": "unfollowChannel", + "fingerprint": "d9c172238d4af69496d1b49bced2c6a088fca286633fb4e5f6fbb0e8ad540216", + "typescript": { "status": "covered", - "method": "MessagingClient.voip.addParticipant" + "method": "MessagingClient.channels.unfollow" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/participants/ring", - "operationId": "voipRingParticipant", - "fingerprint": "3affbe572fe39e4deb4e534351ea8d84e180b38fa5dfe4de444b631144e498e1", + "path": "/messaging/{session}/channels/{id}/unmute", + "operationId": "unmuteChannel", + "fingerprint": "3b7c9f177c8a0ea2a130e562cca735584fe979336d0c040219484aaea301aa44", "typescript": { "status": "covered", - "method": "MessagingClient.voip.ringParticipant" + "method": "MessagingClient.channels.unmute" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/reaction", - "operationId": "sendCallReaction", - "fingerprint": "223b377e6dc6c3b7d68116d6ab31577a12e221ca67c15e86e436c475d6f68bb5", + "path": "/messaging/{session}/chats/{conversation}/archive", + "operationId": "archiveChat", + "fingerprint": "2ff7c852f590f6b574f53e9ae1e76a406a6d1f9dceb1340ceceeca9a37b755e7", "typescript": { "status": "covered", - "method": "MessagingClient.voip.sendReaction" + "method": "MessagingClient.chats.archive" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/reject", - "operationId": "voipRejectCall", - "fingerprint": "17ea93dcaa7037003244b35d8460658f6205dc57c83e77bf49b055486b92c49c", + "path": "/messaging/{session}/chats/{conversation}/unarchive", + "operationId": "unarchiveChat", + "fingerprint": "19b6919ef9d5d5223b6a9f73676817baeb160e356dc8efae702afe6a3077a2f7", "typescript": { "status": "covered", - "method": "MessagingClient.voip.reject" + "method": "MessagingClient.chats.unarchive" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/renegotiate", - "operationId": "voipRenegotiate", - "fingerprint": "2de5b0bb0cfaabd0acf80fb0da7023c3009e5e10a831b516975020f1dba6a827", + "path": "/messaging/{session}/cloud-credentials/reauthorize", + "operationId": "reauthorizeCloudCredentials", + "fingerprint": "7f3821497a6e34d6ba877972599af0f3774c669916007153cd27ec067cf324b1", "typescript": { - "status": "excluded", - "reason": "used internally by the Calls SDK; not part of the public API" + "status": "covered", + "method": "MessagingClient.sessions.reauthorizeCloudCredentials" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/voip/calls/{id}/reports", - "operationId": "voipReportCallDiagnostics", - "fingerprint": "ed63562b90979126af0948f92d52da3a1222ca2f9cb43b3a167a7888cf7fa696", + "path": "/messaging/{session}/contacts/{contactId}/block", + "operationId": "blockContact", + "fingerprint": "3b350c3a8e4d32547d15dc9c668382640eeeae83108daa7179a79b41312fd8e2", "typescript": { "status": "covered", - "method": "MessagingClient.voip.report" + "method": "MessagingClient.contacts.block" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/webhooks", - "operationId": "createWebhook", - "fingerprint": "32a27e6607c564443bd1e0fb7e89e310612ee4f401afa45d1ecba0ff34ea7a17", + "path": "/messaging/{session}/contacts/{contactId}/unblock", + "operationId": "unblockContact", + "fingerprint": "d1f1d46a5fc5cbca497dbc8f743371531eff354cbb36d9d5ba08023d52806e8d", "typescript": { "status": "covered", - "method": "MessagingClient.webhooks.create" + "method": "MessagingClient.contacts.unblock" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/business/catalog", - "operationId": "createBusinessCatalog", - "fingerprint": "492ce22ee2cd5795b31e5dc60eeec4b6cb86ba2a5ba404e211398f6b111a7f8d", + "path": "/messaging/{session}/groups", + "operationId": "createGroup", + "fingerprint": "0d7f044fcc06732034f90f298c747e8bee6411d183970025c1150135834a511e", "typescript": { "status": "covered", - "method": "MessagingClient.business.createCatalog" + "method": "MessagingClient.groups.create" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/business/collections", - "operationId": "createBusinessCollection", - "fingerprint": "34a287fc77e6cf3a9eb5eb9d5610e1f8bfce6aedf0543e9b3e5414d0940db285", + "path": "/messaging/{session}/groups/{id}/admin/demote", + "operationId": "demoteGroupAdmin", + "fingerprint": "5dec2848c4879f9a5bad3bad014f7a64001fe856163e115ab292fcab3fee2105", "typescript": { "status": "covered", - "method": "MessagingClient.business.createCollection" + "method": "MessagingClient.groups.demoteParticipants" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/business/collections/reorder", - "operationId": "reorderBusinessCollections", - "fingerprint": "a8bd61e7f927a2425095b292c09b0fa21da590d6d77d15d4cc3ed7e8ccb8e6ec", + "path": "/messaging/{session}/groups/{id}/admin/promote", + "operationId": "promoteGroupAdmin", + "fingerprint": "a9f0832dd57c23b323a3ed09ef0eee1fcdcb31afb8617599679ea0e867c429c8", "typescript": { "status": "covered", - "method": "MessagingClient.business.reorderCollections" + "method": "MessagingClient.groups.promoteParticipants" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/business/collections/{collectionId}/appeal", - "operationId": "appealBusinessCollection", - "fingerprint": "4c9971ad30477d18faec7f3572b92e8a3df0ba90a3e76674434311d41a2211ce", + "path": "/messaging/{session}/groups/{id}/invite-code/revoke", + "operationId": "revokeGroupInviteCode", + "fingerprint": "d55faab7f56439bd778502ff08f62971ef80cac6545e4666cfa4a169692eb913", "typescript": { "status": "covered", - "method": "MessagingClient.business.appealCollection" + "method": "MessagingClient.groups.revokeInviteCode" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/business/orders/{orderId}/lookup", - "operationId": "getBusinessOrder", - "fingerprint": "106d41246053f153fcd0f3eac03c9e5ebf7dfaba4289bd493f281bf4e933d8ea", + "path": "/messaging/{session}/groups/{id}/leave", + "operationId": "leaveGroup", + "fingerprint": "7eda94bd835007ba9f4edd698ab8f321f50c2e5b3d0b30a302b5d7737a8f6173", "typescript": { "status": "covered", - "method": "MessagingClient.business.getOrder" + "method": "MessagingClient.groups.leave" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/business/products", - "operationId": "createBusinessProduct", - "fingerprint": "22eb1b9ce7e50f80648ca2c11d344a72feb41d58d9e34153b3cea2524203578e", + "path": "/messaging/{session}/groups/{id}/participants/add", + "operationId": "addGroupParticipants", + "fingerprint": "45846fe0fffef9574dcb11494aef9ced9e551b591f02d707f61ec5eee5be3ed6", "typescript": { "status": "covered", - "method": "MessagingClient.business.createProduct" + "method": "MessagingClient.groups.addParticipants" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/business/products/{productId}/appeal", - "operationId": "appealBusinessProduct", - "fingerprint": "7c4e0e5de3092be7127ea5bd181d1a54f20f4999f095c79c8824d5402e2d2383", + "path": "/messaging/{session}/groups/{id}/participants/remove", + "operationId": "removeGroupParticipants", + "fingerprint": "bf5bcb26f39b951b5a1e95eb611b2060151c70a5783352c105460bfe39c6ea44", "typescript": { "status": "covered", - "method": "MessagingClient.business.appealProduct" + "method": "MessagingClient.groups.removeParticipants" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/business/quick-replies", - "operationId": "createBusinessQuickReply", - "fingerprint": "41f7a94afd0cf6d62ef0a51fee5c5ae1c66eba3d81f4a41c435faba2424518ea", + "path": "/messaging/{session}/groups/join", + "operationId": "joinGroup", + "fingerprint": "deb36fd8fc5d14d5859b24eabdcd2b74c26ec197cbce2da6fd75753985eb7872", "typescript": { "status": "covered", - "method": "MessagingClient.quickReplies.create" + "method": "MessagingClient.groups.join" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/calls/{callId}/reject", - "operationId": "rejectCall", - "fingerprint": "5efab538bb81e17b53f9511eee7408fc32e29573b0d743a03072138398362718", + "path": "/messaging/{session}/labels", + "operationId": "createLabel", + "fingerprint": "cce3207fba180daa5d6d59445393a028dca243d7c088f1b38191dfc955e836d2", "typescript": { "status": "covered", - "method": "MessagingClient.calls.reject" + "method": "MessagingClient.labels.create" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/channels", - "operationId": "createChannel", - "fingerprint": "4b8c8380a142c78d9638628f896bdc5bd39ccb4bcd9324471bc1bda320271401", + "path": "/messaging/{session}/messages/react", + "operationId": "sendReaction", + "fingerprint": "3aa8ea0864263c964b4c96a3291687990b3f4a815f1463eab6c2cbc6f9a69eb0", "typescript": { "status": "covered", - "method": "MessagingClient.channels.create" + "method": "MessagingClient.messages.react" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/channels/{id}/follow", - "operationId": "followChannel", - "fingerprint": "5c6813284149fb816f01e4afd77a0c2f15d40c77ca83697c951c17edea738ff2", + "path": "/messaging/{session}/messages/seen", + "operationId": "sendSeen", + "fingerprint": "d1bdef25b5dbf6ec4590b89f0a81eb9e1da189db1a6b5890489b082b678bca0d", "typescript": { "status": "covered", - "method": "MessagingClient.channels.follow" + "method": "MessagingClient.messages.markSeen" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/channels/{id}/live-updates", - "operationId": "subscribeChannelLiveUpdates", - "fingerprint": "073cab21c20c49e1d87aaf4ce6e2783c2933861e3bb41c513e379fbc161da81a", + "path": "/messaging/{session}/messages/send", + "operationId": "sendMessage", + "fingerprint": "6f1f010da3c868de45de2936d88ad3ce1ebe09f216c1095fa38a509ca466596b", "typescript": { "status": "covered", - "method": "MessagingClient.channels.subscribeToLiveUpdates" + "method": "MessagingClient.messages.send" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/channels/{id}/messages/{messageId}/reaction", - "operationId": "reactToChannelMessage", - "fingerprint": "5fecaa876e052c7425c467ba1eaabb4a63276bf074404ab39cef1c3f2f38d3f3", + "path": "/messaging/{session}/messages/star", + "operationId": "starMessage", + "fingerprint": "af6ecdbc36153a3fea61bc00e07a8cf6b14df776be52e128974819ca065fc000", "typescript": { "status": "covered", - "method": "MessagingClient.channels.reactToMessage" + "method": "MessagingClient.messages.star" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/channels/{id}/messages/{messageId}/viewed", - "operationId": "markChannelMessageViewed", - "fingerprint": "72b0f69a45de94468e68e94113862af7306ab5807839df3f81bd06bef60d7ce9", + "path": "/messaging/{session}/messages/typing", + "operationId": "setTyping", + "fingerprint": "6a810fb5a05be055f6cdf9930a70f7db8ed3e5997811633c77216a48480a461f", "typescript": { "status": "covered", - "method": "MessagingClient.channels.markMessageViewed" + "method": "MessagingClient.messages.setTyping" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/channels/{id}/mute", - "operationId": "muteChannel", - "fingerprint": "e63daebd213dccf2a45a5e4439c448593f1c7da06622571302a18b08cfa77d20", + "path": "/messaging/{session}/official-groups", + "operationId": "createOfficialGroup", + "fingerprint": "d1e4db2c30f786ebe5338eda8c74f321c6206acfd048e79ed71d0ad050a96be5", "typescript": { "status": "covered", - "method": "MessagingClient.channels.mute" + "method": "MessagingClient.officialGroups.create" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/channels/{id}/unfollow", - "operationId": "unfollowChannel", - "fingerprint": "d9c172238d4af69496d1b49bced2c6a088fca286633fb4e5f6fbb0e8ad540216", + "path": "/messaging/{session}/official-groups/{group}/invite-link/reset", + "operationId": "resetOfficialGroupInviteLink", + "fingerprint": "33f0f394382de3ad8395001e8804f3b23ffee1c3f2fd9e299d5392389d444ef8", "typescript": { "status": "covered", - "method": "MessagingClient.channels.unfollow" + "method": "MessagingClient.officialGroups.resetInviteLink" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/channels/{id}/unmute", - "operationId": "unmuteChannel", - "fingerprint": "3b7c9f177c8a0ea2a130e562cca735584fe979336d0c040219484aaea301aa44", + "path": "/messaging/{session}/official-groups/{group}/join-requests/approve", + "operationId": "approveOfficialGroupJoinRequests", + "fingerprint": "688ed0fe59dfc17920ac62348847995a1aaf445b1f095203bea29d68501c34f5", "typescript": { "status": "covered", - "method": "MessagingClient.channels.unmute" + "method": "MessagingClient.officialGroups.approveJoinRequests" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/chats/{conversation}/archive", - "operationId": "archiveChat", - "fingerprint": "2ff7c852f590f6b574f53e9ae1e76a406a6d1f9dceb1340ceceeca9a37b755e7", + "path": "/messaging/{session}/official-groups/{group}/join-requests/reject", + "operationId": "rejectOfficialGroupJoinRequests", + "fingerprint": "4eb0912a02286ad1a1b1906a269dd20dfc191bbd545233ccc2eff7df6736fa3a", "typescript": { "status": "covered", - "method": "MessagingClient.chats.archive" + "method": "MessagingClient.officialGroups.rejectJoinRequests" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/chats/{conversation}/unarchive", - "operationId": "unarchiveChat", - "fingerprint": "19b6919ef9d5d5223b6a9f73676817baeb160e356dc8efae702afe6a3077a2f7", + "path": "/messaging/{session}/official-groups/{group}/participants/remove", + "operationId": "removeOfficialGroupParticipants", + "fingerprint": "eb56e9668c8095c2fa7338abdc262a9ea36e927a038099ba3f0881f0c88ad592", "typescript": { "status": "covered", - "method": "MessagingClient.chats.unarchive" + "method": "MessagingClient.officialGroups.removeParticipants" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/cloud-credentials/reauthorize", - "operationId": "reauthorizeCloudCredentials", - "fingerprint": "7f3821497a6e34d6ba877972599af0f3774c669916007153cd27ec067cf324b1", + "path": "/messaging/{session}/official-groups/{group}/pins", + "operationId": "pinOfficialGroupMessage", + "fingerprint": "3c37d374822bbf2dbd4cbadf5bde4dc26d0ad46fbe11b2432046b5587e3f594b", "typescript": { "status": "covered", - "method": "MessagingClient.sessions.reauthorizeCloudCredentials" + "method": "MessagingClient.officialGroups.pin" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/contacts/{contactId}/block", - "operationId": "blockContact", - "fingerprint": "3b350c3a8e4d32547d15dc9c668382640eeeae83108daa7179a79b41312fd8e2", + "path": "/messaging/{session}/pair/code", + "operationId": "requestPairCode", + "fingerprint": "43bc4544837be33e3832c7c9bf8ab7702079d8ac0b64301f5f41b0cc295a3920", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.block" + "method": "MessagingClient.sessions.requestPairingCode" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/contacts/{contactId}/unblock", - "operationId": "unblockContact", - "fingerprint": "d1f1d46a5fc5cbca497dbc8f743371531eff354cbb36d9d5ba08023d52806e8d", + "path": "/messaging/{session}/presence", + "operationId": "setPresence", + "fingerprint": "184e5642e0d56f2f9da4c01d76b2dc8a655c7944e30d8d618351d5869e6609cd", "typescript": { "status": "covered", - "method": "MessagingClient.contacts.unblock" + "method": "MessagingClient.presence.set" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/groups", - "operationId": "createGroup", - "fingerprint": "0d7f044fcc06732034f90f298c747e8bee6411d183970025c1150135834a511e", + "path": "/messaging/{session}/presence/{conversation}/subscribe", + "operationId": "subscribePresence", + "fingerprint": "f8d9ecf7027e3cde839fde0c2c3d1b1c784a984c219d085c01e3db9399c5d58b", "typescript": { "status": "covered", - "method": "MessagingClient.groups.create" + "method": "MessagingClient.presence.subscribe" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/groups/join", - "operationId": "joinGroup", - "fingerprint": "deb36fd8fc5d14d5859b24eabdcd2b74c26ec197cbce2da6fd75753985eb7872", + "path": "/messaging/{session}/templates", + "operationId": "createTemplate", + "fingerprint": "929a65c1fd7c48e836fd607be5fcf83930140de25adf53d93fa59ee2d07ed528", "typescript": { "status": "covered", - "method": "MessagingClient.groups.join" + "method": "MessagingClient.cloudTemplates.create" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/groups/{id}/admin/demote", - "operationId": "demoteGroupAdmin", - "fingerprint": "5dec2848c4879f9a5bad3bad014f7a64001fe856163e115ab292fcab3fee2105", + "path": "/messaging/cloud-api/embedded-signup", + "operationId": "embeddedSignup", + "fingerprint": "8fa663a365c745cf62d7c6bdce11dfe5a9d3a6f93a13494bd5fb44a2fde3cd29", "typescript": { "status": "covered", - "method": "MessagingClient.groups.demoteParticipants" + "method": "MessagingClient.cloudOnboarding.advance" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/groups/{id}/admin/promote", - "operationId": "promoteGroupAdmin", - "fingerprint": "a9f0832dd57c23b323a3ed09ef0eee1fcdcb31afb8617599679ea0e867c429c8", + "path": "/messaging/features/evaluate", + "operationId": "evaluateFeatures", + "fingerprint": "ce87686ff3267c65f17fb77d389f7aa1d6464a030965704256a946e586a94d12", "typescript": { - "status": "covered", - "method": "MessagingClient.groups.promoteParticipants" + "status": "excluded", + "reason": "Host-runtime feature evaluation is not a customer messaging operation in this SDK." } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/groups/{id}/invite-code/revoke", - "operationId": "revokeGroupInviteCode", - "fingerprint": "d55faab7f56439bd778502ff08f62971ef80cac6545e4666cfa4a169692eb913", + "path": "/messaging/media/{id}/download-and-save", + "operationId": "downloadAndSaveMedia", + "fingerprint": "018c0346629ae122dfdae54a6c5521eb0616ad5368bb1555b634edfa7caf79e5", "typescript": { "status": "covered", - "method": "MessagingClient.groups.revokeInviteCode" + "method": "MessagingClient.media.persist" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/groups/{id}/leave", - "operationId": "leaveGroup", - "fingerprint": "7eda94bd835007ba9f4edd698ab8f321f50c2e5b3d0b30a302b5d7737a8f6173", + "path": "/messaging/projects/{projectSlug}/campaigns", + "operationId": "createCampaign", + "fingerprint": "cd375472bffd91aef28f0a5be4e96892edb2eae2369e1fe1a75d4e4ff1972225", "typescript": { "status": "covered", - "method": "MessagingClient.groups.leave" + "method": "MessagingClient.campaigns.create" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/groups/{id}/participants/add", - "operationId": "addGroupParticipants", - "fingerprint": "45846fe0fffef9574dcb11494aef9ced9e551b591f02d707f61ec5eee5be3ed6", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}/launch", + "operationId": "launchCampaign", + "fingerprint": "58b2e466972439bedb0af24c559b0f0db025819cb2a807313156159b8e4a59b0", "typescript": { "status": "covered", - "method": "MessagingClient.groups.addParticipants" + "method": "MessagingClient.campaigns.launch" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/groups/{id}/participants/remove", - "operationId": "removeGroupParticipants", - "fingerprint": "bf5bcb26f39b951b5a1e95eb611b2060151c70a5783352c105460bfe39c6ea44", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}/pause", + "operationId": "pauseCampaign", + "fingerprint": "b3000027edea590f0ebd2a50ede679659867ceba58edbe6be0624c9cc566ce20", "typescript": { "status": "covered", - "method": "MessagingClient.groups.removeParticipants" + "method": "MessagingClient.campaigns.pause" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/labels", - "operationId": "createLabel", - "fingerprint": "cce3207fba180daa5d6d59445393a028dca243d7c088f1b38191dfc955e836d2", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}/recipients", + "operationId": "addProjectCampaignRecipients", + "fingerprint": "b01f480367a45722d420ef6d31ba0bc712e00696fe4f4fc802d94840b2f7aadf", "typescript": { "status": "covered", - "method": "MessagingClient.labels.create" + "method": "MessagingClient.campaigns.addRecipients" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/messages/react", - "operationId": "sendReaction", - "fingerprint": "3aa8ea0864263c964b4c96a3291687990b3f4a815f1463eab6c2cbc6f9a69eb0", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}/requeue", + "operationId": "requeueCampaign", + "fingerprint": "c6e1f0278f461a0107f24263d9a6a73a698313b8dcacb9f7ebbbcba78f7f2cb5", "typescript": { "status": "covered", - "method": "MessagingClient.messages.react" + "method": "MessagingClient.campaigns.requeue" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/messages/seen", - "operationId": "sendSeen", - "fingerprint": "d1bdef25b5dbf6ec4590b89f0a81eb9e1da189db1a6b5890489b082b678bca0d", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}/reschedule", + "operationId": "rescheduleCampaign", + "fingerprint": "9b96f4321f98a3758c1b00b87a897bf5d52a48e78df99f40fab9324f6ec148ea", + "typescript": { + "status": "excluded", + "reason": "Campaign reschedule is owned by SDK PR #310 and will be covered when that PR merges." + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}/resume", + "operationId": "resumeCampaign", + "fingerprint": "0bcbd931ce0857076821b1de9ae28cd55964ee549ddd448082d2bb972c8595c6", "typescript": { "status": "covered", - "method": "MessagingClient.messages.markSeen" + "method": "MessagingClient.campaigns.resume" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/messages/send", - "operationId": "sendMessage", - "fingerprint": "6f1f010da3c868de45de2936d88ad3ce1ebe09f216c1095fa38a509ca466596b", + "path": "/messaging/projects/{projectSlug}/campaigns/{id}/stop", + "operationId": "stopCampaign", + "fingerprint": "a0d01a317ec9af4c35a3c0376ca5cfb2711fee86395c0b51d792bb3fdaf171d9", "typescript": { "status": "covered", - "method": "MessagingClient.messages.send" + "method": "MessagingClient.campaigns.stop" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/messages/star", - "operationId": "starMessage", - "fingerprint": "af6ecdbc36153a3fea61bc00e07a8cf6b14df776be52e128974819ca065fc000", + "path": "/messaging/projects/{projectSlug}/templates", + "operationId": "createProjectTemplate", + "fingerprint": "936c6df8cfafdd96f4a330f7fbd889c11df79bb5d14e631da7ee43700bb078f0", "typescript": { "status": "covered", - "method": "MessagingClient.messages.star" + "method": "MessagingClient.templates.create" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/messages/typing", - "operationId": "setTyping", - "fingerprint": "6a810fb5a05be055f6cdf9930a70f7db8ed3e5997811633c77216a48480a461f", + "path": "/messaging/projects/{projectSlug}/templates/{id}/preview", + "operationId": "previewProjectTemplate", + "fingerprint": "9e7d6a15b5d27ac8493101c2e3a2adca77a50e35b11eda618a3035d2b98aadd4", "typescript": { "status": "covered", - "method": "MessagingClient.messages.setTyping" + "method": "MessagingClient.templates.preview" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/pair/code", - "operationId": "requestPairCode", - "fingerprint": "43bc4544837be33e3832c7c9bf8ab7702079d8ac0b64301f5f41b0cc295a3920", + "path": "/messaging/projects/{projectSlug}/templates/{id}/submit", + "operationId": "submitProjectTemplateToMeta", + "fingerprint": "00cd9c4e7eeac56d0d6cd86ea3efc1c88fb83a571ddae795beb9304b0f7ce9e8", "typescript": { "status": "covered", - "method": "MessagingClient.sessions.requestPairingCode" + "method": "MessagingClient.templates.submit" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/presence", - "operationId": "setPresence", - "fingerprint": "184e5642e0d56f2f9da4c01d76b2dc8a655c7944e30d8d618351d5869e6609cd", + "path": "/messaging/quicklinks", + "operationId": "createQuickLink", + "fingerprint": "2b3dcf973037ae96f17743d781ce68e162e69d69a119f2aaf2ee5ebbe2e056bd", "typescript": { "status": "covered", - "method": "MessagingClient.presence.set" + "method": "MessagingClient.quickLinks.create" } }, { "family": "messaging", "method": "POST", - "path": "/messaging/{session}/presence/{conversation}/subscribe", - "operationId": "subscribePresence", - "fingerprint": "f8d9ecf7027e3cde839fde0c2c3d1b1c784a984c219d085c01e3db9399c5d58b", + "path": "/messaging/testing/{projectId}/events", + "operationId": "triggerTestEvent", + "fingerprint": "c2d5c723923084c9fcca1be246f046b23d6a613eae5b6e25b71c2e6df462ec55", "typescript": { "status": "covered", - "method": "MessagingClient.presence.subscribe" + "method": "MessagingClient.testing.triggerEvent" } }, { "family": "messaging", "method": "POST", - "path": "/quicklink/{token}/cancel", - "operationId": "cancelQuickLinkByToken", - "fingerprint": "01e0c1827929a3a5c5baf2ed7abadf859d36159dc257a20c98df1d9b6f3e2f8b", + "path": "/messaging/testing/{projectId}/history-fixtures", + "operationId": "createTestingHistoryFixture", + "fingerprint": "8941f260e0e4156976fe76ca2ce3545dbf2ff0a0a909bee938687e201cdfac65", "typescript": { - "status": "excluded", - "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", - "milestone": "not-server-sdk" + "status": "covered", + "method": "MessagingClient.testing.createHistoryFixture" } }, { "family": "messaging", "method": "POST", - "path": "/quicklink/{token}/cloud", - "operationId": "completeQuickLinkCloudSignup", - "fingerprint": "4737cd1a70237a10a2fd46b339cefde693e064644b61e0d2874f8ae2e7060bb1", + "path": "/messaging/voip/call-links", + "operationId": "voipCreateCallLink", + "fingerprint": "04411c20a11706c5b46412e10e01b45ed232ee74c8f4eec04fc9bc0ec1da59f0", "typescript": { - "status": "excluded", - "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." + "status": "covered", + "method": "MessagingClient.voip.createCallLink" } }, { "family": "messaging", "method": "POST", - "path": "/quicklink/{token}/code", - "operationId": "requestQuickLinkCode", - "fingerprint": "4760de4a52464d5f9b6c524694dd57798fef13fed80e4be28810021573cbedb9", + "path": "/messaging/voip/call-links/preview", + "operationId": "voipPreviewCallLink", + "fingerprint": "2190c289fd89e5ff99ad1510dd7e1d22daa4d2516dbb89620d5adfe6e74b103b", "typescript": { - "status": "excluded", - "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", - "milestone": "not-server-sdk" + "status": "covered", + "method": "MessagingClient.voip.previewCallLink" } }, { "family": "messaging", "method": "POST", - "path": "/quicklink/{token}/confirm", - "operationId": "confirmQuickLink", - "fingerprint": "91be047b4af0855599d1e0c80c57511e6e771fb71a5cb33ca3a5bb86b7f91a2b", + "path": "/messaging/voip/calls", + "operationId": "voipPlaceCall", + "fingerprint": "ac30de149318e3cc2b62e5a348ff47379dfe896043993beb2225a14203be7ac0", "typescript": { - "status": "excluded", - "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", - "milestone": "not-server-sdk" + "status": "covered", + "method": "MessagingClient.voip.place" } }, { "family": "messaging", "method": "POST", - "path": "/quicklink/{token}/connection", - "operationId": "selectQuickLinkConnection", - "fingerprint": "c33269e7e8020f5a24d7edea80025c27c6cbd72a8fae197e6a604a40e9f3c92e", + "path": "/messaging/voip/calls/{id}/accept", + "operationId": "voipAcceptCall", + "fingerprint": "130015311b8f5ebe5761358a94873dd7a59bd621f1b819e5ea11b093ce8dfe16", "typescript": { - "status": "excluded", - "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." + "status": "covered", + "method": "MessagingClient.voip.accept" } }, { "family": "messaging", "method": "POST", - "path": "/quicklink/{token}/history", - "operationId": "chooseQuickLinkHistory", - "fingerprint": "05ebbba1d760f03fcf03d3216ab895f33e13396f98ccc7be55bc79667745ecc2", + "path": "/messaging/voip/calls/{id}/candidate", + "operationId": "voipCandidate", + "fingerprint": "b62c06c6f2a2b7205f64971689ac5465e13e9d67a98991a11ac45c023aedab14", "typescript": { "status": "excluded", - "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." + "reason": "used internally by the Calls SDK; not part of the public API" } }, { "family": "messaging", "method": "POST", - "path": "/quicklink/{token}/phone", - "operationId": "confirmQuickLinkPhone", - "fingerprint": "4a28673d52f6b1a93649c08e94e930e4d6d6b57898b2fd8d7e16200c5221208c", + "path": "/messaging/voip/calls/{id}/hand", + "operationId": "setCallHandRaised", + "fingerprint": "7424eaf9d824d7ef4e45da083bc9920ff48b5d38046c81d71212038321d45b00", "typescript": { - "status": "excluded", - "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." + "status": "covered", + "method": "MessagingClient.voip.setHandRaised" } }, { "family": "messaging", "method": "POST", - "path": "/quicklink/{token}/phone/reset", - "operationId": "resetQuickLinkPhone", - "fingerprint": "ce13c7b679d3cd62f740ff8231e3f682020d2f86f053a246b489050b22aeecaf", + "path": "/messaging/voip/calls/{id}/leave", + "operationId": "voipLeaveCall", + "fingerprint": "9ffa810ac8871a72e020a7d89a27d79e1149bfe9f29004c2ec6122bc21c367b5", "typescript": { - "status": "excluded", - "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." + "status": "covered", + "method": "MessagingClient.voip.leave" } }, { "family": "messaging", "method": "POST", - "path": "/quicklink/{token}/testing/start", - "operationId": "startQuickLinkTesting", - "fingerprint": "a928d2923438e0c3f593cda298df36727b5f010f8af84c9b73579385a12ccdb9", + "path": "/messaging/voip/calls/{id}/offer", + "operationId": "voipOffer", + "fingerprint": "bba631d552cf007e61d8319678e18e6bd59bb71a2db0ea08b37db228d4ac6535", "typescript": { "status": "excluded", - "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." + "reason": "used internally by the Calls SDK; not part of the public API" } }, { "family": "messaging", - "method": "PUT", - "path": "/messaging/projects/{projectId}/health-policy", - "operationId": "updateProjectHealthPolicy", - "fingerprint": "79da5ec255f53f91ffbebfc06f33e6dc631938f7d1cd9ab6abe088eb315ff41b", + "method": "POST", + "path": "/messaging/voip/calls/{id}/participants", + "operationId": "voipAddParticipant", + "fingerprint": "96e4a107ae55dcaf974cf248adb2c5f80c27cf19b01bd4472ec5fa465bcb3027", "typescript": { "status": "covered", - "method": "MessagingClient.banSafe.updateProjectHealthPolicy" + "method": "MessagingClient.voip.addParticipant" } }, { "family": "messaging", - "method": "PUT", - "path": "/messaging/projects/{projectId}/insurance-evidence", - "operationId": "updateProjectInsuranceEvidence", - "fingerprint": "fae516839029212fdf7c8c0f0683d03e900fa4e04e5b53cc566a0b0e5ee93ea6", + "method": "POST", + "path": "/messaging/voip/calls/{id}/participants/ring", + "operationId": "voipRingParticipant", + "fingerprint": "3affbe572fe39e4deb4e534351ea8d84e180b38fa5dfe4de444b631144e498e1", "typescript": { "status": "covered", - "method": "MessagingClient.banSafe.updateProjectInsuranceEvidence" + "method": "MessagingClient.voip.ringParticipant" } }, { "family": "messaging", - "method": "PUT", - "path": "/messaging/projects/{projectId}/safe-mode", - "operationId": "updateProjectSafeMode", - "fingerprint": "a7e98580a55db96e737ae0e8338012194fbca16ac1ce6776377d8d777abd9fca", + "method": "POST", + "path": "/messaging/voip/calls/{id}/reaction", + "operationId": "sendCallReaction", + "fingerprint": "223b377e6dc6c3b7d68116d6ab31577a12e221ca67c15e86e436c475d6f68bb5", "typescript": { "status": "covered", - "method": "MessagingClient.banSafe.updateProjectSafeMode" + "method": "MessagingClient.voip.sendReaction" } }, { "family": "messaging", - "method": "PUT", - "path": "/messaging/projects/{projectId}/warmup-plan", - "operationId": "updateProjectWarmupPlan", - "fingerprint": "1d45729a9097fb9a920474d48f32bccae8b3dcc41d65121a75727d1f1558a9fa", + "method": "POST", + "path": "/messaging/voip/calls/{id}/reject", + "operationId": "voipRejectCall", + "fingerprint": "17ea93dcaa7037003244b35d8460658f6205dc57c83e77bf49b055486b92c49c", "typescript": { "status": "covered", - "method": "MessagingClient.banSafe.updateProjectWarmupPlan" + "method": "MessagingClient.voip.reject" } }, { "family": "messaging", - "method": "PUT", - "path": "/messaging/routing/hybrid", - "operationId": "setHybridRoutingPolicy", - "fingerprint": "1569efd3ac10a5114edbb7716b0aa24b748ba19c65d6844c57a651fe990191a5", + "method": "POST", + "path": "/messaging/voip/calls/{id}/renegotiate", + "operationId": "voipRenegotiate", + "fingerprint": "2de5b0bb0cfaabd0acf80fb0da7023c3009e5e10a831b516975020f1dba6a827", + "typescript": { + "status": "excluded", + "reason": "used internally by the Calls SDK; not part of the public API" + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/messaging/voip/calls/{id}/reports", + "operationId": "voipReportCallDiagnostics", + "fingerprint": "ed63562b90979126af0948f92d52da3a1222ca2f9cb43b3a167a7888cf7fa696", "typescript": { "status": "covered", - "method": "MessagingClient.hybridLink.setPolicy" + "method": "MessagingClient.voip.report" } }, { "family": "messaging", - "method": "PUT", - "path": "/messaging/webhooks/{id}", - "operationId": "updateWebhook", - "fingerprint": "5607f5be756b957f350610acd96c8470e5967201e8b02b4808ce7e9b5915de05", + "method": "POST", + "path": "/messaging/voip/calls/check", + "operationId": "checkCall", + "fingerprint": "6e8a94f6264018360e5dc83c606bc62ec70945ec15e2ab1665977b7172be21ca", "typescript": { "status": "covered", - "method": "MessagingClient.webhooks.update" + "method": "MessagingClient.voip.check" + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/messaging/webhooks", + "operationId": "createWebhook", + "fingerprint": "32a27e6607c564443bd1e0fb7e89e310612ee4f401afa45d1ecba0ff34ea7a17", + "typescript": { + "status": "covered", + "method": "MessagingClient.webhooks.create" + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/cancel", + "operationId": "cancelQuickLinkByToken", + "fingerprint": "01e0c1827929a3a5c5baf2ed7abadf859d36159dc257a20c98df1d9b6f3e2f8b", + "typescript": { + "status": "excluded", + "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", + "milestone": "not-server-sdk" + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/cloud", + "operationId": "completeQuickLinkCloudSignup", + "fingerprint": "4737cd1a70237a10a2fd46b339cefde693e064644b61e0d2874f8ae2e7060bb1", + "typescript": { + "status": "excluded", + "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/code", + "operationId": "requestQuickLinkCode", + "fingerprint": "4760de4a52464d5f9b6c524694dd57798fef13fed80e4be28810021573cbedb9", + "typescript": { + "status": "excluded", + "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", + "milestone": "not-server-sdk" + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/confirm", + "operationId": "confirmQuickLink", + "fingerprint": "91be047b4af0855599d1e0c80c57511e6e771fb71a5cb33ca3a5bb86b7f91a2b", + "typescript": { + "status": "excluded", + "reason": "Credential-free public QuickLink participant route used only by the hosted QuickLink page, not the SDKs.", + "milestone": "not-server-sdk" + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/connection", + "operationId": "selectQuickLinkConnection", + "fingerprint": "c33269e7e8020f5a24d7edea80025c27c6cbd72a8fae197e6a604a40e9f3c92e", + "typescript": { + "status": "excluded", + "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/history", + "operationId": "chooseQuickLinkHistory", + "fingerprint": "05ebbba1d760f03fcf03d3216ab895f33e13396f98ccc7be55bc79667745ecc2", + "typescript": { + "status": "excluded", + "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/phone", + "operationId": "confirmQuickLinkPhone", + "fingerprint": "4a28673d52f6b1a93649c08e94e930e4d6d6b57898b2fd8d7e16200c5221208c", + "typescript": { + "status": "excluded", + "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/phone/reset", + "operationId": "resetQuickLinkPhone", + "fingerprint": "ce13c7b679d3cd62f740ff8231e3f682020d2f86f053a246b489050b22aeecaf", + "typescript": { + "status": "excluded", + "reason": "Public onboarding capability route is owned by the hosted QuickLink flow, not a machine credential resource." + } + }, + { + "family": "messaging", + "method": "POST", + "path": "/quicklink/{token}/testing/start", + "operationId": "startQuickLinkTesting", + "fingerprint": "a928d2923438e0c3f593cda298df36727b5f010f8af84c9b73579385a12ccdb9", + "typescript": { + "status": "excluded", + "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." } }, { @@ -2191,17 +2290,6 @@ "method": "MessagingClient.hybridLink.setPaused" } }, - { - "family": "messaging", - "method": "PUT", - "path": "/messaging/{session}/labels/chats/{conversation}", - "operationId": "setChatLabels", - "fingerprint": "105154ac3876d2a78726c9f70227a4d360fcd6c84d7e0bff5a3d93efcf6b2bb0", - "typescript": { - "status": "covered", - "method": "MessagingClient.labels.replaceForChat" - } - }, { "family": "messaging", "method": "PUT", @@ -2216,12 +2304,12 @@ { "family": "messaging", "method": "PUT", - "path": "/messaging/{session}/privacy/disappearing/default", - "operationId": "setDefaultDisappearingTimer", - "fingerprint": "ec5d683979cbe513b7485cfbf6e27742ec95ea29266aca457cce0b9914b8d782", + "path": "/messaging/{session}/labels/chats/{conversation}", + "operationId": "setChatLabels", + "fingerprint": "105154ac3876d2a78726c9f70227a4d360fcd6c84d7e0bff5a3d93efcf6b2bb0", "typescript": { "status": "covered", - "method": "MessagingClient.privacy.setDefaultDisappearingTimer" + "method": "MessagingClient.labels.replaceForChat" } }, { @@ -2235,6 +2323,17 @@ "method": "MessagingClient.privacy.set" } }, + { + "family": "messaging", + "method": "PUT", + "path": "/messaging/{session}/privacy/disappearing/default", + "operationId": "setDefaultDisappearingTimer", + "fingerprint": "ec5d683979cbe513b7485cfbf6e27742ec95ea29266aca457cce0b9914b8d782", + "typescript": { + "status": "covered", + "method": "MessagingClient.privacy.setDefaultDisappearingTimer" + } + }, { "family": "messaging", "method": "PUT", @@ -2279,6 +2378,72 @@ "method": "MessagingClient.banSafe.updateSessionSafeMode" } }, + { + "family": "messaging", + "method": "PUT", + "path": "/messaging/projects/{projectId}/health-policy", + "operationId": "updateProjectHealthPolicy", + "fingerprint": "79da5ec255f53f91ffbebfc06f33e6dc631938f7d1cd9ab6abe088eb315ff41b", + "typescript": { + "status": "covered", + "method": "MessagingClient.banSafe.updateProjectHealthPolicy" + } + }, + { + "family": "messaging", + "method": "PUT", + "path": "/messaging/projects/{projectId}/insurance-evidence", + "operationId": "updateProjectInsuranceEvidence", + "fingerprint": "fae516839029212fdf7c8c0f0683d03e900fa4e04e5b53cc566a0b0e5ee93ea6", + "typescript": { + "status": "covered", + "method": "MessagingClient.banSafe.updateProjectInsuranceEvidence" + } + }, + { + "family": "messaging", + "method": "PUT", + "path": "/messaging/projects/{projectId}/safe-mode", + "operationId": "updateProjectSafeMode", + "fingerprint": "a7e98580a55db96e737ae0e8338012194fbca16ac1ce6776377d8d777abd9fca", + "typescript": { + "status": "covered", + "method": "MessagingClient.banSafe.updateProjectSafeMode" + } + }, + { + "family": "messaging", + "method": "PUT", + "path": "/messaging/projects/{projectId}/warmup-plan", + "operationId": "updateProjectWarmupPlan", + "fingerprint": "1d45729a9097fb9a920474d48f32bccae8b3dcc41d65121a75727d1f1558a9fa", + "typescript": { + "status": "covered", + "method": "MessagingClient.banSafe.updateProjectWarmupPlan" + } + }, + { + "family": "messaging", + "method": "PUT", + "path": "/messaging/routing/hybrid", + "operationId": "setHybridRoutingPolicy", + "fingerprint": "1569efd3ac10a5114edbb7716b0aa24b748ba19c65d6844c57a651fe990191a5", + "typescript": { + "status": "covered", + "method": "MessagingClient.hybridLink.setPolicy" + } + }, + { + "family": "messaging", + "method": "PUT", + "path": "/messaging/webhooks/{id}", + "operationId": "updateWebhook", + "fingerprint": "5607f5be756b957f350610acd96c8470e5967201e8b02b4808ce7e9b5915de05", + "typescript": { + "status": "covered", + "method": "MessagingClient.webhooks.update" + } + }, { "family": "platform", "method": "DELETE", @@ -2488,6 +2653,17 @@ "method": "Client.project(projectId).flows.delete" } }, + { + "family": "platform", + "method": "DELETE", + "path": "/platform/flows/{flowId}/endpoint", + "operationId": "deleteFlowEndpoint", + "fingerprint": "c282b7d48c759117c33fbc797616541664c01da36679335444e53d48d37e4e6a", + "typescript": { + "status": "excluded", + "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." + } + }, { "family": "platform", "method": "DELETE", @@ -2557,23 +2733,23 @@ { "family": "platform", "method": "DELETE", - "path": "/platform/sessions/{sessionId}", - "operationId": "deletePlatformSession", - "fingerprint": "57d15ba7f31ccd24c9b2cf3ccc948f5bc2e21d3a80a574fccae108564394d39e", + "path": "/platform/sessions/{session}/client-rules", + "operationId": "deleteClientRules", + "fingerprint": "dacbc8dc161b17ab49b99b220b5b9bf0d30a0b6321d05777165bbff00f4f3a0b", "typescript": { "status": "covered", - "method": "Client.sessions.delete" + "method": "MessagingClient.clientTokens.deleteRules" } }, { "family": "platform", "method": "DELETE", - "path": "/platform/sessions/{session}/client-rules", - "operationId": "deleteClientRules", - "fingerprint": "dacbc8dc161b17ab49b99b220b5b9bf0d30a0b6321d05777165bbff00f4f3a0b", + "path": "/platform/sessions/{sessionId}", + "operationId": "deletePlatformSession", + "fingerprint": "57d15ba7f31ccd24c9b2cf3ccc948f5bc2e21d3a80a574fccae108564394d39e", "typescript": { "status": "covered", - "method": "MessagingClient.clientTokens.deleteRules" + "method": "Client.sessions.delete" } }, { @@ -2874,6 +3050,17 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "GET", + "path": "/console/projects/{projectId}/setup", + "operationId": "getProjectSetup", + "fingerprint": "944bcd5eadab8fbc720cdf6191c4ed4ab32bf9c1ace0d9251ffcd80d3724b4d9", + "typescript": { + "status": "excluded", + "reason": "Console-only project setup uses dashboard identity and is unavailable to SDK credentials." + } + }, { "family": "platform", "method": "GET", @@ -3226,6 +3413,28 @@ "method": "Client.billing.retrieve" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/billing/controls/{scope}/{resourceId}", + "operationId": "getResourceBillingControls", + "fingerprint": "e1f6bb026fddccb31c5df0fd17c069d56fb57cf53983f3820e92125a224d99b5", + "typescript": { + "status": "covered", + "method": "Client.billing.getResourceControls" + } + }, + { + "family": "platform", + "method": "GET", + "path": "/platform/billing/limits", + "operationId": "getBillingLimits", + "fingerprint": "8d58390a96ad855789cc5492d23b38317fe9bd4a310cfe669ff562d73234e927", + "typescript": { + "status": "covered", + "method": "Client.billing.getLimits" + } + }, { "family": "platform", "method": "GET", @@ -3237,6 +3446,17 @@ "method": "Client.billing.listPricing" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/billing/priorities", + "operationId": "getBillingPriorities", + "fingerprint": "2a76b6d931aee49183f1f3aebd85e5ad30f3f36a8ce73d23eec530067201e70e", + "typescript": { + "status": "covered", + "method": "Client.billing.getPriorities" + } + }, { "family": "platform", "method": "GET", @@ -3303,6 +3523,17 @@ "method": "Client.calls.list" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/calls/{callId}", + "operationId": "getCallRecord", + "fingerprint": "f1a34d2b0028a96b067c770b5f798c662107b58723305ffa9d67dcda2c695c40", + "typescript": { + "status": "covered", + "method": "Client.calls.retrieve" + } + }, { "family": "platform", "method": "GET", @@ -3325,17 +3556,6 @@ "method": "Client.calls.stats" } }, - { - "family": "platform", - "method": "GET", - "path": "/platform/calls/{callId}", - "operationId": "getCallRecord", - "fingerprint": "f1a34d2b0028a96b067c770b5f798c662107b58723305ffa9d67dcda2c695c40", - "typescript": { - "status": "covered", - "method": "Client.calls.retrieve" - } - }, { "family": "platform", "method": "GET", @@ -3369,6 +3589,17 @@ "method": "Client.campaigns.analytics" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/campaigns/{campaignId}/conversions", + "operationId": "getCampaignConversions", + "fingerprint": "ee905d707d0072b9867a188ba845f7354637ea733c71c42f5e7bca85db72fdc4", + "typescript": { + "status": "covered", + "method": "Client.campaigns.conversions" + } + }, { "family": "platform", "method": "GET", @@ -3468,6 +3699,17 @@ "method": "Client.events.retrieve" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/flow-encryption-keys", + "operationId": "getFlowEncryptionKey", + "fingerprint": "2cef096982f4a550dff1c3cded7248685c28d433c36bd2304d99a193f8cda000", + "typescript": { + "status": "excluded", + "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." + } + }, { "family": "platform", "method": "GET", @@ -3491,36 +3733,25 @@ } }, { - "family": "messaging", + "family": "platform", "method": "GET", - "path": "/messaging/{session}/templates", - "operationId": "listTemplates", - "fingerprint": "5df7cd21bd821e127a4cccfab21711592c7f63bf6fb1b6cd73e20b2fb9d407db", + "path": "/platform/flows/{flowId}/endpoint", + "operationId": "getFlowEndpoint", + "fingerprint": "12354b56aedcccd9a81fae27d6deb93da3e6490e07192cb8137770312bafe5ff", "typescript": { - "status": "covered", - "method": "MessagingClient.cloudTemplates.list" + "status": "excluded", + "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." } }, { - "family": "messaging", + "family": "platform", "method": "GET", - "path": "/messaging/{session}/templates/{name}", - "operationId": "getTemplate", - "fingerprint": "3eb4b6d52b52b011a1c87c42e36289960e137930b28cd4b6564c9e196f42f6cc", - "typescript": { - "status": "covered", - "method": "MessagingClient.cloudTemplates.retrieve" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/{session}/templates", - "operationId": "createTemplate", - "fingerprint": "929a65c1fd7c48e836fd607be5fcf83930140de25adf53d93fa59ee2d07ed528", + "path": "/platform/flows/{flowId}/endpoint/receipts", + "operationId": "listFlowEndpointReceipts", + "fingerprint": "861ecbba5343f1b72688b9c8da9fb2a678de49cf10abf16794464e8870c68427", "typescript": { - "status": "covered", - "method": "MessagingClient.cloudTemplates.create" + "status": "excluded", + "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." } }, { @@ -3757,23 +3988,23 @@ { "family": "platform", "method": "GET", - "path": "/platform/projects/{projectId}/events/stream", - "operationId": "streamProjectEvents", - "fingerprint": "9ede36f646f722bee5b3550f49bc7ba85cd1883517299099dca518b2550837b3", + "path": "/platform/projects/{projectId}/events/{eventId}", + "operationId": "getProjectEvent", + "fingerprint": "58f2c379f6e47c843d71e71feabb1d4ee9315c6bc6cf4e6667376742f217a344", "typescript": { "status": "covered", - "method": "Client.events.stream" + "method": "Client.project(projectId).events.retrieve" } }, { "family": "platform", "method": "GET", - "path": "/platform/projects/{projectId}/events/{eventId}", - "operationId": "getProjectEvent", - "fingerprint": "58f2c379f6e47c843d71e71feabb1d4ee9315c6bc6cf4e6667376742f217a344", + "path": "/platform/projects/{projectId}/events/stream", + "operationId": "streamProjectEvents", + "fingerprint": "9ede36f646f722bee5b3550f49bc7ba85cd1883517299099dca518b2550837b3", "typescript": { "status": "covered", - "method": "Client.project(projectId).events.retrieve" + "method": "Client.events.stream" } }, { @@ -3787,6 +4018,17 @@ "method": "Client.projects.getHealthPolicy" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/projects/{projectId}/hybrid-merge-candidates", + "operationId": "listHybridMergeCandidates", + "fingerprint": "e4fdcbb9deffd164c07bd7f10b50455f339fed3cb0d8817a4344eac0f8af49ee", + "typescript": { + "status": "covered", + "method": "Client.projects.listHybridMergeCandidates" + } + }, { "family": "platform", "method": "GET", @@ -3952,6 +4194,28 @@ "method": "Client.sessions.list" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/sessions/{session}/call-settings", + "operationId": "getCallSettings", + "fingerprint": "fd7e3a28d2238b2ecd849fe0fe458615fce3fbb4a774bb217e7a170618996764", + "typescript": { + "status": "covered", + "method": "MessagingClient.voip.retrieveCallSettings" + } + }, + { + "family": "platform", + "method": "GET", + "path": "/platform/sessions/{session}/client-rules", + "operationId": "getClientRules", + "fingerprint": "6fc471621ec90e34fcddbb1fb95136a8c60fd914b2eb93170798ed00bdb35b17", + "typescript": { + "status": "covered", + "method": "MessagingClient.clientTokens.retrieveRules" + } + }, { "family": "platform", "method": "GET", @@ -3963,6 +4227,17 @@ "method": "Client.sessions.retrieve" } }, + { + "family": "platform", + "method": "GET", + "path": "/platform/sessions/{sessionId}/capabilities", + "operationId": "getSessionCapabilities", + "fingerprint": "fdb3d1b95d882c43dfd2d10c690bbe70ee4674ad9c3fad94f1160631e963b892", + "typescript": { + "status": "covered", + "method": "Client.sessions.getCapabilities" + } + }, { "family": "platform", "method": "GET", @@ -3996,28 +4271,6 @@ "method": "Client.sessions.retrieveTierChange" } }, - { - "family": "platform", - "method": "GET", - "path": "/platform/sessions/{session}/call-settings", - "operationId": "getCallSettings", - "fingerprint": "fd7e3a28d2238b2ecd849fe0fe458615fce3fbb4a774bb217e7a170618996764", - "typescript": { - "status": "covered", - "method": "MessagingClient.voip.retrieveCallSettings" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/sessions/{session}/client-rules", - "operationId": "getClientRules", - "fingerprint": "6fc471621ec90e34fcddbb1fb95136a8c60fd914b2eb93170798ed00bdb35b17", - "typescript": { - "status": "covered", - "method": "MessagingClient.clientTokens.retrieveRules" - } - }, { "family": "platform", "method": "GET", @@ -4574,9 +4827,9 @@ { "family": "platform", "method": "POST", - "path": "/console/notifications/archive", - "operationId": "archiveAllNotification", - "fingerprint": "e334f83057772eaf58a5022a1e845212bdfea2997299b5de6b60546ff89cfa73", + "path": "/console/notifications/{notificationId}/archive", + "operationId": "archiveNotification", + "fingerprint": "e0a154d5b882b7ed297c9c00ef941e4d1eb5785d804a161a4227594b30302d6c", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -4585,9 +4838,9 @@ { "family": "platform", "method": "POST", - "path": "/console/notifications/read", - "operationId": "markAllReadNotification", - "fingerprint": "f84f53a076a287ceeada4527b158e2a243ceb91644ce7a85913136732a75fcf7", + "path": "/console/notifications/{notificationId}/read", + "operationId": "markReadNotification", + "fingerprint": "d11bcddebf11b322a0730914e27208178c784086d2cbdb725457c25b976e8d03", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -4596,9 +4849,9 @@ { "family": "platform", "method": "POST", - "path": "/console/notifications/test", - "operationId": "sendTestNotificationEmail", - "fingerprint": "a68583eb097cc46913a2be325f717c953ffdff7a109710441f173b6c8e70a530", + "path": "/console/notifications/archive", + "operationId": "archiveAllNotification", + "fingerprint": "e334f83057772eaf58a5022a1e845212bdfea2997299b5de6b60546ff89cfa73", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -4607,9 +4860,9 @@ { "family": "platform", "method": "POST", - "path": "/console/notifications/{notificationId}/archive", - "operationId": "archiveNotification", - "fingerprint": "e0a154d5b882b7ed297c9c00ef941e4d1eb5785d804a161a4227594b30302d6c", + "path": "/console/notifications/read", + "operationId": "markAllReadNotification", + "fingerprint": "f84f53a076a287ceeada4527b158e2a243ceb91644ce7a85913136732a75fcf7", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -4618,9 +4871,9 @@ { "family": "platform", "method": "POST", - "path": "/console/notifications/{notificationId}/read", - "operationId": "markReadNotification", - "fingerprint": "d11bcddebf11b322a0730914e27208178c784086d2cbdb725457c25b976e8d03", + "path": "/console/notifications/test", + "operationId": "sendTestNotificationEmail", + "fingerprint": "a68583eb097cc46913a2be325f717c953ffdff7a109710441f173b6c8e70a530", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -4692,6 +4945,17 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "POST", + "path": "/console/projects/{projectId}/setup/usage", + "operationId": "recordProjectSetupUsage", + "fingerprint": "17d245efd62936847361886f5cc31bc1d1a06c9bfb56208e8eabdd7e465883b5", + "typescript": { + "status": "excluded", + "reason": "Console-only project setup uses dashboard identity and is unavailable to SDK credentials." + } + }, { "family": "platform", "method": "POST", @@ -4750,9 +5014,9 @@ { "family": "platform", "method": "POST", - "path": "/console/teams/leave", - "operationId": "leaveTeamUserProfile", - "fingerprint": "b629f9c00744801aed5781856b17e10b953e38f780c22ae6e97cd7401a41af26", + "path": "/console/teams/{teamId}/delete", + "operationId": "deleteUserProfileTeam", + "fingerprint": "ccdb9f2864db03847089cf0bfe43c5a72a7b77f57e9fd6d0501b7434e69475d3", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -4761,9 +5025,9 @@ { "family": "platform", "method": "POST", - "path": "/console/teams/{teamId}/delete", - "operationId": "deleteUserProfileTeam", - "fingerprint": "ccdb9f2864db03847089cf0bfe43c5a72a7b77f57e9fd6d0501b7434e69475d3", + "path": "/console/teams/leave", + "operationId": "leaveTeamUserProfile", + "fingerprint": "b629f9c00744801aed5781856b17e10b953e38f780c22ae6e97cd7401a41af26", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -4783,10 +5047,10 @@ { "family": "platform", "method": "POST", - "path": "/console/voice/audio/tts", - "operationId": "synthesizeConsoleVoiceAudio", - "fingerprint": "de7ceffc9463624a24c6e85ceff7c2f6057df21452144176b75c4ce336761861", - "typescript": { + "path": "/console/voice/audio/{assetId}/complete", + "operationId": "completeConsoleVoiceAudioUpload", + "fingerprint": "f0699a8103e5d08786cb8d5a5110397008a53f9aa7cbcd9b67685b4a2b64f3d6", + "typescript": { "status": "excluded", "reason": "Console-only voice routes require dashboard membership; server SDK uses Platform voice routes." } @@ -4794,9 +5058,9 @@ { "family": "platform", "method": "POST", - "path": "/console/voice/audio/{assetId}/complete", - "operationId": "completeConsoleVoiceAudioUpload", - "fingerprint": "f0699a8103e5d08786cb8d5a5110397008a53f9aa7cbcd9b67685b4a2b64f3d6", + "path": "/console/voice/audio/tts", + "operationId": "synthesizeConsoleVoiceAudio", + "fingerprint": "de7ceffc9463624a24c6e85ceff7c2f6057df21452144176b75c4ce336761861", "typescript": { "status": "excluded", "reason": "Console-only voice routes require dashboard membership; server SDK uses Platform voice routes." @@ -4849,23 +5113,23 @@ { "family": "platform", "method": "POST", - "path": "/platform/audiences/uploads", - "operationId": "createAudienceUploadUrl", - "fingerprint": "2cc0d9974e639c69de899d9b74e24b4284c7ccb1621962b6a24ecfd4b5f995fd", + "path": "/platform/audiences/{listId}/members", + "operationId": "addAudienceMembers", + "fingerprint": "9a6faa8ec2349008298319f63f4831df3c5f4f77597a4b55a8560a26f998f153", "typescript": { "status": "covered", - "method": "Client.audiences.createUpload" + "method": "Client.audiences.addMembers" } }, { "family": "platform", "method": "POST", - "path": "/platform/audiences/{listId}/members", - "operationId": "addAudienceMembers", - "fingerprint": "9a6faa8ec2349008298319f63f4831df3c5f4f77597a4b55a8560a26f998f153", + "path": "/platform/audiences/uploads", + "operationId": "createAudienceUploadUrl", + "fingerprint": "2cc0d9974e639c69de899d9b74e24b4284c7ccb1621962b6a24ecfd4b5f995fd", "typescript": { "status": "covered", - "method": "Client.audiences.addMembers" + "method": "Client.audiences.createUpload" } }, { @@ -4934,6 +5198,17 @@ "method": "Client.campaigns.archive" } }, + { + "family": "platform", + "method": "POST", + "path": "/platform/campaigns/{campaignId}/conversions", + "operationId": "recordCampaignConversion", + "fingerprint": "1a688b9d292dc06e3b44de2f3c62596df9800dd6a1130be178179a9752447f20", + "typescript": { + "status": "covered", + "method": "Client.campaigns.recordConversion" + } + }, { "family": "platform", "method": "POST", @@ -5099,6 +5374,17 @@ "method": "Client.events.replay" } }, + { + "family": "platform", + "method": "POST", + "path": "/platform/flow-encryption-keys/rotate", + "operationId": "rotateFlowEncryptionKey", + "fingerprint": "44534cbc6d6ee4f783e3f1782dfa4f638f5c94fe9cb176f93fb74c18dc8f2457", + "typescript": { + "status": "excluded", + "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." + } + }, { "family": "platform", "method": "POST", @@ -5165,72 +5451,6 @@ "method": "Client.project(projectId).flows.upload" } }, - { - "family": "platform", - "method": "DELETE", - "path": "/platform/flows/{flowId}/endpoint", - "operationId": "deleteFlowEndpoint", - "fingerprint": "c282b7d48c759117c33fbc797616541664c01da36679335444e53d48d37e4e6a", - "typescript": { - "status": "excluded", - "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." - } - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/flow-encryption-keys", - "operationId": "getFlowEncryptionKey", - "fingerprint": "2cef096982f4a550dff1c3cded7248685c28d433c36bd2304d99a193f8cda000", - "typescript": { - "status": "excluded", - "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." - } - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/flows/{flowId}/endpoint", - "operationId": "getFlowEndpoint", - "fingerprint": "12354b56aedcccd9a81fae27d6deb93da3e6490e07192cb8137770312bafe5ff", - "typescript": { - "status": "excluded", - "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." - } - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/flows/{flowId}/endpoint/receipts", - "operationId": "listFlowEndpointReceipts", - "fingerprint": "861ecbba5343f1b72688b9c8da9fb2a678de49cf10abf16794464e8870c68427", - "typescript": { - "status": "excluded", - "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." - } - }, - { - "family": "platform", - "method": "POST", - "path": "/platform/flow-encryption-keys/rotate", - "operationId": "rotateFlowEncryptionKey", - "fingerprint": "44534cbc6d6ee4f783e3f1782dfa4f638f5c94fe9cb176f93fb74c18dc8f2457", - "typescript": { - "status": "excluded", - "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." - } - }, - { - "family": "platform", - "method": "PUT", - "path": "/platform/flows/{flowId}/endpoint", - "operationId": "setFlowEndpoint", - "fingerprint": "cefa55fe9a7793bc12f590909c8620dd9b82a7f793750167505bce84143ae5a4", - "typescript": { - "status": "excluded", - "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." - } - }, { "family": "platform", "method": "POST", @@ -5355,23 +5575,23 @@ { "family": "platform", "method": "POST", - "path": "/platform/projects/{projectId}/events/stream/{streamId}/ack", - "operationId": "acknowledgeProjectEventStream", - "fingerprint": "1738b5cb4d9927663ec3c898cdbf5b37ab4b9286a997bd10878745e56248f10d", + "path": "/platform/projects/{projectId}/events/{eventId}/replays", + "operationId": "replayProjectEvent", + "fingerprint": "6c81b9cd49a7a92e485fde0d1953ebd3f7e258d91047e3ce7d1c4d7cdae41c9f", "typescript": { "status": "covered", - "method": "Client.events.acknowledgeStream" + "method": "Client.project(projectId).events.replay" } }, { "family": "platform", "method": "POST", - "path": "/platform/projects/{projectId}/events/{eventId}/replays", - "operationId": "replayProjectEvent", - "fingerprint": "6c81b9cd49a7a92e485fde0d1953ebd3f7e258d91047e3ce7d1c4d7cdae41c9f", + "path": "/platform/projects/{projectId}/events/stream/{streamId}/ack", + "operationId": "acknowledgeProjectEventStream", + "fingerprint": "1738b5cb4d9927663ec3c898cdbf5b37ab4b9286a997bd10878745e56248f10d", "typescript": { "status": "covered", - "method": "Client.project(projectId).events.replay" + "method": "Client.events.acknowledgeStream" } }, { @@ -5462,28 +5682,6 @@ "method": "Client.project(projectId).webhooks.test" } }, - { - "family": "platform", - "method": "POST", - "path": "/platform/sessions/delete", - "operationId": "deleteSessions", - "fingerprint": "a8c7a7af2ddbed25ae8e0e8b83284611dd979880e25045ac9af8180ea711414f", - "typescript": { - "status": "covered", - "method": "Client.sessions.deleteMany" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/platform/sessions/stop", - "operationId": "stopSessions", - "fingerprint": "73b1297b8fb7fad3aac9a92b1709a4fe7ce21f2bdd4aaf5a18ce085af6fb2c1f", - "typescript": { - "status": "covered", - "method": "Client.sessions.stopMany" - } - }, { "family": "platform", "method": "POST", @@ -5587,6 +5785,28 @@ "milestone": "security-design" } }, + { + "family": "platform", + "method": "POST", + "path": "/platform/sessions/delete", + "operationId": "deleteSessions", + "fingerprint": "a8c7a7af2ddbed25ae8e0e8b83284611dd979880e25045ac9af8180ea711414f", + "typescript": { + "status": "covered", + "method": "Client.sessions.deleteMany" + } + }, + { + "family": "platform", + "method": "POST", + "path": "/platform/sessions/stop", + "operationId": "stopSessions", + "fingerprint": "73b1297b8fb7fad3aac9a92b1709a4fe7ce21f2bdd4aaf5a18ce085af6fb2c1f", + "typescript": { + "status": "covered", + "method": "Client.sessions.stopMany" + } + }, { "family": "platform", "method": "POST", @@ -5635,23 +5855,23 @@ { "family": "platform", "method": "POST", - "path": "/platform/voice/audio/tts", - "operationId": "synthesizeVoiceAudio", - "fingerprint": "ef8a09b90062fdf47a8cbee0433ec3eef425a05d753077f0462e628bf3328858", + "path": "/platform/voice/audio/{assetId}/complete", + "operationId": "completeVoiceAudioUpload", + "fingerprint": "c66ab60d0408ec006572032ea34878ad8b8bcdb96a4436007ef9ba3997de9e7b", "typescript": { "status": "covered", - "method": "Client.voice.audio.synthesize" + "method": "Client.voice.audio.complete" } }, { "family": "platform", "method": "POST", - "path": "/platform/voice/audio/{assetId}/complete", - "operationId": "completeVoiceAudioUpload", - "fingerprint": "c66ab60d0408ec006572032ea34878ad8b8bcdb96a4436007ef9ba3997de9e7b", + "path": "/platform/voice/audio/tts", + "operationId": "synthesizeVoiceAudio", + "fingerprint": "ef8a09b90062fdf47a8cbee0433ec3eef425a05d753077f0462e628bf3328858", "typescript": { "status": "covered", - "method": "Client.voice.audio.complete" + "method": "Client.voice.audio.synthesize" } }, { @@ -5808,6 +6028,17 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "PUT", + "path": "/console/projects/{projectId}/setup", + "operationId": "saveProjectSetup", + "fingerprint": "e5275087de883e1d670e108e24e568c7aa12947af6567600f7e715ed35f5ba2f", + "typescript": { + "status": "excluded", + "reason": "Console-only project setup uses dashboard identity and is unavailable to SDK credentials." + } + }, { "family": "platform", "method": "PUT", @@ -5830,6 +6061,50 @@ "reason": "Hosted QuickLink recipient and authenticated Console-only operations are outside the trusted-server SDK surface." } }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/billing/controls/{scope}/{resourceId}", + "operationId": "setResourceBillingControls", + "fingerprint": "c997e6d5b183fa55d16fdf54d036ca08cc61f18f69c4473cd170a65a3960465f", + "typescript": { + "status": "covered", + "method": "Client.billing.setResourceControls" + } + }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/billing/limits/{scope}/{resourceId}", + "operationId": "setBillingLimit", + "fingerprint": "34e70cebaa84cc9e432888df4c9f52ed2cc3b0e735362df23135268511a7bd6b", + "typescript": { + "status": "covered", + "method": "Client.billing.setLimit" + } + }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/billing/priorities", + "operationId": "reorderBillingPriorities", + "fingerprint": "b1b1080138edac40d5c3c8b26cdeffc4ab83980300f5085e60f3ca6a9e585fed", + "typescript": { + "status": "covered", + "method": "Client.billing.reorderPriorities" + } + }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/billing/priorities/{scope}/{resourceId}", + "operationId": "setBillingPriority", + "fingerprint": "6fcecd89767ac69fab95df23a17e5465c5b33acf3d7ba94e4fd3d77522a6271c", + "typescript": { + "status": "covered", + "method": "Client.billing.setPriority" + } + }, { "family": "platform", "method": "PUT", @@ -5852,6 +6127,17 @@ "method": "Client.callRetention.update" } }, + { + "family": "platform", + "method": "PUT", + "path": "/platform/flows/{flowId}/endpoint", + "operationId": "setFlowEndpoint", + "fingerprint": "cefa55fe9a7793bc12f590909c8620dd9b82a7f793750167505bce84143ae5a4", + "typescript": { + "status": "excluded", + "reason": "Hosted Flow endpoint and Flow encryption key operations from API PR #428 have no SDK owner yet; they are outside the Hybrid Link tier-transition change and need a separate SDK PR." + } + }, { "family": "platform", "method": "PUT", @@ -5918,17 +6204,6 @@ "method": "Client.projects.updateWarmupPlan" } }, - { - "family": "platform", - "method": "GET", - "path": "/platform/projects/{projectId}/hybrid-merge-candidates", - "operationId": "listHybridMergeCandidates", - "fingerprint": "e4fdcbb9deffd164c07bd7f10b50455f339fed3cb0d8817a4344eac0f8af49ee", - "typescript": { - "status": "covered", - "method": "Client.projects.listHybridMergeCandidates" - } - }, { "family": "platform", "method": "PUT", @@ -5951,28 +6226,6 @@ "method": "Client.sessionConfiguration.update" } }, - { - "family": "platform", - "method": "PUT", - "path": "/platform/sessions/{sessionId}", - "operationId": "updateSession", - "fingerprint": "0671bf1af939a9f1873f4704c17ac3f39970409744347dd0ae2b7df789e2ef36", - "typescript": { - "status": "covered", - "method": "Client.sessions.update" - } - }, - { - "family": "platform", - "method": "PUT", - "path": "/platform/sessions/{sessionId}/safe-mode", - "operationId": "updateNumberSafeMode", - "fingerprint": "3b22c97e1b512959e272204b874a84833b8413445ae410ce1fcd8cf3001c9528", - "typescript": { - "status": "covered", - "method": "Client.sessions.updateSafeMode" - } - }, { "family": "platform", "method": "PUT", @@ -5995,224 +6248,26 @@ "method": "MessagingClient.clientTokens.updateRules" } }, - { - "family": "messaging", - "method": "DELETE", - "path": "/messaging/{session}/official-groups/{group}", - "operationId": "deleteOfficialGroup", - "fingerprint": "cf321af37b6b755b2f8e9b97f8d06836ea46b9d7dea639e77b01f40b52e0c4bc", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.delete" - } - }, - { - "family": "messaging", - "method": "GET", - "path": "/messaging/{session}/official-groups", - "operationId": "listOfficialGroups", - "fingerprint": "96e3e0f5fbca8eb4c811700876172ea478faea7ae45a75b765168cc981cd8062", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.list" - } - }, - { - "family": "messaging", - "method": "GET", - "path": "/messaging/{session}/official-groups/{group}", - "operationId": "getOfficialGroup", - "fingerprint": "bb03e30f20c0ba55966715b1866556c87fba1a4af36d68e1becb89ae10724c8b", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.retrieve" - } - }, - { - "family": "messaging", - "method": "GET", - "path": "/messaging/{session}/official-groups/{group}/invite-link", - "operationId": "getOfficialGroupInviteLink", - "fingerprint": "f5835a10fd91fab374cc379c6b46dc3f5fb8cd66436db23a1277a0b616940e4d", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.getInviteLink" - } - }, - { - "family": "messaging", - "method": "GET", - "path": "/messaging/{session}/official-groups/{group}/join-requests", - "operationId": "listOfficialGroupJoinRequests", - "fingerprint": "6ac3d11480f9402bc8efcbaaf63343afa40a79be00ea6772f3dc2dcafdf30faf", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.listJoinRequests" - } - }, - { - "family": "messaging", - "method": "PATCH", - "path": "/messaging/{session}/official-groups/{group}", - "operationId": "updateOfficialGroup", - "fingerprint": "a4fb6d47150a50b2ed2280b3315d232d1d09676eef6624ea042e17e3a3104b7d", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.update" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/{session}/official-groups", - "operationId": "createOfficialGroup", - "fingerprint": "d1e4db2c30f786ebe5338eda8c74f321c6206acfd048e79ed71d0ad050a96be5", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.create" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/{session}/official-groups/{group}/invite-link/reset", - "operationId": "resetOfficialGroupInviteLink", - "fingerprint": "33f0f394382de3ad8395001e8804f3b23ffee1c3f2fd9e299d5392389d444ef8", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.resetInviteLink" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/{session}/official-groups/{group}/join-requests/approve", - "operationId": "approveOfficialGroupJoinRequests", - "fingerprint": "688ed0fe59dfc17920ac62348847995a1aaf445b1f095203bea29d68501c34f5", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.approveJoinRequests" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/{session}/official-groups/{group}/join-requests/reject", - "operationId": "rejectOfficialGroupJoinRequests", - "fingerprint": "4eb0912a02286ad1a1b1906a269dd20dfc191bbd545233ccc2eff7df6736fa3a", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.rejectJoinRequests" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/{session}/official-groups/{group}/participants/remove", - "operationId": "removeOfficialGroupParticipants", - "fingerprint": "eb56e9668c8095c2fa7338abdc262a9ea36e927a038099ba3f0881f0c88ad592", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.removeParticipants" - } - }, - { - "family": "messaging", - "method": "POST", - "path": "/messaging/{session}/official-groups/{group}/pins", - "operationId": "pinOfficialGroupMessage", - "fingerprint": "3c37d374822bbf2dbd4cbadf5bde4dc26d0ad46fbe11b2432046b5587e3f594b", - "typescript": { - "status": "covered", - "method": "MessagingClient.officialGroups.pin" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/campaigns/{campaignId}/conversions", - "operationId": "getCampaignConversions", - "fingerprint": "ee905d707d0072b9867a188ba845f7354637ea733c71c42f5e7bca85db72fdc4", - "typescript": { - "status": "covered", - "method": "Client.campaigns.conversions" - } - }, - { - "family": "platform", - "method": "POST", - "path": "/platform/campaigns/{campaignId}/conversions", - "operationId": "recordCampaignConversion", - "fingerprint": "1a688b9d292dc06e3b44de2f3c62596df9800dd6a1130be178179a9752447f20", - "typescript": { - "status": "covered", - "method": "Client.campaigns.recordConversion" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/sessions/{sessionId}/capabilities", - "operationId": "getSessionCapabilities", - "fingerprint": "fdb3d1b95d882c43dfd2d10c690bbe70ee4674ad9c3fad94f1160631e963b892", - "typescript": { - "status": "covered", - "method": "Client.sessions.getCapabilities" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/billing/limits", - "operationId": "getBillingLimits", - "fingerprint": "09f303846c1ce0044c0b15357d11108cb4da1d379caf2c02b9b65ccefb9c792b", - "typescript": { - "status": "covered", - "method": "Client.billing.getLimits" - } - }, - { - "family": "platform", - "method": "GET", - "path": "/platform/billing/priorities", - "operationId": "getBillingPriorities", - "fingerprint": "7c5b91e3412d7a1fdbfb7af700c142a0d751eeedce3e2b98d63f48f440c01039", - "typescript": { - "status": "covered", - "method": "Client.billing.getPriorities" - } - }, - { - "family": "platform", - "method": "PUT", - "path": "/platform/billing/limits/{scope}/{resourceId}", - "operationId": "setBillingLimit", - "fingerprint": "cd84f8690610148f325a379712233e148844cbe9da093154fc3281b18184f69a", - "typescript": { - "status": "covered", - "method": "Client.billing.setLimit" - } - }, { "family": "platform", "method": "PUT", - "path": "/platform/billing/priorities", - "operationId": "reorderBillingPriorities", - "fingerprint": "e618e279c18d8b53715f15ae6e277a9464aadf0420cbebc0cfe9127e9f475f5e", + "path": "/platform/sessions/{sessionId}", + "operationId": "updateSession", + "fingerprint": "0671bf1af939a9f1873f4704c17ac3f39970409744347dd0ae2b7df789e2ef36", "typescript": { "status": "covered", - "method": "Client.billing.reorderPriorities" + "method": "Client.sessions.update" } }, { "family": "platform", "method": "PUT", - "path": "/platform/billing/priorities/{scope}/{resourceId}", - "operationId": "setBillingPriority", - "fingerprint": "d5b33799f33d73775906061fcf94322af949ae722d5896807cdfe998f0da92a8", + "path": "/platform/sessions/{sessionId}/safe-mode", + "operationId": "updateNumberSafeMode", + "fingerprint": "3b22c97e1b512959e272204b874a84833b8413445ae410ce1fcd8cf3001c9528", "typescript": { "status": "covered", - "method": "Client.billing.setPriority" + "method": "Client.sessions.updateSafeMode" } } ] diff --git a/contracts/openapi.messaging.json b/contracts/openapi.messaging.json index a9b413f8..95038e1b 100644 --- a/contracts/openapi.messaging.json +++ b/contracts/openapi.messaging.json @@ -19190,6 +19190,28 @@ "format": "uuid", "description": "Customer that owns the connected number when Customers is enabled for the project." }, + "billingControls": { + "type": "object", + "properties": { + "limitCredits": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 1000000 + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + } + }, + "required": [ + "limitCredits", + "priority" + ], + "additionalProperties": false, + "description": "Initial monthly spending limit and funding priority for a real Number. Requires a team key with billing:manage or a live team owner/admin on Pay-As-You-Go. Applied atomically before pairing admission." + }, "externalId": { "type": "string", "minLength": 1, diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index a2defd2c..a17bd170 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -3,7 +3,7 @@ "info": { "title": "Polymorfa Console & Management API", "version": "1.0.0", - "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (241 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (101 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." + "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (243 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (104 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." }, "servers": [ { @@ -24637,6 +24637,240 @@ } } }, + "/platform/billing/controls/{scope}/{resourceId}": { + "get": { + "operationId": "getResourceBillingControls", + "tags": [ + "billing" + ], + "summary": "Read a resource's limit and priority", + "description": "Reads one project, customer or real number without loading the team directory. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:read`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "billing:read", + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "scope", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "project", + "customer", + "number" + ] + } + }, + { + "name": "resourceId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ResourceBillingControls" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" + } + } + }, + "put": { + "operationId": "setResourceBillingControls", + "tags": [ + "billing" + ], + "summary": "Set a resource's limit and priority", + "description": "Saves the monthly limit and priority together, atomically. Both observed revisions are required. Conflicts leave both settings unchanged. Null removes a limit; zero blocks new charges. Existing paid service continues. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:manage`. Project tokens are not accepted.", + "x-audience": "public", + "x-polymorfa-resource-boundary": "organization", + "x-excluded-from-public": false, + "x-required-scope": "billing:manage", + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "scope", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "project", + "customer", + "number" + ] + } + }, + { + "name": "resourceId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "limitCredits": { + "type": "number", + "minimum": 0, + "multipleOf": 0.000001, + "maximum": 1000000, + "nullable": true + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + }, + "expectedBudgetRevision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + }, + "expectedPriorityRevision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + } + }, + "required": [ + "limitCredits", + "priority", + "expectedBudgetRevision", + "expectedPriorityRevision" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ResourceBillingControls" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "503": { + "$ref": "#/components/responses/PropagationPending" + } + } + } + }, "/platform/billing/limits": { "get": { "operationId": "getBillingLimits", @@ -24644,7 +24878,7 @@ "billing" ], "summary": "Read monthly spending limits", - "description": "Reads attributed charges and active reservations in Polymorfa credits. Months reset at 00:00 UTC on the first day. Omit projectId for project limits across the team; pass it to include that project's number limits. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:read`. Project tokens are not accepted.", + "description": "Reads attributed charges and active reservations in Polymorfa credits. Months reset at 00:00 UTC on the first day. Omit projectId for project limits across the team; pass it to include that project's customer and number limits. Pass scope=project to read only project limits. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, @@ -24663,7 +24897,19 @@ "in": "query", "required": false, "schema": { - "type": "string" + "type": "string", + "format": "uuid" + } + }, + { + "name": "scope", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "project" + ] } } ], @@ -24745,7 +24991,12 @@ "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "enum": [ + "project", + "customer", + "number" + ] } }, { @@ -24858,7 +25109,7 @@ "billing" ], "summary": "Read funding priorities", - "description": "Lists active projects and real numbers. Higher project priority takes precedence; higher number priority breaks ties before renewal due time and stable number identity. Numbers are ranked within a project. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:read`. Project tokens are not accepted.", + "description": "Lists active projects, customers and real numbers. Pass scope=project to read only project ranks. Higher project priority takes precedence, followed by customer priority and number priority before renewal due time and stable number identity. Numbers are ranked within a project. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, @@ -24871,6 +25122,19 @@ "ConsoleSession": [] } ], + "parameters": [ + { + "name": "scope", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "project" + ] + } + } + ], "responses": { "200": { "description": "Success", @@ -24905,6 +25169,9 @@ } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, @@ -24922,7 +25189,7 @@ "billing" ], "summary": "Reorder funding priorities", - "description": "resourceIds is the complete list in highest-first order. Project order includes every active project; number order includes every real number in projectId. Saves the whole order atomically. Concurrent changes or an incomplete list return 409. Retrying the same successful order is harmless. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:manage`. Project tokens are not accepted.", + "description": "resourceIds is the complete list in highest-first order. Project order includes every active project; customer and number orders include every active customer or real number in projectId. Saves the whole order atomically. Concurrent changes or an incomplete list return 409. Retrying the same successful order is harmless. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, @@ -24979,6 +25246,7 @@ "scope": { "type": "string", "enum": [ + "customer", "number" ] }, @@ -25094,7 +25362,12 @@ "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "enum": [ + "project", + "customer", + "number" + ] } }, { @@ -25825,19 +26098,18 @@ } } }, - "/console/projects/{projectId}": { - "patch": { - "operationId": "updateProject", + "/console/projects/{projectId}/setup": { + "get": { + "operationId": "getProjectSetup", "tags": [ "projects" ], - "summary": "Update project settings", - "description": "Updates the display name, icon, or default number plan without changing the project ID, URL, stage, or individual number overrides.\n\n**Hybrid Link.** A default plan without Hybrid Link returns `409` with code `hybrid_choice_required` while a Hybrid Link Number inherits the project plan. Change each such Number's tier first and choose which connection it keeps.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "summary": "Read project setup progress", + "description": "Returns current project setup facts and the setup goals selected for this project. Includes a saved enum-only integration plan, revision and live edit eligibility; onboarding choices seed the first project. Completion is derived from current records. Does not enable features, create resources, or verify deployed integrations.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Dashboard project name, icon, and default number plan settings. Requires a current owner or admin.", - "x-required-organization-role": "owner_or_admin", + "x-exclusion-reason": "Dashboard-only project setup guidance; no credential or customer data.", "security": [ { "ConsoleSession": [] @@ -25854,64 +26126,781 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": false, - "minProperties": 1, - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 200 - }, - "defaultTier": { - "type": "string", - "enum": [ - "free", - "standard", - "pro" - ] - }, - "icon": { - "type": "object", - "additionalProperties": false, - "required": [ - "type", - "value" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "emoji", - "icon", - "image" - ] - }, - "value": { - "type": "string", - "minLength": 1, - "maxLength": 512 - }, - "color": { - "type": "string", - "maxLength": 64 - }, - "storageId": { - "type": "string", - "maxLength": 256 - } - } - } - } - } - } - } - }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "object", + "required": [ + "projectId", + "facts", + "connectionPath", + "goals", + "profile", + "revision", + "canEdit" + ], + "properties": { + "projectId": { + "type": "string", + "format": "uuid" + }, + "profile": { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "runtime", + "language", + "integration", + "apiSurface", + "connectionPath", + "audience", + "events", + "sessionSource", + "goals" + ], + "properties": { + "version": { + "type": "integer", + "enum": [ + 1 + ] + }, + "runtime": { + "type": "string", + "enum": [ + "server", + "browser", + "mobile", + "hybrid", + "unsure" + ] + }, + "language": { + "type": "string", + "enum": [ + "typescript", + "javascript", + "python", + "go", + "java", + "csharp", + "php", + "ruby", + "other", + "unsure" + ] + }, + "integration": { + "type": "string", + "enum": [ + "typescript-sdk", + "browser-calls", + "http", + "unsure" + ] + }, + "apiSurface": { + "type": "string", + "enum": [ + "messaging", + "platform", + "graph", + "unsure" + ] + }, + "connectionPath": { + "type": "string", + "enum": [ + "meta", + "linked", + "hybrid", + "unsure" + ] + }, + "audience": { + "type": "string", + "enum": [ + "internal", + "customers", + "unsure" + ] + }, + "events": { + "type": "string", + "enum": [ + "webhooks", + "polling", + "unsure" + ] + }, + "sessionSource": { + "type": "string", + "enum": [ + "new", + "baileys", + "whatsmeow", + "unsure" + ] + }, + "goals": { + "type": "array", + "maxItems": 8, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "templates", + "campaigns", + "catalogs", + "calls", + "flows", + "hms", + "embedded", + "safe-mode" + ] + } + } + }, + "nullable": true + }, + "revision": { + "type": "integer", + "minimum": 0 + }, + "canEdit": { + "type": "boolean" + }, + "facts": { + "type": "object", + "additionalProperties": false, + "required": [ + "numberCreated", + "webhookCreated", + "numberConnected", + "credentialCreated", + "apiUsed", + "webhookEnabled", + "templateCreated", + "campaignCreated", + "flowCreated", + "catalogProductCreated", + "callSettingsSaved", + "historyRequested", + "historyConfigured", + "quickLinkSettingsSaved", + "quickLinkEnabled", + "safetyViewed" + ], + "properties": { + "numberCreated": { + "type": "boolean" + }, + "webhookCreated": { + "type": "boolean" + }, + "numberConnected": { + "type": "boolean" + }, + "credentialCreated": { + "type": "boolean" + }, + "apiUsed": { + "type": "boolean" + }, + "webhookEnabled": { + "type": "boolean" + }, + "templateCreated": { + "type": "boolean" + }, + "campaignCreated": { + "type": "boolean" + }, + "flowCreated": { + "type": "boolean" + }, + "catalogProductCreated": { + "type": "boolean" + }, + "callSettingsSaved": { + "type": "boolean" + }, + "historyRequested": { + "type": "boolean" + }, + "historyConfigured": { + "type": "boolean" + }, + "quickLinkSettingsSaved": { + "type": "boolean" + }, + "quickLinkEnabled": { + "type": "boolean" + }, + "safetyViewed": { + "type": "boolean" + } + } + }, + "connectionPath": { + "type": "string", + "enum": [ + "meta", + "linked", + "hybrid", + "unsure" + ] + }, + "goals": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "templates", + "campaigns", + "catalogs", + "calls", + "flows", + "hms", + "embedded", + "safe-mode" + ] + } + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + } + }, + "put": { + "operationId": "saveProjectSetup", + "tags": [ + "projects" + ], + "summary": "Save a project integration plan", + "description": "Saves reviewed enum-only setup answers. Server SDK choices require TypeScript or JavaScript; browser calling requires client code and the Calls feature. Requires a current owner or admin and the expected revision; stale writes return 409. Does not grant access, enable features, import a session or configure resources.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard-only enum-only integration guidance; no credentials, feature authority or runtime configuration.", + "x-required-organization-role": "owner_or_admin", + "security": [ + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "required": [ + "expectedRevision", + "profile" + ], + "properties": { + "expectedRevision": { + "type": "integer", + "minimum": 0 + }, + "profile": { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "runtime", + "language", + "integration", + "apiSurface", + "connectionPath", + "audience", + "events", + "sessionSource", + "goals" + ], + "properties": { + "version": { + "type": "integer", + "enum": [ + 1 + ] + }, + "runtime": { + "type": "string", + "enum": [ + "server", + "browser", + "mobile", + "hybrid", + "unsure" + ] + }, + "language": { + "type": "string", + "enum": [ + "typescript", + "javascript", + "python", + "go", + "java", + "csharp", + "php", + "ruby", + "other", + "unsure" + ] + }, + "integration": { + "type": "string", + "enum": [ + "typescript-sdk", + "browser-calls", + "http", + "unsure" + ] + }, + "apiSurface": { + "type": "string", + "enum": [ + "messaging", + "platform", + "graph", + "unsure" + ] + }, + "connectionPath": { + "type": "string", + "enum": [ + "meta", + "linked", + "hybrid", + "unsure" + ] + }, + "audience": { + "type": "string", + "enum": [ + "internal", + "customers", + "unsure" + ] + }, + "events": { + "type": "string", + "enum": [ + "webhooks", + "polling", + "unsure" + ] + }, + "sessionSource": { + "type": "string", + "enum": [ + "new", + "baileys", + "whatsmeow", + "unsure" + ] + }, + "goals": { + "type": "array", + "maxItems": 8, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "templates", + "campaigns", + "catalogs", + "calls", + "flows", + "hms", + "embedded", + "safe-mode" + ] + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "object", + "required": [ + "revision" + ], + "properties": { + "revision": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + } + } + }, + "/console/projects/{projectId}/setup/usage": { + "post": { + "operationId": "recordProjectSetupUsage", + "tags": [ + "projects" + ], + "summary": "Record consented setup usage", + "description": "Records a bounded enum-only interaction with a server-derived completion snapshot. Requires current dashboard membership and measurement consent. Stores no prompt or imported answer content. Exact event ids deduplicate retries. Measurements expire after 90 days and staff reads aggregate counts only.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard-only consented setup usage; aggregate staff analysis only.", + "security": [ + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "method", + "event", + "step", + "measurementAllowed" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "ai", + "manual", + "both" + ] + }, + "event": { + "type": "string", + "enum": [ + "method_change", + "wizard_open", + "question_answer", + "answers_imported", + "plan_saved", + "task_open", + "manual_open", + "prompt_copy", + "progress_view" + ] + }, + "step": { + "type": "string", + "enum": [ + "setup", + "runtime", + "language", + "integration", + "apiSurface", + "connectionPath", + "audience", + "events", + "sessionSource", + "goals", + "connect", + "webhook", + "verify", + "templates", + "campaigns", + "catalogs", + "calls", + "flows", + "hms", + "embedded", + "safe-mode" + ] + }, + "measurementAllowed": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "object", + "required": [ + "recorded" + ], + "properties": { + "recorded": { + "type": "boolean" + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + } + } + }, + "/console/projects/{projectId}": { + "patch": { + "operationId": "updateProject", + "tags": [ + "projects" + ], + "summary": "Update project settings", + "description": "Updates the display name, icon, or default number plan without changing the project ID, URL, stage, or individual number overrides.\n\n**Hybrid Link.** A default plan without Hybrid Link returns `409` with code `hybrid_choice_required` while a Hybrid Link Number inherits the project plan. Change each such Number's tier first and choose which connection it keeps.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard project name, icon, and default number plan settings. Requires a current owner or admin.", + "x-required-organization-role": "owner_or_admin", + "security": [ + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "defaultTier": { + "type": "string", + "enum": [ + "free", + "standard", + "pro" + ] + }, + "icon": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "emoji", + "icon", + "image" + ] + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "color": { + "type": "string", + "maxLength": 64 + }, + "storageId": { + "type": "string", + "maxLength": 256 + } + } + } + } + } + } + } + }, "responses": { "200": { "description": "Success", @@ -38272,6 +39261,37 @@ ] } }, + "customers": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + } + }, + "required": [ + "id", + "projectId", + "name", + "priority" + ] + } + }, "numbers": { "type": "array", "items": { @@ -38307,6 +39327,7 @@ "required": [ "revision", "projects", + "customers", "numbers" ] }, @@ -38318,6 +39339,7 @@ "type": "string", "enum": [ "project", + "customer", "number" ] }, @@ -38366,6 +39388,30 @@ "revision" ] }, + "ResourceBillingControls": { + "type": "object", + "additionalProperties": false, + "properties": { + "budget": { + "$ref": "#/components/schemas/BillingLimit" + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + }, + "priorityRevision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + } + }, + "required": [ + "budget", + "priority", + "priorityRevision" + ] + }, "BillingLimits": { "type": "object", "additionalProperties": false, diff --git a/contracts/source.json b/contracts/source.json index 94c69f80..dce6886c 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,19 +1,19 @@ { "repository": "polymorfa/polymorfa", - "commit": "2cf44217655e8f4659ff9bbfa869d6499f4926ce", + "commit": "ec5f287953fe983ffa43e98dacbff4672c98c8ce", "branch": "t3code/project-billing-dashboard", "published": false, - "note": "Limits and project/number funding priorities on an unmerged implementation branch. Both snapshots are byte-identical to this API source commit. API deployment and SDK/CLI package publication remain separate gates.", + "note": "Project, customer and number limits and priorities; native single-resource controls and atomic initial QuickLink controls. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", "snapshotPath": "contracts/openapi.messaging.json", - "sha256": "fd9709bc7f75e362a1c2008dbde44fd6ba1db310207cf0bed85282a12bef16d4" + "sha256": "7f6ddab25a673e45da7a209292ddfd7354acccf30c27e2fa651113e321b66d3d" }, "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "82451703efb451fd847d0f664ca3d03101428ac98300fe7f7ecfd26e7dca58f9" + "sha256": "62da5f577924847a6bbb46bebd960336ac919bda9988b04b49b787393f3f21b6" } } } diff --git a/packages/typescript/README.md b/packages/typescript/README.md index 3b317641..0fb28e3b 100644 --- a/packages/typescript/README.md +++ b/packages/typescript/README.md @@ -2893,3 +2893,25 @@ return a conflict. Limits reset monthly at 00:00 UTC on the first day. Null removes a cap; zero blocks new charges. Paid windows remain intact. These methods do not grant permissions, paid access or deployed availability. Publication of this source revision is separate from API deployment. + + +For one customer or number, read and save both controls without loading the +organization directory: + +```typescript +const {data: {data: current}} = await platform.billing.getResourceControls("customer", customerId); +await platform.billing.setResourceControls("customer", customerId, { + limitCredits: 1500, priority: 25, + expectedBudgetRevision: current.budget.revision, + expectedPriorityRevision: current.priorityRevision, +}); +``` + +Both revisions are required; a conflict leaves both settings unchanged. Funding +uses project, customer, then number priority. A customer's assigned numbers +share its monthly cap. `getLimits({scope:"project"})` and +`getPriorities({scope:"project"})` avoid downloading number/customer directories. +Real initial QuickLink creation accepts `billingControls:{limitCredits:250, +priority:30}` before pairing admission, with team `billing:manage` authority. +Testing and supplementary connections reject these fields. Package publication +and API deployment remain separate gates. diff --git a/packages/typescript/src/index.ts b/packages/typescript/src/index.ts index 5eb44489..deed869d 100644 --- a/packages/typescript/src/index.ts +++ b/packages/typescript/src/index.ts @@ -1242,4 +1242,12 @@ export { type FlowProviderResult, } from "./platform/flows.js"; -export type { BillingScope, BillingLimit, BillingLimits, BillingPriority, BillingPriorities, ReorderBillingPrioritiesInput } from "./platform/billing-controls.js"; +export type { + ResourceBillingControls, + BillingScope, + BillingLimit, + BillingLimits, + BillingPriority, + BillingPriorities, + ReorderBillingPrioritiesInput, +} from "./platform/billing-controls.js"; diff --git a/packages/typescript/src/messaging/quicklinks.ts b/packages/typescript/src/messaging/quicklinks.ts index fa52ea92..dd1801e9 100644 --- a/packages/typescript/src/messaging/quicklinks.ts +++ b/packages/typescript/src/messaging/quicklinks.ts @@ -1,3 +1,4 @@ +import { billingLimit, billingPriority } from "../platform/billing-controls.js"; import type { SessionConfigurationOverrides } from "./session-configuration.js"; import { PolymorfaConfigurationError } from "../errors.js"; import type { @@ -34,6 +35,10 @@ export type QuickLinkHybridPhase = "cloud_setup" | "linked_pairing" | "repair_linked" | "ready"; interface CreateQuickLinkBase { + readonly billingControls?: { + readonly limitCredits: number | null; + readonly priority: number; + }; readonly connectionGoal?: QuickLinkConnectionGoal; readonly addConnection?: QuickLinkConnectionKind; readonly projectId?: string; @@ -164,6 +169,15 @@ export class QuickLinksResource { options: RequestOptions = {}, ): Promise> { this.assertServerCredential(); + if (input.billingControls) { + billingLimit(input.billingControls.limitCredits); + billingPriority(input.billingControls.priority); + if (input.purpose === "add_connection" || input.configuration?.testing) + throw new PolymorfaConfigurationError( + "Initial billing controls require a real new number", + "billingControls", + ); + } return this.transport.request({ method: "POST", path: "/messaging/quicklinks", diff --git a/packages/typescript/src/platform/billing-controls.ts b/packages/typescript/src/platform/billing-controls.ts index 9ccbdf89..4bb0483f 100644 --- a/packages/typescript/src/platform/billing-controls.ts +++ b/packages/typescript/src/platform/billing-controls.ts @@ -1,6 +1,6 @@ import { PolymorfaValidationError } from "../errors.js"; -export type BillingScope = "project" | "number"; +export type BillingScope = "project" | "customer" | "number"; export interface BillingPriority { readonly id: string; readonly name: string; @@ -9,6 +9,9 @@ export interface BillingPriority { export interface BillingPriorities { readonly revision: number; readonly projects: readonly BillingPriority[]; + readonly customers: readonly (BillingPriority & { + readonly projectId: string; + })[]; readonly numbers: readonly (BillingPriority & { readonly projectId: string; })[]; @@ -42,7 +45,7 @@ export type ReorderBillingPrioritiesInput = readonly expectedRevision: number; } | { - readonly scope: "number"; + readonly scope: "customer" | "number"; readonly projectId: string; readonly resourceIds: readonly string[]; readonly expectedRevision: number; @@ -61,8 +64,8 @@ export function billingRevision(value: number): void { invalid("expectedRevision must be a nonnegative integer"); } export function billingScope(value: BillingScope): void { - if (value !== "project" && value !== "number") - invalid("scope must be project or number"); + if (value !== "project" && value !== "customer" && value !== "number") + invalid("scope must be project, customer or number"); } export function billingPriority(value: number): void { if (!Number.isInteger(value) || value < 0 || value > 1000000) @@ -74,7 +77,7 @@ export function billingLimit(value: number | null): void { (!Number.isFinite(value) || value < 0 || value > 1000000 || - Math.abs(value * 1000000 - Math.round(value * 1000000)) > 0.00001) + !/^\d+(\.\d{1,6})?$/.test(String(value))) ) invalid( "limitCredits must be null or 0–1000000 credits with at most six decimal places", @@ -85,3 +88,9 @@ function invalid(message: string): never { code: "invalid_billing_control", }); } + +export interface ResourceBillingControls { + readonly budget: BillingLimit; + readonly priority: number; + readonly priorityRevision: number; +} diff --git a/packages/typescript/src/platform/billing.ts b/packages/typescript/src/platform/billing.ts index 4ece4600..6381a1f9 100644 --- a/packages/typescript/src/platform/billing.ts +++ b/packages/typescript/src/platform/billing.ts @@ -6,6 +6,7 @@ import { billingPriority, billingLimit, type BillingScope, + type ResourceBillingControls, type BillingLimits, type BillingPriorities, type ReorderBillingPrioritiesInput, @@ -47,18 +48,57 @@ export class BillingResource { return this.get("/pricing", options); } + getResourceControls( + scope: BillingScope, + resourceId: string, + options: RequestOptions = {}, + ): Promise>> { + billingScope(scope); + return this.transport.request({ + method: "GET", + path: `/platform/billing/controls/${scope}/${billingId(resourceId)}`, + ...options, + }); + } + setResourceControls( + scope: BillingScope, + resourceId: string, + input: { + readonly limitCredits: number | null; + readonly priority: number; + readonly expectedBudgetRevision: number; + readonly expectedPriorityRevision: number; + }, + options: RequestOptions = {}, + ): Promise>> { + billingScope(scope); + billingLimit(input.limitCredits); + billingPriority(input.priority); + billingRevision(input.expectedBudgetRevision); + billingRevision(input.expectedPriorityRevision); + return this.transport.request({ + method: "PUT", + path: `/platform/billing/controls/${scope}/${billingId(resourceId)}`, + body: input, + ...options, + }); + } /** Monthly credit limits. Organization credentials with billing:read only. */ getLimits( - params: { readonly projectId?: string } = {}, + params: { readonly projectId?: string; readonly scope?: "project" } = {}, options: RequestOptions = {}, ): Promise>> { + if (params.scope !== undefined && params.scope !== "project") + throw new PolymorfaValidationError("Read scope must be project."); return this.transport.request({ method: "GET", path: "/platform/billing/limits", - query: - params.projectId === undefined + query: { + ...(params.projectId === undefined ? {} - : { projectId: billingId(params.projectId) }, + : { projectId: billingId(params.projectId) }), + ...(params.scope === undefined ? {} : { scope: params.scope }), + }, ...options, }); } @@ -85,11 +125,15 @@ export class BillingResource { }); } getPriorities( + params: { readonly scope?: "project" } = {}, options: RequestOptions = {}, ): Promise>> { + if (params.scope !== undefined && params.scope !== "project") + throw new PolymorfaValidationError("Read scope must be project."); return this.transport.request({ method: "GET", path: "/platform/billing/priorities", + query: params.scope === undefined ? {} : { scope: params.scope }, ...options, }); } @@ -125,7 +169,7 @@ export class BillingResource { { code: "invalid_billing_control" }, ); const body = - input.scope === "number" + input.scope !== "project" ? { scope: input.scope, projectId: billingId(input.projectId), diff --git a/packages/typescript/test/billing-controls.test.ts b/packages/typescript/test/billing-controls.test.ts index 4d18a06b..bb9b4694 100644 --- a/packages/typescript/test/billing-controls.test.ts +++ b/packages/typescript/test/billing-controls.test.ts @@ -16,7 +16,7 @@ it("serializes limits and single/reordered project and number priorities using r server = await startTestServer(() => ({ headers: { "content-type": "application/json" }, body: JSON.stringify({ - data: { revision: 1, projects: [], numbers: [] }, + data: { revision: 1, projects: [], customers: [], numbers: [] }, }), })); const client = new Client({ @@ -107,3 +107,70 @@ it("preserves a stale revision conflict without retrying or rewriting the reques ).rejects.toBeInstanceOf(PolymorfaConflictError); expect(server.requests).toHaveLength(1); }); + +it("serializes singleton customer controls with both revisions and validates before transport", async () => { + server = await startTestServer(() => ({ + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + data: { + budget: { + scope: "customer", + resourceId: id, + projectId: other, + name: "Acme", + limitCredits: 200, + spentCredits: 0, + reservedCredits: 0, + revision: 2, + }, + priority: 40, + priorityRevision: 3, + }, + }), + })); + const client = new Client({ + baseUrl: server.url, + credential: { type: "organizationApiKey", value: ORGANIZATION_API_KEY }, + maxNetworkRetries: 0, + }); + await client.billing.getResourceControls("customer", id); + const input = { + limitCredits: 200, + priority: 40, + expectedBudgetRevision: 1, + expectedPriorityRevision: 2, + }; + await client.billing.setResourceControls("customer", id, input); + expect(server.requests.map((r) => [r.method, r.path])).toEqual([ + ["GET", `/platform/billing/controls/customer/${id}`], + ["PUT", `/platform/billing/controls/customer/${id}`], + ]); + expect(JSON.parse(server.requests[1]!.body)).toEqual(input); + expect(() => + client.billing.setResourceControls("customer", id, { + ...input, + priority: -1, + }), + ).toThrow(PolymorfaValidationError); + expect(server.requests).toHaveLength(2); +}); + +it("filters project directories without loading customer and number lists", async () => { + server = await startTestServer(() => ({ + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + data: { revision: 1, projects: [], customers: [], numbers: [] }, + }), + })); + const client = new Client({ + baseUrl: server.url, + credential: { type: "organizationApiKey", value: ORGANIZATION_API_KEY }, + maxNetworkRetries: 0, + }); + await client.billing.getLimits({ projectId: id, scope: "project" }); + await client.billing.getPriorities({ scope: "project" }); + expect(server.requests.map((r) => r.path)).toEqual([ + `/platform/billing/limits?projectId=${id}&scope=project`, + "/platform/billing/priorities?scope=project", + ]); +}); diff --git a/packages/typescript/test/contract-shapes.test.ts b/packages/typescript/test/contract-shapes.test.ts index 1b6ea3ce..260b3dba 100644 --- a/packages/typescript/test/contract-shapes.test.ts +++ b/packages/typescript/test/contract-shapes.test.ts @@ -192,6 +192,7 @@ describe("SDK types match the pinned contract snapshots", () => { it("keeps QuickLink creation and Cloud onboarding request fields", () => { expect( keys({ + billingControls: true, purpose: true, connectionGoal: true, session: true, diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index 67696a23..ab545cbd 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -196,7 +196,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("2cf44217655e8f4659ff9bbfa869d6499f4926ce"); + expect(source.commit).toBe("ec5f287953fe983ffa43e98dacbff4672c98c8ce"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index c9d84f2c..019902d5 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -173,12 +173,12 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "2cf44217655e8f4659ff9bbfa869d6499f4926ce", - total: 563, - covered: 427, + sourceCommit: "ec5f287953fe983ffa43e98dacbff4672c98c8ce", + total: 568, + covered: 429, partial: 0, missing: 0, - excluded: 136, + excluded: 139, changed: 0, }); const resolutions = (result.report?.resolutions ?? []) as Array<{ diff --git a/packages/typescript/test/quicklinks.test.ts b/packages/typescript/test/quicklinks.test.ts index 38390f58..8aec332c 100644 --- a/packages/typescript/test/quicklinks.test.ts +++ b/packages/typescript/test/quicklinks.test.ts @@ -4,6 +4,7 @@ import { afterEach, describe, expect, expectTypeOf, it, vi } from "vitest"; import { MessagingClient, PolymorfaConfigurationError, + PolymorfaValidationError, type ApiResponse, type CancelQuickLinkResponse, type CreateQuickLinkResponse, @@ -274,3 +275,44 @@ describe("MessagingClient.quickLinks", () => { expect(fetch).not.toHaveBeenCalled(); }); }); + +it("applies initial financial controls in the create request and rejects test-number or imprecise controls before sending", async () => { + const server = await startTestServer(() => ({ + status: 201, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + success: true, + data: { + id: "ql_budget", + url: "https://example.test/budget", + session: "reserved", + expiresAt: null, + }, + }), + })); + servers.push(server); + const client = new MessagingClient({ + baseUrl: server.url, + credential: { type: "apiKey", value: ORGANIZATION_API_KEY }, + maxNetworkRetries: 0, + }); + await client.quickLinks.create({ + projectId: "11111111-2222-4333-8444-555555555555", + billingControls: { limitCredits: 250, priority: 30 }, + }); + expect(JSON.parse(server.requests[0]!.body)).toMatchObject({ + billingControls: { limitCredits: 250, priority: 30 }, + }); + expect(() => + client.quickLinks.create({ + configuration: { testing: { country: "US" } }, + billingControls: { limitCredits: 250, priority: 30 }, + }), + ).toThrow(PolymorfaConfigurationError); + expect(() => + client.quickLinks.create({ + billingControls: { limitCredits: 0.000001000000001, priority: 30 }, + }), + ).toThrow(PolymorfaValidationError); + expect(server.requests).toHaveLength(1); +}); From b7a4870fd3d4929d2ff181d07d23a614f2fcd536 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 14:56:16 +0300 Subject: [PATCH 04/11] feat(billing): support mixed funding controls and scoped priorities Pin byte-identical API snapshots to ba09326dd3c6688f05320c1f7685aeb0290a0ed2. Cover project-filtered priority reads and mixed customer/number reorders while keeping private Console usage insights excluded. Validate scoped identifiers and document inherited customer priority with number overrides. --- contracts/README.md | 14 +- contracts/coverage.json | 8 +- contracts/openapi.platform.json | 318 +++++++++++++++++- contracts/source.json | 6 +- packages/typescript/README.md | 36 +- .../src/platform/billing-controls.ts | 9 + packages/typescript/src/platform/billing.ts | 39 ++- .../typescript/test/billing-controls.test.ts | 104 ++++++ .../test/coverage-reconciliation.test.ts | 2 +- packages/typescript/test/coverage.test.ts | 2 +- 10 files changed, 509 insertions(+), 29 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index 8e491140..c1457073 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -42,13 +42,19 @@ published SDK package before updating their pinned dependency. ## Full snapshots The current snapshots are byte-identical copies of Messaging and Platform -contracts at API implementation commit `ec5f287953fe983ffa43e98dacbff4672c98c8ce` on +contracts at API implementation commit `ba09326dd3c6688f05320c1f7685aeb0290a0ed2` on `t3code/project-billing-dashboard`. This branch is unmerged. Seven financial operations cover limits, priorities, complete ordering and one-resource combined -read/save. Project, customer and number scopes use the confirmed funding hierarchy. +read/save. Project priority takes precedence. Numbers inherit customer priority; +a higher number priority overrides it within the project. Customer limits still +include all assigned numbers. Initial real QuickLink creation accepts optional billing controls before admission. -Six existing operation fingerprints changed for those fields and read filters; -three new Console-only setup operations are explicitly excluded from SDK authority. +This repin changes three operation fingerprints: project-filtered priority reads, +mixed customer/number ordering, and private Console usage insights. The two +financial operations are covered by typed SDK methods. Usage insights remain +excluded because they require dashboard identity; no server SDK method grants +access to that Console route. The three Console-only setup operations also +remain excluded from SDK authority. Reads require organization credentials with `billing:read`; writes require `billing:manage`, live team authority and Pay-As-You-Go. Existing credential masks do not gain these permissions. Paid service continues through its window when a diff --git a/contracts/coverage.json b/contracts/coverage.json index 42c1f644..8f6b171f 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "ec5f287953fe983ffa43e98dacbff4672c98c8ce", + "sourceCommit": "ba09326dd3c6688f05320c1f7685aeb0290a0ed2", "excludedContracts": [ { "family": "graph", @@ -2978,7 +2978,7 @@ "method": "GET", "path": "/console/insights/usage", "operationId": "usageInsight", - "fingerprint": "b2ea967eb6db42513f720f6fc06b8ad3463f3b4d9a21dfe73ef82d836abceba9", + "fingerprint": "6bf3ad6817b02fd4c3831a4675b21bc281731621887f0eed52ae7f54d2d9db96", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -3451,7 +3451,7 @@ "method": "GET", "path": "/platform/billing/priorities", "operationId": "getBillingPriorities", - "fingerprint": "2a76b6d931aee49183f1f3aebd85e5ad30f3f36a8ce73d23eec530067201e70e", + "fingerprint": "0d538e633f10f1884cd69ebccc79b0780faac10398a31b6e8265e47807266ea3", "typescript": { "status": "covered", "method": "Client.billing.getPriorities" @@ -6088,7 +6088,7 @@ "method": "PUT", "path": "/platform/billing/priorities", "operationId": "reorderBillingPriorities", - "fingerprint": "b1b1080138edac40d5c3c8b26cdeffc4ab83980300f5085e60f3ca6a9e585fed", + "fingerprint": "abc3e26df3c2b767c87d08c6b9469cc319b198125cad67794bd122c57a657f66", "typescript": { "status": "covered", "method": "Client.billing.reorderPriorities" diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index a17bd170..fb95fe93 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -25109,7 +25109,7 @@ "billing" ], "summary": "Read funding priorities", - "description": "Lists active projects, customers and real numbers. Pass scope=project to read only project ranks. Higher project priority takes precedence, followed by customer priority and number priority before renewal due time and stable number identity. Numbers are ranked within a project. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:read`. Project tokens are not accepted.", + "description": "Lists active projects, customers and real numbers. Pass scope=project to read only project ranks. Pass projectId to read one project and its customer/number leaderboard. Project priority takes precedence. Within a project, numbers inherit their customer priority; a higher number priority overrides it. Number priority then breaks ties before renewal due time and stable number identity. Numbers are ranked within a project. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:read`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, @@ -25133,6 +25133,15 @@ "project" ] } + }, + { + "name": "projectId", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + } } ], "responses": { @@ -25178,6 +25187,9 @@ "403": { "$ref": "#/components/responses/Forbidden" }, + "404": { + "$ref": "#/components/responses/NotFound" + }, "503": { "$ref": "#/components/responses/PropagationPending" } @@ -25189,7 +25201,7 @@ "billing" ], "summary": "Reorder funding priorities", - "description": "resourceIds is the complete list in highest-first order. Project order includes every active project; customer and number orders include every active customer or real number in projectId. Saves the whole order atomically. Concurrent changes or an incomplete list return 409. Retrying the same successful order is harmless. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:manage`. Project tokens are not accepted.", + "description": "resourceIds is the complete list in highest-first order. Project order includes every active project; customer and number orders include every active customer or real number in projectId. Use scope=resource with projectId and a complete resources array of {scope: customer or number, resourceId} for one mixed leaderboard. Saves the whole order atomically. Concurrent changes or an incomplete list return 409. Retrying the same successful order is harmless. Requires an organization API key with the stated billing scope, or a signed-in team owner or admin. Project and client credentials cannot access these controls. Billing must be configured. Writes require Pay-As-You-Go. These controls never enable charging of unpriced usage.\n\nRequires a team API key with `billing:manage`. Project tokens are not accepted.", "x-audience": "public", "x-polymorfa-resource-boundary": "organization", "x-excluded-from-public": false, @@ -25208,6 +25220,59 @@ "application/json": { "schema": { "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "scope": { + "type": "string", + "enum": [ + "resource" + ] + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "resources": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "scope": { + "type": "string", + "enum": [ + "customer", + "number" + ] + }, + "resourceId": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "scope", + "resourceId" + ] + }, + "uniqueItems": true, + "maxItems": 1000000 + }, + "expectedRevision": { + "type": "integer", + "minimum": 0, + "maximum": 2147483646 + } + }, + "required": [ + "scope", + "projectId", + "resources", + "expectedRevision" + ] + }, { "type": "object", "additionalProperties": false, @@ -37759,8 +37824,8 @@ "tags": [ "insights" ], - "summary": "Usage insight", - "description": "**EXCLUDED from the public API** (`console`). Customer console usage and entitlement presentation.", + "summary": "Read Console usage", + "description": "Private Console usage for the authenticated team or one active project. `start` and `end` are inclusive Unix-millisecond boundaries, with a maximum span of 366 days. A missing end defaults to now; a missing start defaults to 29 days before the end. Daily buckets use UTC dates and include only dates with observed audit or credit-ledger activity. API request counts include API and MCP operations; peak request rate is the busiest one-minute bucket, not a sum of daily peaks. Number and connection metrics are live snapshots, and message-rate usage remains null because its peak is not measured. The project breakdown includes active projects and omits unattributed team activity, which remains in team totals.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, @@ -37777,7 +37842,8 @@ "required": false, "description": "Project to read. Omit to total every project in the organization.", "schema": { - "type": "string" + "type": "string", + "format": "uuid" } }, { @@ -37785,7 +37851,9 @@ "in": "query", "required": false, "schema": { - "type": "string" + "type": "integer", + "format": "int64", + "description": "Inclusive Unix-millisecond boundary. The range may span at most 366 days." } }, { @@ -37793,7 +37861,9 @@ "in": "query", "required": false, "schema": { - "type": "string" + "type": "integer", + "format": "int64", + "description": "Inclusive Unix-millisecond boundary. The range may span at most 366 days." } } ], @@ -37817,19 +37887,31 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DataEnvelope" + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ConsoleUsageInsights" + } + }, + "required": [ + "data" + ] } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, - "501": { - "$ref": "#/components/responses/NotImplemented" + "404": { + "$ref": "#/components/responses/NotFound" } } } @@ -39753,6 +39835,222 @@ "data" ] }, + "ConsoleUsageInsights": { + "type": "object", + "additionalProperties": false, + "properties": { + "period": { + "type": "object", + "additionalProperties": false, + "properties": { + "start": { + "type": "integer", + "format": "int64" + }, + "end": { + "type": "integer", + "format": "int64" + } + }, + "required": [ + "start", + "end" + ] + }, + "metrics": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConsoleUsageMetric" + } + }, + "totals": { + "type": "object", + "additionalProperties": false, + "properties": { + "failedRequests": { + "type": "integer", + "minimum": 0 + }, + "sessionDayCredits": { + "type": "number" + } + }, + "required": [ + "failedRequests", + "sessionDayCredits" + ] + }, + "daily": { + "type": "array", + "maxItems": 367, + "description": "Observed UTC dates in ascending order. Missing dates have no observed activity. Partial first and last days count only activity within the inclusive requested range.", + "items": { + "$ref": "#/components/schemas/ConsoleDailyUsage" + } + }, + "byProject": { + "type": "array", + "description": "Included for a team-wide read; omitted for a selected project. Active projects only. Team-level activity without a project is included in totals but has no project row.", + "items": { + "$ref": "#/components/schemas/ConsoleProjectUsage" + } + } + }, + "required": [ + "period", + "metrics", + "totals", + "daily" + ] + }, + "ConsoleUsageMetric": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "enum": [ + "numbers", + "connected", + "session-days", + "api-requests", + "request-rate", + "message-rate", + "active-days" + ] + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "unit": { + "type": "string", + "enum": [ + "numbers", + "sessions", + "days", + "requests", + "req/min", + "msg/min" + ] + }, + "used": { + "type": "number", + "minimum": 0, + "nullable": true, + "description": "Null for the unmeasured message-rate peak." + }, + "limit": { + "type": "integer", + "minimum": -1, + "description": "-1 means no limit. Session-days uses 0 because every observed session-day is metered." + }, + "overage": { + "type": "number", + "minimum": 0, + "nullable": true + }, + "overageLabel": { + "type": "string" + } + }, + "required": [ + "key", + "label", + "description", + "unit", + "used", + "limit", + "overage", + "overageLabel" + ] + }, + "ConsoleDailyUsage": { + "type": "object", + "additionalProperties": false, + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "UTC date, YYYY-MM-DD." + }, + "sessionDays": { + "type": "integer", + "minimum": 0 + }, + "sessionDayCredits": { + "type": "number" + }, + "apiRequests": { + "type": "integer", + "minimum": 0 + }, + "failedRequests": { + "type": "integer", + "minimum": 0 + }, + "peakRequestRate": { + "type": "integer", + "minimum": 0, + "description": "Maximum API and MCP request count in one UTC minute on this date within the requested range." + }, + "activeDays": { + "type": "integer", + "enum": [ + 1 + ], + "description": "This date has at least one observed audit or credit-ledger row, including activity that is not an API request or session charge." + } + }, + "required": [ + "date", + "sessionDays", + "sessionDayCredits", + "apiRequests", + "failedRequests", + "peakRequestRate", + "activeDays" + ] + }, + "ConsoleProjectUsage": { + "type": "object", + "additionalProperties": false, + "properties": { + "projectId": { + "type": "string", + "format": "uuid" + }, + "connected": { + "type": "integer", + "minimum": 0 + }, + "sessionDays": { + "type": "integer", + "minimum": 0 + }, + "sessionDayCredits": { + "type": "number" + }, + "apiRequests": { + "type": "integer", + "minimum": 0 + }, + "failedRequests": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "projectId", + "connected", + "sessionDays", + "sessionDayCredits", + "apiRequests", + "failedRequests" + ] + }, "ErrorEnvelope": { "type": "object", "required": [ diff --git a/contracts/source.json b/contracts/source.json index dce6886c..27e99e0a 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,9 +1,9 @@ { "repository": "polymorfa/polymorfa", - "commit": "ec5f287953fe983ffa43e98dacbff4672c98c8ce", + "commit": "ba09326dd3c6688f05320c1f7685aeb0290a0ed2", "branch": "t3code/project-billing-dashboard", "published": false, - "note": "Project, customer and number limits and priorities; native single-resource controls and atomic initial QuickLink controls. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", + "note": "Project, customer and number funding controls with mixed resource ranking and project-filtered priority reads. Private Console usage insights include bounded UTC daily aggregates and remain excluded from server SDK authority. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", @@ -13,7 +13,7 @@ "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "62da5f577924847a6bbb46bebd960336ac919bda9988b04b49b787393f3f21b6" + "sha256": "972fe78f67ea7cb07a891f7510d40a2e8a12a2716dc2a368e0886dcdc32a229a" } } } diff --git a/packages/typescript/README.md b/packages/typescript/README.md index 0fb28e3b..3776eeaf 100644 --- a/packages/typescript/README.md +++ b/packages/typescript/README.md @@ -2907,11 +2907,41 @@ await platform.billing.setResourceControls("customer", customerId, { }); ``` -Both revisions are required; a conflict leaves both settings unchanged. Funding -uses project, customer, then number priority. A customer's assigned numbers -share its monthly cap. `getLimits({scope:"project"})` and +Both revisions are required; a conflict leaves both settings unchanged. Project +priority takes precedence. Numbers inherit customer priority; a higher number +priority overrides it within the project. A customer's assigned numbers share +its monthly cap. `getLimits({scope:"project"})` and `getPriorities({scope:"project"})` avoid downloading number/customer directories. Real initial QuickLink creation accepts `billingControls:{limitCredits:250, priority:30}` before pairing admission, with team `billing:manage` authority. Testing and supplementary connections reject these fields. Package publication and API deployment remain separate gates. + +Read one project's funding controls and save its combined customer and number +order: + +```typescript +const { data: { data: controls } } = await platform.billing.getPriorities({ projectId }); +const resources = [ + ...controls.customers.map(customer => ({ + scope: "customer" as const, + resourceId: customer.id, + priority: customer.priority, + })), + ...controls.numbers.map(number => ({ + scope: "number" as const, + resourceId: number.id, + priority: number.priority, + })), +].sort((first, second) => second.priority - first.priority); +await platform.billing.reorderPriorities({ + scope: "resource", + projectId, + resources: resources.map(({ scope, resourceId }) => ({ scope, resourceId })), + expectedRevision: controls.revision, +}); +``` + +Pass every active customer and real number, highest priority first. Duplicate, +incomplete, or stale orders fail without partial changes. This operation never +bypasses a spending cap. diff --git a/packages/typescript/src/platform/billing-controls.ts b/packages/typescript/src/platform/billing-controls.ts index 4bb0483f..40b6f320 100644 --- a/packages/typescript/src/platform/billing-controls.ts +++ b/packages/typescript/src/platform/billing-controls.ts @@ -39,6 +39,15 @@ export interface BillingLimits { readonly budgets: readonly BillingLimit[]; } export type ReorderBillingPrioritiesInput = + | { + readonly scope: "resource"; + readonly projectId: string; + readonly resources: readonly { + readonly scope: "customer" | "number"; + readonly resourceId: string; + }[]; + readonly expectedRevision: number; + } | { readonly scope: "project"; readonly resourceIds: readonly string[]; diff --git a/packages/typescript/src/platform/billing.ts b/packages/typescript/src/platform/billing.ts index 6381a1f9..b219535e 100644 --- a/packages/typescript/src/platform/billing.ts +++ b/packages/typescript/src/platform/billing.ts @@ -125,7 +125,7 @@ export class BillingResource { }); } getPriorities( - params: { readonly scope?: "project" } = {}, + params: { readonly scope?: "project"; readonly projectId?: string } = {}, options: RequestOptions = {}, ): Promise>> { if (params.scope !== undefined && params.scope !== "project") @@ -133,7 +133,12 @@ export class BillingResource { return this.transport.request({ method: "GET", path: "/platform/billing/priorities", - query: params.scope === undefined ? {} : { scope: params.scope }, + query: { + ...(params.scope === undefined ? {} : { scope: params.scope }), + ...(params.projectId === undefined + ? {} + : { projectId: billingId(params.projectId) }), + }, ...options, }); } @@ -160,8 +165,36 @@ export class BillingResource { input: ReorderBillingPrioritiesInput, options: RequestOptions = {}, ): Promise>> { - billingScope(input.scope); billingRevision(input.expectedRevision); + if (input.scope === "resource") { + const resources = input.resources.map((row) => { + if (row.scope !== "customer" && row.scope !== "number") + throw new PolymorfaValidationError( + "Resource scope must be customer or number.", + ); + return { scope: row.scope, resourceId: billingId(row.resourceId) }; + }); + if ( + resources.length > 1000000 || + new Set(resources.map((r) => `${r.scope}:${r.resourceId}`)).size !== + resources.length + ) + throw new PolymorfaValidationError( + "resources must be a unique complete list.", + ); + return this.transport.request({ + method: "PUT", + path: "/platform/billing/priorities", + body: { + scope: input.scope, + projectId: billingId(input.projectId), + resources, + expectedRevision: input.expectedRevision, + }, + ...options, + }); + } + billingScope(input.scope); const resourceIds = input.resourceIds.map(billingId); if (new Set(resourceIds).size !== resourceIds.length) throw new PolymorfaValidationError( diff --git a/packages/typescript/test/billing-controls.test.ts b/packages/typescript/test/billing-controls.test.ts index bb9b4694..4285bf1f 100644 --- a/packages/typescript/test/billing-controls.test.ts +++ b/packages/typescript/test/billing-controls.test.ts @@ -174,3 +174,107 @@ it("filters project directories without loading customer and number lists", asyn "/platform/billing/priorities?scope=project", ]); }); + +it("serializes project-scoped mixed funding controls and rejects duplicate or invalid resource scopes before HTTP", async () => { + server = await startTestServer(() => ({ + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + data: { revision: 5, projects: [], customers: [], numbers: [] }, + }), + })); + const client = new Client({ + baseUrl: server.url, + credential: { type: "organizationApiKey", value: ORGANIZATION_API_KEY }, + maxNetworkRetries: 0, + }); + await client.billing.getPriorities({ projectId: id }); + const resources = [ + { scope: "number" as const, resourceId: other }, + { scope: "customer" as const, resourceId: id }, + ]; + await client.billing.reorderPriorities({ + scope: "resource", + projectId: id, + resources, + expectedRevision: 5, + }); + expect(server.requests[0]?.path).toBe( + `/platform/billing/priorities?projectId=${id}`, + ); + expect(JSON.parse(server.requests[1]!.body)).toEqual({ + scope: "resource", + projectId: id, + resources, + expectedRevision: 5, + }); + expect(() => + client.billing.reorderPriorities({ + scope: "resource", + projectId: id, + resources: [resources[0]!, resources[0]!], + expectedRevision: 5, + }), + ).toThrow(PolymorfaValidationError); + expect(() => + client.billing.reorderPriorities({ + scope: "resource", + projectId: id, + resources: [{ scope: "project" as never, resourceId: id }], + expectedRevision: 5, + }), + ).toThrow(PolymorfaValidationError); + expect(server.requests).toHaveLength(2); +}); + +it("normalizes scoped IDs, preserves distinct resource kinds, and validates project filters before HTTP", async () => { + server = await startTestServer(() => ({ + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + data: { revision: 5, projects: [], customers: [], numbers: [] }, + }), + })); + const client = new Client({ + baseUrl: server.url, + credential: { type: "organizationApiKey", value: ORGANIZATION_API_KEY }, + maxNetworkRetries: 0, + }); + const resourceId = "AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA"; + await client.billing.getPriorities({ + scope: "project", + projectId: resourceId, + }); + await client.billing.reorderPriorities({ + scope: "resource", + projectId: resourceId, + resources: [ + { scope: "number", resourceId }, + { scope: "customer", resourceId }, + ], + expectedRevision: 5, + }); + expect(server.requests[0]?.path).toBe( + `/platform/billing/priorities?scope=project&projectId=${resourceId.toLowerCase()}`, + ); + expect(JSON.parse(server.requests[1]!.body)).toMatchObject({ + projectId: resourceId.toLowerCase(), + resources: [ + { scope: "number", resourceId: resourceId.toLowerCase() }, + { scope: "customer", resourceId: resourceId.toLowerCase() }, + ], + }); + expect(() => client.billing.getPriorities({ projectId: "invalid" })).toThrow( + PolymorfaValidationError, + ); + expect(() => + client.billing.reorderPriorities({ + scope: "resource", + projectId: resourceId, + resources: [ + { scope: "number", resourceId }, + { scope: "number", resourceId: resourceId.toLowerCase() }, + ], + expectedRevision: 5, + }), + ).toThrow(PolymorfaValidationError); + expect(server.requests).toHaveLength(2); +}); diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index ab545cbd..ca44177f 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -196,7 +196,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("ec5f287953fe983ffa43e98dacbff4672c98c8ce"); + expect(source.commit).toBe("ba09326dd3c6688f05320c1f7685aeb0290a0ed2"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index 019902d5..08ad08d2 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -173,7 +173,7 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "ec5f287953fe983ffa43e98dacbff4672c98c8ce", + sourceCommit: "ba09326dd3c6688f05320c1f7685aeb0290a0ed2", total: 568, covered: 429, partial: 0, From ac41e314282c1127c05ec1cd752eecd461fab9f8 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 19:35:50 +0300 Subject: [PATCH 05/11] chore(contracts): pin Console consumption snapshot --- contracts/README.md | 17 ++- contracts/coverage.json | 4 +- contracts/openapi.platform.json | 139 +++++++++++++++++- contracts/source.json | 6 +- .../test/coverage-reconciliation.test.ts | 2 +- packages/typescript/test/coverage.test.ts | 2 +- 6 files changed, 155 insertions(+), 15 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index c1457073..629f229c 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -42,19 +42,22 @@ published SDK package before updating their pinned dependency. ## Full snapshots The current snapshots are byte-identical copies of Messaging and Platform -contracts at API implementation commit `ba09326dd3c6688f05320c1f7685aeb0290a0ed2` on +contracts at API implementation commit `61d0a0e6e2c7b0eca1971657afc80926b4d4142b` on `t3code/project-billing-dashboard`. This branch is unmerged. Seven financial operations cover limits, priorities, complete ordering and one-resource combined read/save. Project priority takes precedence. Numbers inherit customer priority; a higher number priority overrides it within the project. Customer limits still include all assigned numbers. Initial real QuickLink creation accepts optional billing controls before admission. -This repin changes three operation fingerprints: project-filtered priority reads, -mixed customer/number ordering, and private Console usage insights. The two -financial operations are covered by typed SDK methods. Usage insights remain -excluded because they require dashboard identity; no server SDK method grants -access to that Console route. The three Console-only setup operations also -remain excluded from SDK authority. +This repin changes only the excluded private Console `usageInsight` fingerprint. +Its aggregate breakdown includes number-window tiers, registered call and voice +meters, and explicitly unavailable HMS history. Metered quantities do not imply +credit charges. The preceding source, `ba09326dd3c6688f05320c1f7685aeb0290a0ed2`, +added project-filtered priority reads and mixed customer/number ordering; those +public operations retain their typed SDK methods and unchanged fingerprints. +Usage insights require dashboard identity; no server SDK method grants access +to that Console route. The three Console-only setup operations also remain +excluded from SDK authority. Reads require organization credentials with `billing:read`; writes require `billing:manage`, live team authority and Pay-As-You-Go. Existing credential masks do not gain these permissions. Paid service continues through its window when a diff --git a/contracts/coverage.json b/contracts/coverage.json index 8f6b171f..207e0b8a 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "ba09326dd3c6688f05320c1f7685aeb0290a0ed2", + "sourceCommit": "61d0a0e6e2c7b0eca1971657afc80926b4d4142b", "excludedContracts": [ { "family": "graph", @@ -2978,7 +2978,7 @@ "method": "GET", "path": "/console/insights/usage", "operationId": "usageInsight", - "fingerprint": "6bf3ad6817b02fd4c3831a4675b21bc281731621887f0eed52ae7f54d2d9db96", + "fingerprint": "19920f41795584ba68545ce0d18e4a05b277be8c6623547345ca518fc2bafccb", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index fb95fe93..979b55dd 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -39888,6 +39888,14 @@ "$ref": "#/components/schemas/ConsoleDailyUsage" } }, + "consumption": { + "type": "array", + "maxItems": 16, + "description": "Credit-ledger number windows and registered usage meters. Credits come only from the charged number-window ledger; observed call and voice quantities do not imply a charge. HMS has no aggregate usage source and returns unavailable values.", + "items": { + "$ref": "#/components/schemas/ConsoleConsumption" + } + }, "byProject": { "type": "array", "description": "Included for a team-wide read; omitted for a selected project. Active projects only. Team-level activity without a project is included in totals but has no project row.", @@ -39900,9 +39908,138 @@ "period", "metrics", "totals", + "daily", + "consumption" + ] + }, + "ConsoleConsumption": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "enum": [ + "number.free", + "number.standard", + "number.pro", + "number.other", + "call.duration", + "call.cloud_pulses", + "campaign.call", + "tts.characters", + "tts.seconds", + "stt.seconds", + "agent.seconds", + "agent.tokens", + "agent.provider_cost", + "channels.peak", + "storage.byte_days", + "hms.storage" + ] + }, + "group": { + "type": "string", + "enum": [ + "numbers", + "calls", + "voice", + "agents", + "storage", + "hms" + ] + }, + "unit": { + "type": "string", + "enum": [ + "day", + "second", + "pulse", + "call", + "character", + "token", + "provider_unit", + "channel", + "byte_day" + ] + }, + "aggregation": { + "type": "string", + "enum": [ + "sum", + "max" + ], + "description": "channels.peak uses the maximum observed quantity; additive quantities use the sum." + }, + "quantity": { + "type": "number", + "minimum": 0, + "nullable": true + }, + "credits": { + "type": "number", + "nullable": true, + "description": "Actual credit-ledger debit. Null for observed meters that have no credit settlement; never calculated from a catalog or rate card." + }, + "availability": { + "type": "string", + "enum": [ + "measured", + "unavailable" + ] + }, + "chargeState": { + "type": "string", + "enum": [ + "charged", + "unpriced", + "unavailable" + ] + }, + "daily": { + "type": "array", + "maxItems": 367, + "nullable": true, + "items": { + "$ref": "#/components/schemas/ConsoleConsumptionDay" + }, + "description": "Observed UTC dates in ascending order within the inclusive range. Null when history is unavailable; an empty array is a measured empty range." + } + }, + "required": [ + "key", + "group", + "unit", + "aggregation", + "quantity", + "credits", + "availability", + "chargeState", "daily" ] }, + "ConsoleConsumptionDay": { + "type": "object", + "additionalProperties": false, + "properties": { + "date": { + "type": "string", + "format": "date" + }, + "quantity": { + "type": "number", + "minimum": 0 + }, + "credits": { + "type": "number", + "nullable": true + } + }, + "required": [ + "date", + "quantity", + "credits" + ] + }, "ConsoleUsageMetric": { "type": "object", "additionalProperties": false, @@ -40001,7 +40138,7 @@ "enum": [ 1 ], - "description": "This date has at least one observed audit or credit-ledger row, including activity that is not an API request or session charge." + "description": "This date has at least one observed audit, credit-ledger or usage-meter row, including activity that is not an API request or session charge." } }, "required": [ diff --git a/contracts/source.json b/contracts/source.json index 27e99e0a..530beb45 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,9 +1,9 @@ { "repository": "polymorfa/polymorfa", - "commit": "ba09326dd3c6688f05320c1f7685aeb0290a0ed2", + "commit": "61d0a0e6e2c7b0eca1971657afc80926b4d4142b", "branch": "t3code/project-billing-dashboard", "published": false, - "note": "Project, customer and number funding controls with mixed resource ranking and project-filtered priority reads. Private Console usage insights include bounded UTC daily aggregates and remain excluded from server SDK authority. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", + "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights add aggregate consumption for number windows, registered call and voice meters, and unavailable HMS history; they remain excluded from server SDK authority. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", @@ -13,7 +13,7 @@ "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "972fe78f67ea7cb07a891f7510d40a2e8a12a2716dc2a368e0886dcdc32a229a" + "sha256": "ce4bc5eb8e0115c7873d28ee26da9167c777b5874419c60945224f118fdcba46" } } } diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index ca44177f..06731c61 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -196,7 +196,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("ba09326dd3c6688f05320c1f7685aeb0290a0ed2"); + expect(source.commit).toBe("61d0a0e6e2c7b0eca1971657afc80926b4d4142b"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index 08ad08d2..e8ee8324 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -173,7 +173,7 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "ba09326dd3c6688f05320c1f7685aeb0290a0ed2", + sourceCommit: "61d0a0e6e2c7b0eca1971657afc80926b4d4142b", total: 568, covered: 429, partial: 0, From ca399a2baf0abd0b77a541190230f5405a4868f8 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 20:32:27 +0300 Subject: [PATCH 06/11] chore(contracts): pin priced Usage family snapshot --- contracts/README.md | 13 +++++++--- contracts/coverage.json | 4 +-- contracts/openapi.platform.json | 25 ++++++++++++++----- contracts/source.json | 6 ++--- .../test/coverage-reconciliation.test.ts | 2 +- packages/typescript/test/coverage.test.ts | 2 +- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index 629f229c..241e3588 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -42,7 +42,7 @@ published SDK package before updating their pinned dependency. ## Full snapshots The current snapshots are byte-identical copies of Messaging and Platform -contracts at API implementation commit `61d0a0e6e2c7b0eca1971657afc80926b4d4142b` on +contracts at API implementation commit `9aa751ef923990c4b9ff6df56f64e35c7ae78027` on `t3code/project-billing-dashboard`. This branch is unmerged. Seven financial operations cover limits, priorities, complete ordering and one-resource combined read/save. Project priority takes precedence. Numbers inherit customer priority; @@ -50,9 +50,14 @@ a higher number priority overrides it within the project. Customer limits still include all assigned numbers. Initial real QuickLink creation accepts optional billing controls before admission. This repin changes only the excluded private Console `usageInsight` fingerprint. -Its aggregate breakdown includes number-window tiers, registered call and voice -meters, and explicitly unavailable HMS history. Metered quantities do not imply -credit charges. The preceding source, `ba09326dd3c6688f05320c1f7685aeb0290a0ed2`, +Its 18 aggregate categories distinguish catalog pricing from measured quantities +and actual charged credits. Completed outbound calls match the monthly gate; +serverless counts surviving admitted project receipts, without number attribution +or durable billing authority. Complete WhatsApp media/HMS GB-month usage remains +unavailable. The total production-number metric no longer treats the legacy +session limit as a cap. The preceding source, +`61d0a0e6e2c7b0eca1971657afc80926b4d4142b`, added registered consumption meters. +The earlier source, `ba09326dd3c6688f05320c1f7685aeb0290a0ed2`, added project-filtered priority reads and mixed customer/number ordering; those public operations retain their typed SDK methods and unchanged fingerprints. Usage insights require dashboard identity; no server SDK method grants access diff --git a/contracts/coverage.json b/contracts/coverage.json index 207e0b8a..35197a32 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "61d0a0e6e2c7b0eca1971657afc80926b4d4142b", + "sourceCommit": "9aa751ef923990c4b9ff6df56f64e35c7ae78027", "excludedContracts": [ { "family": "graph", @@ -2978,7 +2978,7 @@ "method": "GET", "path": "/console/insights/usage", "operationId": "usageInsight", - "fingerprint": "19920f41795584ba68545ce0d18e4a05b277be8c6623547345ca518fc2bafccb", + "fingerprint": "d1dd7bae8660c815bb26de3a824cf2941ab487bd8c5cd2dc5cb09813ea756c38", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index 979b55dd..0d7e6d39 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -39890,8 +39890,8 @@ }, "consumption": { "type": "array", - "maxItems": 16, - "description": "Credit-ledger number windows and registered usage meters. Credits come only from the charged number-window ledger; observed call and voice quantities do not imply a charge. HMS has no aggregate usage source and returns unavailable values.", + "maxItems": 18, + "description": "Credit-ledger number windows, registered meters and priced overage families. Outbound calls count completed call.duration records, matching the monthly gate. Serverless counts surviving admitted project invocation receipts, including failed or uncertain outcomes; receipts have no per-number allowance attribution. WhatsApp data covers media and enabled HMS but has no approved complete GB-month aggregate source. Pricing and measurement availability never establish a charge; credits come only from the number-window debit ledger.", "items": { "$ref": "#/components/schemas/ConsoleConsumption" } @@ -39934,7 +39934,9 @@ "agent.provider_cost", "channels.peak", "storage.byte_days", - "hms.storage" + "call.outbound", + "whatsapp.storage", + "serverless.invocations" ] }, "group": { @@ -39945,7 +39947,7 @@ "voice", "agents", "storage", - "hms" + "serverless" ] }, "unit": { @@ -39959,7 +39961,9 @@ "token", "provider_unit", "channel", - "byte_day" + "byte_day", + "gb_month", + "invocation" ] }, "aggregation": { @@ -39987,6 +39991,14 @@ "unavailable" ] }, + "pricingState": { + "type": "string", + "enum": [ + "priced", + "unpriced" + ], + "description": "An approved catalog price or an actual priced ledger category exists. Independent of measurement and actual charged-credit availability; never grants a capability or authorizes settlement." + }, "chargeState": { "type": "string", "enum": [ @@ -40013,6 +40025,7 @@ "quantity", "credits", "availability", + "pricingState", "chargeState", "daily" ] @@ -40138,7 +40151,7 @@ "enum": [ 1 ], - "description": "This date has at least one observed audit, credit-ledger or usage-meter row, including activity that is not an API request or session charge." + "description": "This date has at least one observed audit, credit-ledger, usage-meter or admitted function invocation row, including activity that is not an API request or session charge." } }, "required": [ diff --git a/contracts/source.json b/contracts/source.json index 530beb45..336fe49b 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,9 +1,9 @@ { "repository": "polymorfa/polymorfa", - "commit": "61d0a0e6e2c7b0eca1971657afc80926b4d4142b", + "commit": "9aa751ef923990c4b9ff6df56f64e35c7ae78027", "branch": "t3code/project-billing-dashboard", "published": false, - "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights add aggregate consumption for number windows, registered call and voice meters, and unavailable HMS history; they remain excluded from server SDK authority. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", + "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights distinguish catalog pricing from measured quantities and actual charged credits; completed outbound call counts and admitted project invocation receipts are observed, while complete WhatsApp media/HMS GB-month usage is unavailable. The production-number total no longer uses legacy session_limit as a cap. Console insights remain excluded from server SDK authority. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", @@ -13,7 +13,7 @@ "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "ce4bc5eb8e0115c7873d28ee26da9167c777b5874419c60945224f118fdcba46" + "sha256": "4d0b041063833c498b9e47252e8612c15666518212891965042e90d5008ee088" } } } diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index 06731c61..53a18139 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -196,7 +196,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("61d0a0e6e2c7b0eca1971657afc80926b4d4142b"); + expect(source.commit).toBe("9aa751ef923990c4b9ff6df56f64e35c7ae78027"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index e8ee8324..b56f1502 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -173,7 +173,7 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "61d0a0e6e2c7b0eca1971657afc80926b4d4142b", + sourceCommit: "9aa751ef923990c4b9ff6df56f64e35c7ae78027", total: 568, covered: 429, partial: 0, From a92b930e27d6ffd66637c4e4b5bdb7b56b70afa1 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 22:08:35 +0300 Subject: [PATCH 07/11] chore(contracts): pin number-scoped Usage read --- contracts/README.md | 22 ++-- contracts/coverage.json | 4 +- contracts/openapi.platform.json | 114 +++++++++++++++++- contracts/source.json | 6 +- .../test/coverage-reconciliation.test.ts | 2 +- packages/typescript/test/coverage.test.ts | 2 +- 6 files changed, 131 insertions(+), 19 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index 241e3588..7a2e0816 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -42,7 +42,7 @@ published SDK package before updating their pinned dependency. ## Full snapshots The current snapshots are byte-identical copies of Messaging and Platform -contracts at API implementation commit `9aa751ef923990c4b9ff6df56f64e35c7ae78027` on +contracts at API implementation commit `49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3` on `t3code/project-billing-dashboard`. This branch is unmerged. Seven financial operations cover limits, priorities, complete ordering and one-resource combined read/save. Project priority takes precedence. Numbers inherit customer priority; @@ -50,12 +50,20 @@ a higher number priority overrides it within the project. Customer limits still include all assigned numbers. Initial real QuickLink creation accepts optional billing controls before admission. This repin changes only the excluded private Console `usageInsight` fingerprint. -Its 18 aggregate categories distinguish catalog pricing from measured quantities -and actual charged credits. Completed outbound calls match the monthly gate; -serverless counts surviving admitted project receipts, without number attribution -or durable billing authority. Complete WhatsApp media/HMS GB-month usage remains -unavailable. The total production-number metric no longer treats the legacy -session limit as a cap. The preceding source, +It adds exact org/project/number scope metadata and immutable-number filtering. +The selected number's current UTC monthly completed outgoing calls and included +allowance remain separate from the chart period. Unknown tiers have no allowance +read; known unlimited tiers are explicit. Request logs and serverless receipts +have no number attribution, so their number measurements are unavailable. +No per-number allowance is pooled into a project/team quota; credit caps still use +their separate financial read. The preceding private read at +`9aa751ef923990c4b9ff6df56f64e35c7ae78027` distinguished catalog pricing from +measured quantities and actual charged credits across 18 aggregate categories. +Completed outbound calls match the monthly gate; serverless counts surviving +admitted project receipts, without number attribution or durable billing authority. +Complete WhatsApp media/HMS GB-month usage remains unavailable. The total +production-number metric does not treat the legacy session limit as a cap. +The preceding source, `61d0a0e6e2c7b0eca1971657afc80926b4d4142b`, added registered consumption meters. The earlier source, `ba09326dd3c6688f05320c1f7685aeb0290a0ed2`, added project-filtered priority reads and mixed customer/number ordering; those diff --git a/contracts/coverage.json b/contracts/coverage.json index 35197a32..10783c90 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "9aa751ef923990c4b9ff6df56f64e35c7ae78027", + "sourceCommit": "49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3", "excludedContracts": [ { "family": "graph", @@ -2978,7 +2978,7 @@ "method": "GET", "path": "/console/insights/usage", "operationId": "usageInsight", - "fingerprint": "d1dd7bae8660c815bb26de3a824cf2941ab487bd8c5cd2dc5cb09813ea756c38", + "fingerprint": "9fd2eed8de7659be96a0f2c90dccfe057f8bf79d25592f03f75ed8cab5e0b14b", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index 0d7e6d39..e9d78e17 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -37846,6 +37846,16 @@ "format": "uuid" } }, + { + "name": "numberId", + "in": "query", + "required": false, + "description": "Immutable production number UUID. Requires projectId; missing, deleted, testing or mismatched numbers return 404. Filters only immutable attributed records; request logs and serverless receipts have no number attribution.", + "schema": { + "type": "string", + "format": "uuid" + } + }, { "name": "start", "in": "query", @@ -39839,6 +39849,19 @@ "type": "object", "additionalProperties": false, "properties": { + "scope": { + "$ref": "#/components/schemas/ConsoleUsageScope" + }, + "monthlyCalls": { + "type": "object", + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/ConsoleMonthlyOutgoingCalls" + } + ], + "description": "Known-tier selected-number included outgoing allowance and completed-call usage for the current UTC month. Null for team/project reads or an unknown number tier. Independent of the requested chart period and of staff gate overrides; never a hard admission cap or charged-credit quote." + }, "period": { "type": "object", "additionalProperties": false, @@ -39869,7 +39892,9 @@ "properties": { "failedRequests": { "type": "integer", - "minimum": 0 + "minimum": 0, + "nullable": true, + "description": "Null for a number read: audit logs have no immutable number attribution." }, "sessionDayCredits": { "type": "number" @@ -39898,13 +39923,15 @@ }, "byProject": { "type": "array", - "description": "Included for a team-wide read; omitted for a selected project. Active projects only. Team-level activity without a project is included in totals but has no project row.", + "description": "Included for a team-wide read; omitted for a selected project or number. Active projects only. Team-level activity without a project is included in totals but has no project row.", "items": { "$ref": "#/components/schemas/ConsoleProjectUsage" } } }, "required": [ + "scope", + "monthlyCalls", "period", "metrics", "totals", @@ -39912,6 +39939,79 @@ "consumption" ] }, + "ConsoleUsageScope": { + "type": "object", + "additionalProperties": false, + "properties": { + "kind": { + "type": "string", + "enum": [ + "team", + "project", + "number" + ] + }, + "orgId": { + "type": "string", + "format": "uuid" + }, + "projectId": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "numberId": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "numberName": { + "type": "string", + "nullable": true, + "description": "Selected number display name or session name; null without a selected number." + } + }, + "required": [ + "kind", + "orgId", + "projectId", + "numberId", + "numberName" + ] + }, + "ConsoleMonthlyOutgoingCalls": { + "type": "object", + "additionalProperties": false, + "properties": { + "periodStart": { + "type": "integer", + "format": "int64", + "description": "Inclusive first day of the current UTC calendar month in Unix milliseconds." + }, + "periodEnd": { + "type": "integer", + "format": "int64", + "description": "Exclusive first day of the next UTC calendar month in Unix milliseconds." + }, + "used": { + "type": "integer", + "minimum": 0, + "description": "Completed outbound call.duration records for the exact number and project, including zero-duration calls. In-flight placements are not included." + }, + "limit": { + "type": "integer", + "minimum": 0, + "nullable": true, + "description": "Included number-tier monthly outgoing calls, using the runtime gate tier lookup. Null means a known unlimited tier. Separate staff gate overrides are not the included allowance." + } + }, + "required": [ + "periodStart", + "periodEnd", + "used", + "limit" + ] + }, "ConsoleConsumption": { "type": "object", "additionalProperties": false, @@ -40135,16 +40235,20 @@ }, "apiRequests": { "type": "integer", - "minimum": 0 + "minimum": 0, + "nullable": true, + "description": "Null for number scope: request logs have no immutable number attribution." }, "failedRequests": { "type": "integer", - "minimum": 0 + "minimum": 0, + "nullable": true }, "peakRequestRate": { "type": "integer", "minimum": 0, - "description": "Maximum API and MCP request count in one UTC minute on this date within the requested range." + "nullable": true, + "description": "Null for number scope. Maximum API and MCP request count in one UTC minute on this date within the requested range." }, "activeDays": { "type": "integer", diff --git a/contracts/source.json b/contracts/source.json index 336fe49b..a5791687 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,9 +1,9 @@ { "repository": "polymorfa/polymorfa", - "commit": "9aa751ef923990c4b9ff6df56f64e35c7ae78027", + "commit": "49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3", "branch": "t3code/project-billing-dashboard", "published": false, - "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights distinguish catalog pricing from measured quantities and actual charged credits; completed outbound call counts and admitted project invocation receipts are observed, while complete WhatsApp media/HMS GB-month usage is unavailable. The production-number total no longer uses legacy session_limit as a cap. Console insights remain excluded from server SDK authority. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", + "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights accept an immutable number UUID bound to its selected project, return exact scope metadata, and report current-UTC-month completed outgoing calls against the number-tier included allowance independently of the chart period. Request logs and serverless receipts have no number attribution; their number measurements are unavailable. WhatsApp media/HMS GB-month measurement remains unavailable. No allowance pooling, new billing policy or settlement is added. Console insights remain excluded from server SDK authority. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", @@ -13,7 +13,7 @@ "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "4d0b041063833c498b9e47252e8612c15666518212891965042e90d5008ee088" + "sha256": "5f3e8a47070faa2584260a9c1f58fb1088f8e7e0c5a7e279d03435c6225f7630" } } } diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index 53a18139..7329bab0 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -196,7 +196,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("9aa751ef923990c4b9ff6df56f64e35c7ae78027"); + expect(source.commit).toBe("49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index b56f1502..253576dc 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -173,7 +173,7 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "9aa751ef923990c4b9ff6df56f64e35c7ae78027", + sourceCommit: "49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3", total: 568, covered: 429, partial: 0, From 9813ea683f16fe43d89389409de08f11d09849ea Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 23:11:27 +0300 Subject: [PATCH 08/11] chore(contracts): pin private bounded number selector Consume immutable API source b8fa167e47c901131d3bd9865e6479b153ad5157 byte-identically. Add only the dashboard-identity exclusion for listConsoleNumberOptions; preserve every preceding fingerprint and independent supplement pin. Verify 569 operations with 429 covered and 140 excluded. --- contracts/README.md | 12 +- contracts/coverage.json | 13 +- contracts/openapi.platform.json | 177 +++++++++++++++++- contracts/source.json | 6 +- .../test/coverage-reconciliation.test.ts | 22 ++- packages/typescript/test/coverage.test.ts | 6 +- 6 files changed, 224 insertions(+), 12 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index 7a2e0816..8f1c2cf5 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -42,15 +42,21 @@ published SDK package before updating their pinned dependency. ## Full snapshots The current snapshots are byte-identical copies of Messaging and Platform -contracts at API implementation commit `49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3` on +contracts at API implementation commit `b8fa167e47c901131d3bd9865e6479b153ad5157` on `t3code/project-billing-dashboard`. This branch is unmerged. Seven financial operations cover limits, priorities, complete ordering and one-resource combined read/save. Project priority takes precedence. Numbers inherit customer priority; a higher number priority overrides it within the project. Customer limits still include all assigned numbers. Initial real QuickLink creation accepts optional billing controls before admission. -This repin changes only the excluded private Console `usageInsight` fingerprint. -It adds exact org/project/number scope metadata and immutable-number filtering. +This repin adds only the private Console `listConsoleNumberOptions` operation, +a bounded searchable number-label read with keyset pagination. Its dashboard +identity requirement excludes it from server SDK methods. All 568 preceding +operation fingerprints and the public API contract remain unchanged. The ledger +now contains 569 operations: 429 covered and 140 excluded. +The preceding private `usageInsight` contract at +`49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3` +added exact org/project/number scope metadata and immutable-number filtering. The selected number's current UTC monthly completed outgoing calls and included allowance remain separate from the chart period. Unknown tiers have no allowance read; known unlimited tiers are explicit. Request logs and serverless receipts diff --git a/contracts/coverage.json b/contracts/coverage.json index 10783c90..3e7ff8a3 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3", + "sourceCommit": "b8fa167e47c901131d3bd9865e6479b153ad5157", "excludedContracts": [ { "family": "graph", @@ -2962,6 +2962,17 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "GET", + "path": "/console/insights/number-options", + "operationId": "listConsoleNumberOptions", + "fingerprint": "b8f7a1985611bccafc827257f68e3e7f0c147230e59bf1343d223e02ca246333", + "typescript": { + "status": "excluded", + "reason": "Private Console selector requires dashboard identity; organization API keys and project tokens are not accepted." + } + }, { "family": "platform", "method": "GET", diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index e9d78e17..9ac6904f 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -3,7 +3,7 @@ "info": { "title": "Polymorfa Console & Management API", "version": "1.0.0", - "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (243 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (104 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." + "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (243 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (105 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." }, "servers": [ { @@ -37818,6 +37818,114 @@ } } }, + "/console/insights/number-options": { + "get": { + "operationId": "listConsoleNumberOptions", + "tags": [ + "insights" + ], + "summary": "Search Console number options", + "description": "Private dashboard-only, org-scoped production number labels for selectors. Optional projectId must name an active, nondeleting project. Search is a literal case-insensitive substring of display name, current phone, session name or immutable number UUID, evaluated before pagination. Numbers in testing, deletion or deleted status and inactive/deleting projects are excluded; expired number history remains selectable. Results use immutable UUID ascending keyset order, default 25 and maximum 100; a cursor is bound to org, project and normalized search. It is navigation only and never grants authority. Response includes only number ID, project ID and a display label, with no configuration, owner, customer or provider fields. Public session listing is unchanged.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-exclusion-reason": "Dashboard-only bounded, searchable number labels; server credentials cannot use the selector read.", + "security": [ + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "query", + "required": false, + "description": "Project to read. Omit to total every project in the organization.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string", + "maxLength": 200 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/ConsoleNumberOptionsPage" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, "/console/insights/usage": { "get": { "operationId": "usageInsight", @@ -39845,6 +39953,73 @@ "data" ] }, + "ConsoleNumberOptionsPage": { + "type": "object", + "additionalProperties": false, + "properties": { + "scope": { + "type": "object", + "additionalProperties": false, + "properties": { + "orgId": { + "type": "string", + "format": "uuid" + }, + "projectId": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "orgId", + "projectId" + ] + }, + "items": { + "type": "array", + "maxItems": 100, + "items": { + "$ref": "#/components/schemas/ConsoleNumberOption" + } + }, + "nextCursor": { + "type": "string", + "nullable": true, + "maxLength": 2048 + } + }, + "required": [ + "scope", + "items", + "nextCursor" + ] + }, + "ConsoleNumberOption": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Immutable number UUID, never its reusable session name." + }, + "projectId": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Trimmed number name, else current phone, else session name (UUID fallback when all labels are blank)." + } + }, + "required": [ + "id", + "projectId", + "name" + ] + }, "ConsoleUsageInsights": { "type": "object", "additionalProperties": false, diff --git a/contracts/source.json b/contracts/source.json index a5791687..4a12a9a9 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,9 +1,9 @@ { "repository": "polymorfa/polymorfa", - "commit": "49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3", + "commit": "b8fa167e47c901131d3bd9865e6479b153ad5157", "branch": "t3code/project-billing-dashboard", "published": false, - "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights accept an immutable number UUID bound to its selected project, return exact scope metadata, and report current-UTC-month completed outgoing calls against the number-tier included allowance independently of the chart period. Request logs and serverless receipts have no number attribution; their number measurements are unavailable. WhatsApp media/HMS GB-month measurement remains unavailable. No allowance pooling, new billing policy or settlement is added. Console insights remain excluded from server SDK authority. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", + "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights accept an immutable number UUID bound to its selected project, return exact scope metadata, and report current-UTC-month completed outgoing calls against the number-tier included allowance independently of the chart period. Request logs and serverless receipts have no number attribution; their number measurements are unavailable. WhatsApp media/HMS GB-month measurement remains unavailable. No allowance pooling, new billing policy or settlement is added. Private Console number options add a bounded, searchable keyset-paginated label read scoped to the live organization and optional project. Console insights and number options remain excluded from server SDK authority; no public SDK method or credential scope changes. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", @@ -13,7 +13,7 @@ "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "5f3e8a47070faa2584260a9c1f58fb1088f8e7e0c5a7e279d03435c6225f7630" + "sha256": "8c5bbb6a87b8b912036f80ad6d389dadff06cda9ca89e238d45af7307ea9b530" } } } diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index 7329bab0..772bb1c3 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -196,7 +196,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3"); + expect(source.commit).toBe("b8fa167e47c901131d3bd9865e6479b153ad5157"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", @@ -349,6 +349,26 @@ describe("reconciled coverage evidence", () => { ).toBe(false); }); + it("excludes the bounded number selector based on its dashboard identity contract", () => { + const operation = entry("listConsoleNumberOptions"); + expect(operation).toMatchObject({ + family: "platform", + method: "GET", + path: "/console/insights/number-options", + typescript: { status: "excluded" }, + }); + expect(operation.typescript.reason).toContain("dashboard identity"); + expect(operation.typescript.method).toBeUndefined(); + const platform = JSON.parse( + readFileSync(repositoryFile("contracts/openapi.platform.json"), "utf8"), + ) as { + paths: Record>; + }; + expect(platform.paths[operation.path]!.get!.security).toEqual([ + { ConsoleSession: [] }, + ]); + }); + it("excludes the added Console routes based on their credential contract", () => { const platform = JSON.parse( readFileSync(repositoryFile("contracts/openapi.platform.json"), "utf8"), diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index 253576dc..cf76de49 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -173,12 +173,12 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3", - total: 568, + sourceCommit: "b8fa167e47c901131d3bd9865e6479b153ad5157", + total: 569, covered: 429, partial: 0, missing: 0, - excluded: 139, + excluded: 140, changed: 0, }); const resolutions = (result.report?.resolutions ?? []) as Array<{ From 507b699c91f7be783328a371bf0040c8acc9447d Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 23:55:46 +0300 Subject: [PATCH 09/11] chore(contracts): inherit Console Logs in the exact Usage snapshot Pin byte-identical snapshots to integrated API source 8efb062e106ac1d6888bf6fff4fbcdddf8406e48. Exclude five inherited dashboard-only audit/calendar/logging operations and refresh only the private Logs filter fingerprint. Preserve public contracts and independent supplement pins; verify 574 operations, 1457 tests and all workspace builds. --- contracts/README.md | 19 +- contracts/coverage.json | 59 +- contracts/openapi.platform.json | 792 +++++++++++++++++- contracts/source.json | 8 +- packages/typescript/README.md | 20 +- .../test/coverage-reconciliation.test.ts | 30 +- packages/typescript/test/coverage.test.ts | 6 +- 7 files changed, 911 insertions(+), 23 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index 8f1c2cf5..c533c23d 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -42,18 +42,29 @@ published SDK package before updating their pinned dependency. ## Full snapshots The current snapshots are byte-identical copies of Messaging and Platform -contracts at API implementation commit `b8fa167e47c901131d3bd9865e6479b153ad5157` on -`t3code/project-billing-dashboard`. This branch is unmerged. Seven financial +contracts at API implementation commit `8efb062e106ac1d6888bf6fff4fbcdddf8406e48` on +`t3code/usage-logs-stack-integration`. This branch is unmerged. Seven financial operations cover limits, priorities, complete ordering and one-resource combined read/save. Project priority takes precedence. Numbers inherit customer priority; a higher number priority overrides it within the project. Customer limits still include all assigned numbers. Initial real QuickLink creation accepts optional billing controls before admission. -This repin adds only the private Console `listConsoleNumberOptions` operation, +This integrated source inherits five private Console operations: Settings +Activity, the retained-log calendar, project logging reads, sampling writes and +HTTPS drain writes. All require dashboard identity and remain excluded from +server SDK authority. The already-excluded `logsInsight` fingerprint changes +only for its server-side `filters` and `search` parameters. All public and other +existing operation fingerprints remain unchanged. The ledger contains 574 +operations: 429 covered and 145 excluded. The financial methods operate actual +monthly credit-debit limits; the separate nested included-usage/overage quota +model remains an unshipped prototype outside those methods. + +The preceding repin at `b8fa167e47c901131d3bd9865e6479b153ad5157` +added only the private Console `listConsoleNumberOptions` operation, a bounded searchable number-label read with keyset pagination. Its dashboard identity requirement excludes it from server SDK methods. All 568 preceding operation fingerprints and the public API contract remain unchanged. The ledger -now contains 569 operations: 429 covered and 140 excluded. +then contained 569 operations: 429 covered and 140 excluded. The preceding private `usageInsight` contract at `49b529fd74f42fdd6bb30671ebd4b0ec8e9558f3` added exact org/project/number scope metadata and immutable-number filtering. diff --git a/contracts/coverage.json b/contracts/coverage.json index 3e7ff8a3..da40b710 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "b8fa167e47c901131d3bd9865e6479b153ad5157", + "sourceCommit": "8efb062e106ac1d6888bf6fff4fbcdddf8406e48", "excludedContracts": [ { "family": "graph", @@ -2841,6 +2841,17 @@ "reason": "Dashboard identity and account controls require a live browser account session; not part of the machine SDK." } }, + { + "family": "platform", + "method": "GET", + "path": "/console/audit", + "operationId": "getConsoleAuditLogs", + "fingerprint": "cee6857cf3f81bf70aa2483d0146a3267ec1a7aef0ea3cf4a09828fb6196d3d6", + "typescript": { + "status": "excluded", + "reason": "Console-only operation requires dashboard identity; organization API keys and project tokens are not accepted." + } + }, { "family": "platform", "method": "GET", @@ -2951,12 +2962,23 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "GET", + "path": "/console/insights/log-calendar", + "operationId": "getLogCalendar", + "fingerprint": "1e18f21016e4a62f467b64b6d192c2eecbba23ce3e6f03a617329db3601e0554", + "typescript": { + "status": "excluded", + "reason": "Console-only operation requires dashboard identity; organization API keys and project tokens are not accepted." + } + }, { "family": "platform", "method": "GET", "path": "/console/insights/logs", "operationId": "logsInsight", - "fingerprint": "c4a0a6f7a351e4e806335e6ed0eddc9f8c0c35203abe8637d2dfe0dbe010a46c", + "fingerprint": "1c779fc4eceba62a2f40be524fa30f0e3477cc88535d303f0fa6a46a8379b330", "typescript": { "status": "excluded", "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." @@ -3061,6 +3083,17 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "GET", + "path": "/console/projects/{projectId}/logging", + "operationId": "getProjectLogging", + "fingerprint": "ee09b8d569d9b33bd5e65c1ead0bb2b71ee4b8e8e766627bb97a99e24f78fc19", + "typescript": { + "status": "excluded", + "reason": "Console-only operation requires dashboard identity; organization API keys and project tokens are not accepted." + } + }, { "family": "platform", "method": "GET", @@ -6039,6 +6072,28 @@ "reason": "Console-only operation requires dashboard identity; not available to SDK credentials." } }, + { + "family": "platform", + "method": "PUT", + "path": "/console/projects/{projectId}/logging/drains", + "operationId": "saveProjectDrain", + "fingerprint": "d6464828734345de6640155503d469531741f527bcb1bcd24d28ecdc15d39aae", + "typescript": { + "status": "excluded", + "reason": "Console-only operation requires dashboard identity; organization API keys and project tokens are not accepted." + } + }, + { + "family": "platform", + "method": "PUT", + "path": "/console/projects/{projectId}/logging/sampling", + "operationId": "saveProjectSampling", + "fingerprint": "fb655f6a95bc6ba532942cf41e9a4963db9eaa0d100714925d377f9318a27779", + "typescript": { + "status": "excluded", + "reason": "Console-only operation requires dashboard identity; organization API keys and project tokens are not accepted." + } + }, { "family": "platform", "method": "PUT", diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index 9ac6904f..a6b6ba4a 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -3,7 +3,7 @@ "info": { "title": "Polymorfa Console & Management API", "version": "1.0.0", - "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (243 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (105 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." + "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (243 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (110 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." }, "servers": [ { @@ -37698,6 +37698,780 @@ } } }, + "/console/audit": { + "get": { + "operationId": "getConsoleAuditLogs", + "tags": [ + "audit" + ], + "summary": "Read scoped Settings audit history", + "description": "Dashboard-only team or project audit history for a date range. Includes actor name and profile image only for recorded actors in the authenticated team. Public audit API response stays unchanged.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-dashboard-only": true, + "security": [ + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "query", + "required": false, + "description": "Project to read. Omit to total every project in the organization.", + "schema": { + "type": "string" + } + }, + { + "name": "start", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "end", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "array", + "maxItems": 500, + "items": { + "type": "object", + "additionalProperties": true + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + } + } + }, + "/console/projects/{projectId}/logging": { + "get": { + "operationId": "getProjectLogging", + "tags": [ + "projects" + ], + "summary": "Read project log forwarding", + "description": "Project-scoped dashboard configuration. Returns destination URLs and header names, never saved header values or signing secrets.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-dashboard-only": true, + "security": [ + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "revision", + "disabled", + "rules", + "drains" + ], + "properties": { + "revision": { + "type": "integer", + "minimum": 0 + }, + "disabled": { + "type": "boolean" + }, + "rules": { + "type": "array", + "maxItems": 20, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "rate" + ], + "properties": { + "rate": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "type": { + "type": "string", + "enum": [ + "api", + "webhooks", + "sessions" + ] + }, + "kind": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "identifier": { + "type": "string", + "enum": [ + "requestId", + "sessionId", + "eventId", + "webhookId", + "customerId", + "numberId", + "conversationId" + ] + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + } + } + }, + "drains": { + "type": "array", + "maxItems": 5, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "name", + "url", + "enabled", + "revision", + "headerNames", + "hasSigningSecret", + "pending", + "failed", + "lastStatus", + "lastError" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "revision": { + "type": "integer", + "minimum": 1 + }, + "headerNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "hasSigningSecret": { + "type": "boolean" + }, + "pending": { + "type": "integer", + "minimum": 0 + }, + "failed": { + "type": "integer", + "minimum": 0 + }, + "lastStatus": { + "type": "integer", + "nullable": true + }, + "lastError": { + "type": "string", + "nullable": true + } + } + } + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + } + } + }, + "/console/projects/{projectId}/logging/sampling": { + "put": { + "operationId": "saveProjectSampling", + "tags": [ + "projects" + ], + "summary": "Save ordered log sampling rules", + "description": "Samples operational forwarding by request/event identity. Source logs and permanent audit history remain complete. Requires an exact revision and operation UUID; retries replay for 30 days. Incompatible type/identifier combinations are rejected.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-dashboard-only": true, + "x-required-organization-role": "owner_or_admin", + "security": [ + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "required": [ + "operationId", + "revision", + "rules" + ], + "properties": { + "operationId": { + "type": "string", + "format": "uuid" + }, + "revision": { + "type": "integer", + "minimum": 0 + }, + "rules": { + "type": "array", + "maxItems": 20, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "rate" + ], + "properties": { + "rate": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "type": { + "type": "string", + "enum": [ + "api", + "webhooks", + "sessions" + ] + }, + "kind": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "identifier": { + "type": "string", + "enum": [ + "requestId", + "sessionId", + "eventId", + "webhookId", + "customerId", + "numberId", + "conversationId" + ] + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "object", + "required": [ + "revision" + ], + "properties": { + "revision": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + } + } + }, + "/console/projects/{projectId}/logging/drains": { + "put": { + "operationId": "saveProjectDrain", + "tags": [ + "projects" + ], + "summary": "Configure a custom HTTPS log drain", + "description": "Creates, pauses, resumes or deletes one project drain with revision guards and exact operation replay. Up to five drains. URLs and headers are encrypted at rest. Delivery is at least once, never follows redirects, retries at most six times over 24 hours, and retains delivery metadata for seven days. Deleting removes that drain and its queue.\n\nRequires a verified dashboard bearer and current organization authorization. The signed-in member must be an organization owner or admin. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-dashboard-only": true, + "x-required-organization-role": "owner_or_admin", + "security": [ + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "required": [ + "operationId", + "id", + "revision", + "name", + "enabled" + ], + "properties": { + "operationId": { + "type": "string", + "format": "uuid" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "revision": { + "type": "integer", + "minimum": 0 + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 80 + }, + "enabled": { + "type": "boolean" + }, + "destination": { + "type": "object", + "additionalProperties": false, + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "format": "uri", + "maxLength": 2048 + }, + "headers": { + "type": "object", + "maxProperties": 20, + "additionalProperties": { + "type": "string", + "maxLength": 2048 + } + }, + "signingSecret": { + "type": "string", + "minLength": 16, + "maxLength": 256 + } + } + }, + "remove": { + "type": "boolean" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "oneOf": [ + { + "type": "object", + "required": [ + "id", + "revision" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "revision": { + "type": "integer", + "minimum": 1 + } + } + }, + { + "type": "object", + "required": [ + "removed" + ], + "properties": { + "removed": { + "type": "boolean", + "enum": [ + true + ] + } + } + } + ] + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + } + } + }, + "/console/insights/log-calendar": { + "get": { + "operationId": "getLogCalendar", + "tags": [ + "insights" + ], + "summary": "Read scoped log activity dates", + "description": "Returns the earliest retained operational log and all activity dates in the requested IANA timezone. Does not depend on the log list result limit or active filters.\n\nRequires a verified dashboard bearer and current organization authorization. Organization keys and project tokens are not accepted.", + "x-audience": "console", + "x-polymorfa-resource-boundary": "human_only", + "x-excluded-from-public": true, + "x-dashboard-only": true, + "security": [ + { + "ConsoleSession": [] + } + ], + "parameters": [ + { + "name": "projectId", + "in": "query", + "required": false, + "description": "Project to read. Omit to total every project in the organization.", + "schema": { + "type": "string" + } + }, + { + "name": "timezone", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "scope", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Stable request correlation identifier." + }, + "Polymorfa-Version": { + "schema": { + "type": "string" + }, + "description": "API version applied to this response." + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "data": { + "type": "object", + "required": [ + "earliest", + "days" + ], + "properties": { + "earliest": { + "type": "integer", + "nullable": true + }, + "days": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" + } + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + } + } + }, "/console/insights/logs": { "get": { "operationId": "logsInsight", @@ -37779,6 +38553,22 @@ "maxLength": 128, "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$" } + }, + { + "name": "filters", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { diff --git a/contracts/source.json b/contracts/source.json index 4a12a9a9..453d4698 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,9 +1,9 @@ { "repository": "polymorfa/polymorfa", - "commit": "b8fa167e47c901131d3bd9865e6479b153ad5157", - "branch": "t3code/project-billing-dashboard", + "commit": "8efb062e106ac1d6888bf6fff4fbcdddf8406e48", + "branch": "t3code/usage-logs-stack-integration", "published": false, - "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights accept an immutable number UUID bound to its selected project, return exact scope metadata, and report current-UTC-month completed outgoing calls against the number-tier included allowance independently of the chart period. Request logs and serverless receipts have no number attribution; their number measurements are unavailable. WhatsApp media/HMS GB-month measurement remains unavailable. No allowance pooling, new billing policy or settlement is added. Private Console number options add a bounded, searchable keyset-paginated label read scoped to the live organization and optional project. Console insights and number options remain excluded from server SDK authority; no public SDK method or credential scope changes. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", + "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights accept an immutable number UUID bound to its selected project, return exact scope metadata, and report current-UTC-month completed outgoing calls against the number-tier included allowance independently of the chart period. Request logs and serverless receipts have no number attribution; their number measurements are unavailable. WhatsApp media/HMS GB-month measurement remains unavailable. No allowance pooling, new billing policy or settlement is added. Private Console number options add a bounded, searchable keyset-paginated label read scoped to the live organization and optional project. Console insights and number options remain excluded from server SDK authority; no public SDK method or credential scope changes. Inherited Console Logs adds five private audit, calendar and logging-control operations and server-side filter parameters on the private Logs read. All remain dashboard-only exclusions; no public SDK runtime or financial contract changes. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", @@ -13,7 +13,7 @@ "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "8c5bbb6a87b8b912036f80ad6d389dadff06cda9ca89e238d45af7307ea9b530" + "sha256": "7f29c5c4a3d309a127e34fcee8dfcedada572141ab5c5902886abe3c4dcf8a7c" } } } diff --git a/packages/typescript/README.md b/packages/typescript/README.md index 3776eeaf..a74f40dd 100644 --- a/packages/typescript/README.md +++ b/packages/typescript/README.md @@ -2873,7 +2873,7 @@ customer log output are not retained. List responses contain `items` and ```ts const limits = await platform.billing.getLimits({ projectId }); -const number = limits.data.data.budgets.find(b => b.scope === "number")!; +const number = limits.data.data.budgets.find((b) => b.scope === "number")!; await platform.billing.setLimit("number", number.resourceId, { limitCredits: 1000, expectedRevision: number.revision, @@ -2881,7 +2881,7 @@ await platform.billing.setLimit("number", number.resourceId, { const priorities = await platform.billing.getPriorities(); await platform.billing.reorderPriorities({ scope: "project", - resourceIds: priorities.data.data.projects.map(p => p.id).reverse(), + resourceIds: priorities.data.data.projects.map((p) => p.id).reverse(), expectedRevision: priorities.data.data.revision, }); ``` @@ -2894,14 +2894,16 @@ removes a cap; zero blocks new charges. Paid windows remain intact. These methods do not grant permissions, paid access or deployed availability. Publication of this source revision is separate from API deployment. - For one customer or number, read and save both controls without loading the organization directory: ```typescript -const {data: {data: current}} = await platform.billing.getResourceControls("customer", customerId); +const { + data: { data: current }, +} = await platform.billing.getResourceControls("customer", customerId); await platform.billing.setResourceControls("customer", customerId, { - limitCredits: 1500, priority: 25, + limitCredits: 1500, + priority: 25, expectedBudgetRevision: current.budget.revision, expectedPriorityRevision: current.priorityRevision, }); @@ -2921,14 +2923,16 @@ Read one project's funding controls and save its combined customer and number order: ```typescript -const { data: { data: controls } } = await platform.billing.getPriorities({ projectId }); +const { + data: { data: controls }, +} = await platform.billing.getPriorities({ projectId }); const resources = [ - ...controls.customers.map(customer => ({ + ...controls.customers.map((customer) => ({ scope: "customer" as const, resourceId: customer.id, priority: customer.priority, })), - ...controls.numbers.map(number => ({ + ...controls.numbers.map((number) => ({ scope: "number" as const, resourceId: number.id, priority: number.priority, diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index 772bb1c3..fcb6f69b 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -196,7 +196,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("b8fa167e47c901131d3bd9865e6479b153ad5157"); + expect(source.commit).toBe("8efb062e106ac1d6888bf6fff4fbcdddf8406e48"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", @@ -392,4 +392,32 @@ describe("reconciled coverage evidence", () => { ).toEqual([{ ConsoleSession: [] }]); } }); + + it("keeps inherited Logs operations excluded according to dashboard identity", () => { + const platform = JSON.parse( + readFileSync(repositoryFile("contracts/openapi.platform.json"), "utf8"), + ) as { + paths: Record>; + }; + for (const operationId of [ + "getConsoleAuditLogs", + "getLogCalendar", + "getProjectLogging", + "saveProjectDrain", + "saveProjectSampling", + "logsInsight", + ]) { + const operation = entry(operationId); + expect(operation.typescript.status, operationId).toBe("excluded"); + expect(operation.typescript.reason, operationId).toContain( + "dashboard identity", + ); + expect(operation.typescript.method, operationId).toBeUndefined(); + expect( + platform.paths[operation.path]![operation.method.toLowerCase()]! + .security, + operationId, + ).toEqual([{ ConsoleSession: [] }]); + } + }); }); diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index cf76de49..900a5e5b 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -173,12 +173,12 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "b8fa167e47c901131d3bd9865e6479b153ad5157", - total: 569, + sourceCommit: "8efb062e106ac1d6888bf6fff4fbcdddf8406e48", + total: 574, covered: 429, partial: 0, missing: 0, - excluded: 140, + excluded: 145, changed: 0, }); const resolutions = (result.report?.resolutions ?? []) as Array<{ From e1ec65758a7f540beeb29a2808be3b1a30a77c06 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 4 Oct 2026 03:18:02 +0300 Subject: [PATCH 10/11] Pin Usage SDK contracts to the reconciled PR stack --- contracts/README.md | 4 ++-- contracts/coverage.json | 2 +- contracts/source.json | 6 +++--- packages/typescript/test/coverage-reconciliation.test.ts | 2 +- packages/typescript/test/coverage.test.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index c533c23d..d1c65da5 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -42,8 +42,8 @@ published SDK package before updating their pinned dependency. ## Full snapshots The current snapshots are byte-identical copies of Messaging and Platform -contracts at API implementation commit `8efb062e106ac1d6888bf6fff4fbcdddf8406e48` on -`t3code/usage-logs-stack-integration`. This branch is unmerged. Seven financial +contracts at API implementation commit `ac1c7010d5c3cdf0262f1b5deb2c4d7ce6a249a5` on +`t3code/project-billing-dashboard`. This branch is unmerged. Seven financial operations cover limits, priorities, complete ordering and one-resource combined read/save. Project priority takes precedence. Numbers inherit customer priority; a higher number priority overrides it within the project. Customer limits still diff --git a/contracts/coverage.json b/contracts/coverage.json index da40b710..49cd9221 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "8efb062e106ac1d6888bf6fff4fbcdddf8406e48", + "sourceCommit": "ac1c7010d5c3cdf0262f1b5deb2c4d7ce6a249a5", "excludedContracts": [ { "family": "graph", diff --git a/contracts/source.json b/contracts/source.json index 453d4698..4488715a 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -1,9 +1,9 @@ { "repository": "polymorfa/polymorfa", - "commit": "8efb062e106ac1d6888bf6fff4fbcdddf8406e48", - "branch": "t3code/usage-logs-stack-integration", + "commit": "ac1c7010d5c3cdf0262f1b5deb2c4d7ce6a249a5", + "branch": "t3code/project-billing-dashboard", "published": false, - "note": "Project, customer and number funding controls retain their existing public contract. Private Console usage insights accept an immutable number UUID bound to its selected project, return exact scope metadata, and report current-UTC-month completed outgoing calls against the number-tier included allowance independently of the chart period. Request logs and serverless receipts have no number attribution; their number measurements are unavailable. WhatsApp media/HMS GB-month measurement remains unavailable. No allowance pooling, new billing policy or settlement is added. Private Console number options add a bounded, searchable keyset-paginated label read scoped to the live organization and optional project. Console insights and number options remain excluded from server SDK authority; no public SDK method or credential scope changes. Inherited Console Logs adds five private audit, calendar and logging-control operations and server-side filter parameters on the private Logs read. All remain dashboard-only exclusions; no public SDK runtime or financial contract changes. Snapshots are byte-identical to this source commit. This implementation branch is unmerged; API deployment and package publication remain separate gates.", + "note": "Exact Messaging and Platform snapshots from the Usage and Logs stack. Financial operations require organization billing scopes. Private Console operations remain excluded from SDK credentials. API deployment and SDK publication are separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index fcb6f69b..de3fe0df 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -196,7 +196,7 @@ describe("reconciled coverage evidence", () => { }; expect(source.repository).toBe("polymorfa/polymorfa"); // Repinning the reviewed source requires updating this regression gate too. - expect(source.commit).toBe("8efb062e106ac1d6888bf6fff4fbcdddf8406e48"); + expect(source.commit).toBe("ac1c7010d5c3cdf0262f1b5deb2c4d7ce6a249a5"); expect(ledger.sourceCommit).toBe(source.commit); expect(Object.keys(source.contracts).sort()).toEqual([ "messaging", diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index 900a5e5b..2d03f318 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -173,7 +173,7 @@ describe("coverage checker", () => { const result = runRepositoryChecker(); expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ - sourceCommit: "8efb062e106ac1d6888bf6fff4fbcdddf8406e48", + sourceCommit: "ac1c7010d5c3cdf0262f1b5deb2c4d7ce6a249a5", total: 574, covered: 429, partial: 0, From 89fa71ceaeeb9989dea24436e75554b60e5c539e Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 4 Oct 2026 03:55:56 +0300 Subject: [PATCH 11/11] Serialize only declared resource billing control fields --- packages/typescript/src/platform/billing.ts | 7 ++++++- packages/typescript/test/billing-controls.test.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/typescript/src/platform/billing.ts b/packages/typescript/src/platform/billing.ts index b219535e..f1a54bb7 100644 --- a/packages/typescript/src/platform/billing.ts +++ b/packages/typescript/src/platform/billing.ts @@ -79,7 +79,12 @@ export class BillingResource { return this.transport.request({ method: "PUT", path: `/platform/billing/controls/${scope}/${billingId(resourceId)}`, - body: input, + body: { + limitCredits: input.limitCredits, + priority: input.priority, + expectedBudgetRevision: input.expectedBudgetRevision, + expectedPriorityRevision: input.expectedPriorityRevision, + }, ...options, }); } diff --git a/packages/typescript/test/billing-controls.test.ts b/packages/typescript/test/billing-controls.test.ts index 4285bf1f..c2979983 100644 --- a/packages/typescript/test/billing-controls.test.ts +++ b/packages/typescript/test/billing-controls.test.ts @@ -140,7 +140,8 @@ it("serializes singleton customer controls with both revisions and validates bef expectedBudgetRevision: 1, expectedPriorityRevision: 2, }; - await client.billing.setResourceControls("customer", id, input); + const callerInput = { ...input, unrelatedMetadata: "do not send" }; + await client.billing.setResourceControls("customer", id, callerInput); expect(server.requests.map((r) => [r.method, r.path])).toEqual([ ["GET", `/platform/billing/controls/customer/${id}`], ["PUT", `/platform/billing/controls/customer/${id}`],