Goal Runs
Run a published Goal when your application repeats the same validated phone workflow for different targets.
API 0.6 · Server-side · Asynchronous
A Goal owns its call behavior, voice region and locale, input schema, result schema, and provider contract. A Goal Run supplies only one E.164 phone number, per-Run variables, and a business-stable idempotency key.
Use the one-shot Calls API when each request needs new task text or a request-scoped result schema. Goal Runs are a separate contract and do not replace or reinterpret /v1/calls.
For endpoint schemas and response models, browse the read-only Goals API Reference and Goal Runs API Reference.
Example: confirm a delivery window
Imagine a fulfillment system that needs to call customers before a scheduled delivery. An operations user authors and publishes one reusable Goal in CALL-E Chat: explain the order reference and proposed delivery window, ask the customer to confirm or request another time, and return a structured outcome.
Your application then performs the following steps for every order:
- Store the published
goal_idin server-side configuration. - Read the Goal interface and validate which
variablesthe current published version expects. - Create one Goal Run using the customer's E.164 phone number, order-specific variables, and the order event's stable idempotency key.
- Poll until the Goal Run returns either
resultorerror. - Update the fulfillment system directly from
result, for example by confirming the delivery or opening a rescheduling task.
This guide uses that delivery-confirmation workflow throughout. Phone numbers remain placeholders so examples cannot accidentally call a real recipient.
List published Goals
Use GET /v1/goals to discover the authenticated owner's active, listed Goals that have a published RunSpec:
Code
| Parameter | Location | Required | Description |
|---|---|---|---|
Authorization | Header | Yes | Bearer $CALLE_API_KEY. The API key determines the Goal owner scope; an owner id is never accepted from the request. |
limit | Query | No | Number of Goal interfaces to return. Defaults to 20; minimum 1, maximum 100. |
after | Query | No | Opaque value from the previous response's next_cursor. Pass it unchanged and never build it from a Goal id. |
Code
When next_cursor is non-null, pass it unchanged as after on the next request. Cursors are opaque and results use stable Goal identity order. Hidden, draft, paused, retired, unpublished, and other owners' Goals are not returned.
The list is a discovery and recovery tool, not title search. title and description help a developer recognize each published workflow, but they are not stable identity. Do not execute data[0] blindly: persist the goal_id received when the intended Goal is published.
Read one published interface
You can also store the goal_id returned by a successful publish in CALL-E Chat and read it directly. Goal authoring and publication are not Developer API operations.
| Parameter | Location | Required | Description |
|---|---|---|---|
goal_id | Path | Yes | Opaque Goal identity returned by publish success or Goal listing. It is not the nested RunSpec id. |
Authorization | Header | Yes | Developer API key for the same owner that published the Goal. Cross-owner and hidden Goals return 404. |
Code
GET /v1/goals/{goal_id} returns the current normalized input and result schemas:
Code
The response is an interface, not a version selector. title and description explain the current published workflow without exposing its prompt. The response never exposes provider configuration, materialization guidance, or authoring history.
Create a Goal Run
Send one top-level phone to POST /v1/goals/{goal_id}/runs. variables defaults to {} and is validated against the RunSpec pinned during acceptance.
Request headers:
| Parameter | Required | Description |
|---|---|---|
Authorization | Yes | Bearer $CALLE_API_KEY. Keep API keys in backend secrets; never send them from browser code. |
Idempotency-Key | Yes | Stable identity for this logical business action, 1–255 characters. Derive it from a durable event such as delivery:ORD-8472:confirm-window:v1, store it with the order, and reuse it after timeouts. Do not generate a new UUID for every retry. |
Content-Type | Yes | Use application/json. The request is a closed object; unknown fields and contract overrides are rejected. |
Request body fields:
| Field | Required | Description |
|---|---|---|
phone | Yes | Recipient phone in canonical E.164 form: +, country code, and subscriber number. Do not include spaces, parentheses, or extensions. Your application must be authorized to call it. |
variables | No | Per-Run scalar business values defined by the published input_schema. Defaults to {}. |
Do not send target, region, locale, or display_name. Voice region and callee locale belong to the published Goal and cannot be changed per Run. If the workflow needs a customer's name, define a field such as customer_name in the Goal's input_schema and send it through variables.
variables is an object whose allowed keys, required keys, types, defaults, and enum values come from published_run_spec.input_schema. Values are limited to finite JSON scalars: strings, numbers, and booleans. Do not send nested objects, arrays, or null. In this example, all three delivery variables are required by version 4 of the published interface.
Code
The first accepted request and an exact idempotent replay both return 201 Created. The response contains the immutable RunSpec snapshot and never echoes the phone or variables:
Code
201 means CALL-E durably accepted responsibility for the Run. It does not mean the provider accepted the call, the recipient answered, or the structured result is ready.
Save GoalRun.id from the response as GOAL_RUN_ID. The Location response header contains the same resource path, and Retry-After suggests when to begin polling. The response deliberately omits phone and variables; keep your own correlation between the idempotency key and order record.
Poll for results
Poll the read-only Goal Run resource:
Code
| Parameter | Location | Required | Description |
|---|---|---|---|
goal_id | Path | Yes | The Goal identity used when creating the Run. This prevents a Run id from being read through the wrong Goal. |
goal_run_id | Path | Yes | GoalRun.id returned by POST, not the nested telephone run_id. |
Authorization | Header | Yes | API key for the owning account. A missing or invalid key returns 401; a missing, cross-owner, or mismatched resource returns 404. |
GET /v1/goals/{goal_id}/runs/{goal_run_id} only reads persisted facts. It does not dispatch work, move the Goal's published pointer, or start result materialization.
The response deliberately has one completion rule: stop polling when either result or error is non-null. status describes the telephone execution, so a completed call can briefly return result: null, error: null while CALL-E parses and saves the structured result.
Code
When parsing succeeds, result is the exact dynamic object described by the Goal's result_schema. When anything prevents a usable result, CALL-E returns one error object instead:
Code
Use error.code for application logic and error.message for logs or operators. The possible Goal Run error codes are call_failed, no_answer, declined, timed_out, canceled, result_invalid, result_unavailable, and result_failed.
Schemas belong to the Goal
A Run request cannot provide task text, prompts, instructions, schemas, RunSpec identity, provider settings, or materialization settings. To change these fields, update the Goal in Chat and publish a new RunSpec version. Existing Runs keep their pinned version.
Before deploying a variable change, compare the published version, input_schema, and result_schema with the contract expected by your integration. A newly published version affects new idempotency keys only; an exact replay of an existing business command keeps the original pinned RunSpec.
Idempotency
Idempotency-Key is required, 1–255 characters after trimming, and scoped to the authenticated owner plus goal_id.
- Retry a lost response with the same key, phone, and variables.
- Reusing a key with an equivalent request returns the same
GoalRun.idandrun_idwith HTTP201. - Reusing a key with changed input returns
409 idempotency_conflict. - Use a new stable business key for each new logical Run.
The SDK does not generate the key because it must survive network ambiguity and process restarts.
For example, if the first POST for order ORD-8472 times out after CALL-E accepts it, retry with the same key and identical JSON. If the delivery window changes, create a new logical command and key such as delivery:ORD-8472:confirm-window:v2; changing the body while reusing the v1 key returns 409 idempotency_conflict.
Common integration errors
CALL-E returns a stable JSON error envelope. Use error.code for program logic and keep error.message for logs or operator diagnostics.
| HTTP / code | What it usually means | Recommended action |
|---|---|---|
400 invalid_request | The JSON shape, query parameter, cursor, or idempotency header is malformed. | Fix the request. Retrying the same invalid payload will not help. |
401 unauthorized / 403 forbidden | The API key is missing, invalid, or cannot use the requested capability. | Verify the Bearer header, environment, and project permissions. Never fall back to a key from another tenant. |
404 not_found | The Goal or Goal Run is missing, hidden, owned by another account, or paired with the wrong goal_id. | Check the saved goal_id and GoalRun.id. Do not probe other owner ids or substitute the nested run_id. |
409 idempotency_conflict | The key already identifies a Run, but the phone or variables differ. | Retrieve the original workflow record. Use the existing key only for an exact replay; create a new business-version key for an intentionally new call. |
409 goal_not_published / goal_not_executable / goal_not_ready | The Goal has no published interface, is not active, or its exact published contract cannot currently execute. | Stop automatic retries and ask the Goal owner to publish, activate, or repair the Goal. |
422 variables_invalid | Required variables are missing, an extra key was supplied, or a value violates the pinned input schema. | Refresh GET /v1/goals/{goal_id}, compare its version and input schema, then correct the integration payload. |
422 invalid_phone | phone is not a valid supported E.164 number. | Normalize and validate the number before calling. Do not send the documentation placeholder. |
422 schema_override_not_allowed | The request tried to supply task text, schemas, RunSpec selectors, or provider/materialization settings owned by the Goal. | Remove those fields. Change the Goal in Chat and publish a new version instead. |
429 rate_limit_exceeded | The caller has reached an API limit. | Honor Retry-After when present and retry with backoff while preserving the same idempotency key and request. |
502/503 provider_unavailable | CALL-E could not durably accept the Run because a required upstream dependency was unavailable. | Retry with the same idempotency key and identical body. Once a 201 has been returned, later execution failures appear on the Goal Run resource instead of as POST errors. |
See Errors for the HTTP error catalog. HTTP errors mean the API request itself was rejected. After a 201, execution or result-processing problems are returned in the Goal Run's unified error field.
SDK examples
The following methods define the API 0.6 SDK surface. The currently stable 0.2 packages remain Calls-only until matching TypeScript and Python minors are released.
TypeScript uses camelCase on the existing client.goals resource:
Code
Python keeps the same resource with snake_case methods:
Code
waitForResult and wait_for_result return when either result or error becomes non-null. A terminal failure still returns the complete Goal Run object; polling timeout continues to use each SDK's existing timeout exception.