From cb1d0556d3452973106bbb4dba5a62924c4030b3 Mon Sep 17 00:00:00 2001 From: "lin.si" Date: Tue, 8 Sep 2026 14:45:35 +0800 Subject: [PATCH 1/3] fix: expose account admission errors and creation replay contract --- openapi/calle.openapi.yaml | 32 ++++++++++++++++++---- package.json | 2 +- scripts/verify-openapi-contract.mjs | 5 ++-- src/generated/schema.ts | 42 ++++++++++++++++++++++++----- 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/openapi/calle.openapi.yaml b/openapi/calle.openapi.yaml index 7c5004f..d229cee 100644 --- a/openapi/calle.openapi.yaml +++ b/openapi/calle.openapi.yaml @@ -15,7 +15,7 @@ paths: tags: - calls summary: Create Call - description: Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence. + description: Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence. The default outbound line supports one phone number per task. Batch calls require an eligible purchased number selected as the account default outbound number; otherwise creation returns `422 call_not_ready`. Each authenticated user can create up to 20 call plans in any rolling 24-hour period; additional requests return `429 rate_limit_exceeded` before planning begins. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: @@ -26,7 +26,7 @@ paths: $ref: "#/components/schemas/CreateCallRequest" examples: batchLunch: - summary: Check Friday lunch availability for two recipients. + summary: Check Friday lunch availability for two recipients using an eligible purchased outbound number. value: task: Call each recipient and ask whether they can attend Friday lunch in San Francisco. recipients: @@ -172,9 +172,27 @@ paths: "422": $ref: "#/components/responses/ErrorResponse" "429": - $ref: "#/components/responses/ErrorResponse" + description: A call-plan rate limit, account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + example: + error: + code: rate_limit_exceeded + message: The 24-hour call plan limit has been reached. + details: + limit: 20 + window_hours: 24 + count: 20 "500": $ref: "#/components/responses/ErrorResponse" + "503": + description: The provider or an account control is unavailable (`provider_unavailable`, `account_concurrency_unavailable`, or `llm_token_budget_unavailable`). + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" /v1/calls/{call_id}: get: operationId: getCall @@ -738,7 +756,7 @@ components: IdempotencyKey: name: Idempotency-Key in: header - description: Stable caller-provided key used to make create-call retries safe. Reusing the same key with the same request returns the original call instead of creating a duplicate. + description: Stable caller-provided key used to make create-call retries safe. Reusing the same key with the same request returns the original call instead of creating a duplicate. A persisted creation failure replays its original HTTP status and error body; use a new key for a new attempt after resolving the error. While creation is still in progress, a duplicate returns `409 idempotency_conflict` with `details.reason_code=creation_in_progress`. A different request using the same key returns `409 idempotency_conflict`. required: false schema: type: string @@ -1135,7 +1153,7 @@ components: type: - array - "null" - description: Optional explicit recipients for this call task. Omit it when the task text already contains the phone targets CALL-E should use. + description: Optional explicit recipients for this call task. Omit it when the task text already contains the phone targets CALL-E should use. The default outbound line permits one phone number in total across all recipients. Multiple targets require an eligible purchased outbound number; this also applies to targets inferred from task text. minItems: 1 items: $ref: "#/components/schemas/CallTaskRecipientRequest" @@ -1607,6 +1625,10 @@ components: - unauthorized - forbidden - rate_limit_exceeded + - account_concurrency_exceeded + - account_concurrency_unavailable + - llm_token_budget_exceeded + - llm_token_budget_unavailable - insufficient_balance - unsupported_region - unsupported_language diff --git a/package.json b/package.json index 2182dfc..e9b9f2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@call-e/calle", - "version": "0.7.0", + "version": "0.7.1", "description": "TypeScript server SDK for the CALL-E Developer API.", "type": "module", "keywords": [ diff --git a/scripts/verify-openapi-contract.mjs b/scripts/verify-openapi-contract.mjs index 42fcdeb..81c8ba9 100644 --- a/scripts/verify-openapi-contract.mjs +++ b/scripts/verify-openapi-contract.mjs @@ -41,7 +41,7 @@ const requiredOperations = [ requestSchema: "#/components/schemas/CreateCallRequest", responseStatus: "201", responseSchema: "#/components/schemas/CallTask", - errorStatuses: ["400", "401", "403", "409", "422", "429", "500"], + errorStatuses: ["400", "401", "403", "409", "422", "429", "500", "503"], }, { path: "/v1/calls/{call_id}", @@ -118,7 +118,8 @@ for (const operation of requiredOperations) { for (const status of operation.errorStatuses) { assertContract( - endpoint.responses?.[status]?.$ref === "#/components/responses/ErrorResponse", + endpoint.responses?.[status]?.$ref === "#/components/responses/ErrorResponse" || + endpoint.responses?.[status]?.content?.["application/json"]?.schema?.$ref === "#/components/schemas/ErrorEnvelope", `missing stable ${status} error response for ${operation.method.toUpperCase()} ${operation.path}`, ); } diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 9e1c3cb..4190d1e 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -15,7 +15,7 @@ export interface paths { put?: never; /** * Create Call - * @description Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence. + * @description Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence. The default outbound line supports one phone number per task. Batch calls require an eligible purchased number selected as the account default outbound number; otherwise creation returns `422 call_not_ready`. Each authenticated user can create up to 20 call plans in any rolling 24-hour period; additional requests return `429 rate_limit_exceeded` before planning begins. */ post: operations["createCall"]; delete?: never; @@ -378,7 +378,7 @@ export interface components { CreateCallRequest: { /** @description Natural-language instruction for the call task. Include the goal, relevant details the voice agent should know, and the exact information you want collected. */ task: string; - /** @description Optional explicit recipients for this call task. Omit it when the task text already contains the phone targets CALL-E should use. */ + /** @description Optional explicit recipients for this call task. Omit it when the task text already contains the phone targets CALL-E should use. The default outbound line permits one phone number in total across all recipients. Multiple targets require an eligible purchased outbound number; this also applies to targets inferred from task text. */ recipients?: components["schemas"]["CallTaskRecipientRequest"][] | null; /** * @description Optional JSON Schema object that defines the structured result CALL-E should extract for the whole call task. @@ -631,7 +631,7 @@ export interface components { }; APIError: { /** @enum {string} */ - code: "invalid_request" | "unauthorized" | "forbidden" | "rate_limit_exceeded" | "insufficient_balance" | "unsupported_region" | "unsupported_language" | "recipient_blocked" | "policy_violation" | "call_not_ready" | "no_recipients" | "invalid_recipient" | "invalid_phone" | "result_schema_invalid" | "recipient_result_schema_invalid" | "idempotency_conflict" | "goal_not_published" | "goal_not_executable" | "goal_not_ready" | "schema_override_not_allowed" | "variables_invalid" | "provider_unavailable" | "internal_error" | "not_found"; + code: "invalid_request" | "unauthorized" | "forbidden" | "rate_limit_exceeded" | "account_concurrency_exceeded" | "account_concurrency_unavailable" | "llm_token_budget_exceeded" | "llm_token_budget_unavailable" | "insufficient_balance" | "unsupported_region" | "unsupported_language" | "recipient_blocked" | "policy_violation" | "call_not_ready" | "no_recipients" | "invalid_recipient" | "invalid_phone" | "result_schema_invalid" | "recipient_result_schema_invalid" | "idempotency_conflict" | "goal_not_published" | "goal_not_executable" | "goal_not_ready" | "schema_override_not_allowed" | "variables_invalid" | "provider_unavailable" | "internal_error" | "not_found"; message: string; details: { [key: string]: unknown; @@ -652,7 +652,7 @@ export interface components { }; }; parameters: { - /** @description Stable caller-provided key used to make create-call retries safe. Reusing the same key with the same request returns the original call instead of creating a duplicate. */ + /** @description Stable caller-provided key used to make create-call retries safe. Reusing the same key with the same request returns the original call instead of creating a duplicate. A persisted creation failure replays its original HTTP status and error body; use a new key for a new attempt after resolving the error. While creation is still in progress, a duplicate returns `409 idempotency_conflict` with `details.reason_code=creation_in_progress`. A different request using the same key returns `409 idempotency_conflict`. */ IdempotencyKey: string; /** * @description Required business-stable identity for one logical Goal Run, scoped to the authenticated @@ -710,7 +710,7 @@ export interface operations { parameters: { query?: never; header?: { - /** @description Stable caller-provided key used to make create-call retries safe. Reusing the same key with the same request returns the original call instead of creating a duplicate. */ + /** @description Stable caller-provided key used to make create-call retries safe. Reusing the same key with the same request returns the original call instead of creating a duplicate. A persisted creation failure replays its original HTTP status and error body; use a new key for a new attempt after resolving the error. While creation is still in progress, a duplicate returns `409 idempotency_conflict` with `details.reason_code=creation_in_progress`. A different request using the same key returns `409 idempotency_conflict`. */ "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; }; path?: never; @@ -736,8 +736,38 @@ export interface operations { 403: components["responses"]["ErrorResponse"]; 409: components["responses"]["ErrorResponse"]; 422: components["responses"]["ErrorResponse"]; - 429: components["responses"]["ErrorResponse"]; + /** @description A call-plan rate limit, account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "error": { + * "code": "rate_limit_exceeded", + * "message": "The 24-hour call plan limit has been reached.", + * "details": { + * "limit": 20, + * "window_hours": 24, + * "count": 20 + * } + * } + * } + */ + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; 500: components["responses"]["ErrorResponse"]; + /** @description The provider or an account control is unavailable (`provider_unavailable`, `account_concurrency_unavailable`, or `llm_token_budget_unavailable`). */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; }; }; getCall: { From 28e35a1d30eaa51d97a21693c569878f4c6a4790 Mon Sep 17 00:00:00 2001 From: "lin.si" Date: Tue, 8 Sep 2026 15:56:40 +0800 Subject: [PATCH 2/3] docs(sdk): sync configured line admission contract --- openapi/calle.openapi.yaml | 13 +++++-------- src/generated/schema.ts | 14 +++++--------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/openapi/calle.openapi.yaml b/openapi/calle.openapi.yaml index d229cee..89c0220 100644 --- a/openapi/calle.openapi.yaml +++ b/openapi/calle.openapi.yaml @@ -15,7 +15,7 @@ paths: tags: - calls summary: Create Call - description: Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence. The default outbound line supports one phone number per task. Batch calls require an eligible purchased number selected as the account default outbound number; otherwise creation returns `422 call_not_ready`. Each authenticated user can create up to 20 call plans in any rolling 24-hour period; additional requests return `429 rate_limit_exceeded` before planning begins. + description: Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence. Shared platform outbound lines support one phone number per task. Batch calls require an eligible purchased number selected as the account default outbound number; otherwise creation returns `422 call_not_ready`. Account concurrency and LLM token usage are controlled by the effective account configuration. Task concurrency defaults to 1 on shared platform lines and 10 on eligible dedicated purchased numbers; selecting a purchased number as the account default does not make it a shared platform line. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: @@ -172,19 +172,16 @@ paths: "422": $ref: "#/components/responses/ErrorResponse" "429": - description: A call-plan rate limit, account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. + description: An account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" example: error: - code: rate_limit_exceeded - message: The 24-hour call plan limit has been reached. - details: - limit: 20 - window_hours: 24 - count: 20 + code: account_concurrency_exceeded + message: This account has reached its task concurrency limit for the selected outbound line. Wait for a task to finish and retry. + details: {} "500": $ref: "#/components/responses/ErrorResponse" "503": diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 4190d1e..b256089 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -15,7 +15,7 @@ export interface paths { put?: never; /** * Create Call - * @description Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence. The default outbound line supports one phone number per task. Batch calls require an eligible purchased number selected as the account default outbound number; otherwise creation returns `422 call_not_ready`. Each authenticated user can create up to 20 call plans in any rolling 24-hour period; additional requests return `429 rate_limit_exceeded` before planning begins. + * @description Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence. Shared platform outbound lines support one phone number per task. Batch calls require an eligible purchased number selected as the account default outbound number; otherwise creation returns `422 call_not_ready`. Account concurrency and LLM token usage are controlled by the effective account configuration. Task concurrency defaults to 1 on shared platform lines and 10 on eligible dedicated purchased numbers; selecting a purchased number as the account default does not make it a shared platform line. */ post: operations["createCall"]; delete?: never; @@ -736,7 +736,7 @@ export interface operations { 403: components["responses"]["ErrorResponse"]; 409: components["responses"]["ErrorResponse"]; 422: components["responses"]["ErrorResponse"]; - /** @description A call-plan rate limit, account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. */ + /** @description An account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. */ 429: { headers: { [name: string]: unknown; @@ -745,13 +745,9 @@ export interface operations { /** * @example { * "error": { - * "code": "rate_limit_exceeded", - * "message": "The 24-hour call plan limit has been reached.", - * "details": { - * "limit": 20, - * "window_hours": 24, - * "count": 20 - * } + * "code": "account_concurrency_exceeded", + * "message": "This account has reached its task concurrency limit for the selected outbound line. Wait for a task to finish and retry.", + * "details": {} * } * } */ From 16ce4b9a6392bfc416c3fb154a07b2b9982ddddf Mon Sep 17 00:00:00 2001 From: "lin.si" Date: Tue, 8 Sep 2026 18:03:37 +0800 Subject: [PATCH 3/3] docs: sync concurrency upgrade error details --- openapi/calle.openapi.yaml | 12 +++++++++--- src/generated/schema.ts | 14 +++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/openapi/calle.openapi.yaml b/openapi/calle.openapi.yaml index 89c0220..922cfd7 100644 --- a/openapi/calle.openapi.yaml +++ b/openapi/calle.openapi.yaml @@ -172,7 +172,7 @@ paths: "422": $ref: "#/components/responses/ErrorResponse" "429": - description: An account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. + description: An account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. Concurrency errors identify the effective line type and limit. Shared-line errors include KYC and dedicated-number purchase guidance in the message and `details.upgrade`; purchased-line errors ask the user to wait for capacity without another purchase suggestion. Configured limits override the default 1 / 10 values. content: application/json: schema: @@ -180,8 +180,14 @@ paths: example: error: code: account_concurrency_exceeded - message: This account has reached its task concurrency limit for the selected outbound line. Wait for a task to finish and retry. - details: {} + message: Your default shared line (such as us/all) is at its account concurrency limit of 1. This limit is shared across API, MCP, and Dashboard. Wait for an active task to finish, then retry. For up to 10 concurrent tasks, complete identity verification (KYC), purchase a dedicated phone number, and select the activated number as your default outbound number. [Verify identity and buy a number](https://dashboard.heycall-e.com/account/numbers/buy). + details: + line_type: platform_default + max_active_tasks: 1 + upgrade: + max_active_tasks: 10 + kyc_required: true + purchase_url: https://dashboard.heycall-e.com/account/numbers/buy "500": $ref: "#/components/responses/ErrorResponse" "503": diff --git a/src/generated/schema.ts b/src/generated/schema.ts index b256089..88699aa 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -736,7 +736,7 @@ export interface operations { 403: components["responses"]["ErrorResponse"]; 409: components["responses"]["ErrorResponse"]; 422: components["responses"]["ErrorResponse"]; - /** @description An account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. */ + /** @description An account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. Concurrency errors identify the effective line type and limit. Shared-line errors include KYC and dedicated-number purchase guidance in the message and `details.upgrade`; purchased-line errors ask the user to wait for capacity without another purchase suggestion. Configured limits override the default 1 / 10 values. */ 429: { headers: { [name: string]: unknown; @@ -746,8 +746,16 @@ export interface operations { * @example { * "error": { * "code": "account_concurrency_exceeded", - * "message": "This account has reached its task concurrency limit for the selected outbound line. Wait for a task to finish and retry.", - * "details": {} + * "message": "Your default shared line (such as us/all) is at its account concurrency limit of 1. This limit is shared across API, MCP, and Dashboard. Wait for an active task to finish, then retry. For up to 10 concurrent tasks, complete identity verification (KYC), purchase a dedicated phone number, and select the activated number as your default outbound number. [Verify identity and buy a number](https://dashboard.heycall-e.com/account/numbers/buy).", + * "details": { + * "line_type": "platform_default", + * "max_active_tasks": 1, + * "upgrade": { + * "max_active_tasks": 10, + * "kyc_required": true, + * "purchase_url": "https://dashboard.heycall-e.com/account/numbers/buy" + * } + * } * } * } */