Skip to content

Quickstart

By the end of this page you will have placed a simulated call to a magic test number and received a call_order.completed result. No real telephone rings at any point: the sandbox host runs simulated telephony.

Tokens come from the authorisation server, not from the API host. They are valid for 30 minutes and there are no refresh tokens: when a token expires you request another with the same credentials. Ask for a token slightly before you need one rather than retrying on the first 401.

Terminal window
curl -sS -X POST https://auth.pacepayments.ai/oauth2/token \
-d grant_type=client_credentials \
-d client_id="$PACE_CLIENT_ID" \
-d client_secret="$PACE_CLIENT_SECRET"

The response carries the scopes actually granted to your registration. Check them once during setup - a missing scope shows up later as a 403 insufficient_scope on one endpoint while everything else works.

An order type is the purpose of the call. It binds the conversation to agent behaviour vetted by Pace, which is why you reference one instead of describing what the agent should say.

Terminal window
curl -sS https://sandbox.api.pacepayments.ai/v1/order-types \
-H "Authorization: Bearer $ACCESS_TOKEN"

Keys are German - zahlungserinnerung, mahnung, forderungsuebernahme and so on. They are identifiers, not display text; read title for something to show a user.

Use the magic number +4989000000001. In the sandbox it produces a conversation that ends in a payment promise, which gives you a fully populated result to write your handler against.

Terminal window
curl -sS -X POST https://sandbox.api.pacepayments.ai/v1/call-orders \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"order_type": "zahlungserinnerung",
"client_reference": "INV-2026-118442",
"subject": {
"reference": "CUST-88213",
"name": { "given_name": "Erika", "family_name": "Mustermann" },
"date_of_birth": "1989-03-28",
"address": {
"street": "Seminarstr.", "house_number": "2b",
"postal_code": "01067", "city": "Dresden", "country": "DE"
},
"timezone": "Europe/Berlin"
},
"contacts": [
{ "channel": "phone", "value": "+4989000000001", "type": "mobile", "priority": 1 }
],
"context": {
"claim": {
"outstanding": { "value": "129.90", "currency": "EUR" },
"creditor_name": "Truman Textilien GmbH",
"basis": { "kind": "subscription", "description": "Annual textile care plan" }
}
}
}'

You get 202 Accepted and an order in status accepted. Acceptance means the order passed validation, the order type exists, the subject is not on the suppression list, and a permissible dial instant exists inside your window. It does not mean anyone has been called yet.

Two routes, and they carry the same events. Start with the feed - it needs no public endpoint, so you can finish this quickstart from a laptop.

The feed retains 30 days. after accepts either a cursor from a previous page or a bare event ID, so you can resume from the last event you processed.

curl
curl -sS "https://sandbox.api.pacepayments.ai/v1/events?event_types=call_order.*" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Python
events = requests.get(
"https://sandbox.api.pacepayments.ai/v1/events",
headers={"Authorization": f"Bearer {access_token}"},
params={"call_order_id": order_id},
timeout=15,
).json()["data"]
done = next(
(e for e in events if e["type"] == "call_order.completed"), None
)

Fetch the order once it reaches a terminal status. result is present on every terminal order, including ones where nobody answered.

curl
curl -sS "https://sandbox.api.pacepayments.ai/v1/call-orders/$ORDER_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Response (abridged)
{
"id": "ord_01J32K7M2QW9RT",
"status": "completed",
"client_reference": "INV-2026-118442",
"order_type_version": 1,
"decisive_call_id": "call_01J32K8P4R…",
"result": {
"reachability": "reached",
"engagement": "conversation",
"identity": { "verified": true, "method": "address_birthday" },
"termination": { "actor": "subject", "reason": "completed" },
"summary_code": "payment_promised",
"outcomes": [
{ "type": "payment_promise",
"data": { "amount": { "value": "129.90", "currency": "EUR" },
"due_date": "2026-09-15" } }
]
},
"compliance": {
"ai_disclosure": { "disclosed": true, "at": "2026-08-30T09:14:22+02:00" },
"contact_window_policy": "de_uwg7_default"
}
}

Branch on summary_code. It is one value, deliberately, so that a client has one thing to switch on - the four dimensions above it explain why the call came out that way, and outcomes[] carries the structured detail.