# API Reference

## Health

`GET /health`

Returns configured provider and compliance mode.

## Authentication

Client endpoints accept organization API keys. In local mode, `GATEWAY_API_KEYS=key_1,key_2` can still be used as a static fallback. Provider webhooks and health remain unauthenticated because provider webhook verification belongs to provider adapters.

Accepted client auth headers:

```text
Authorization: Bearer key_1
x-api-key: key_1
```

## Email Signup

`POST /auth/signup`

Request:

```json
{ "email": "dev@example.com" }
```

Creates or reuses a user and default organization, then sends a Resend verification email. In development without `RESEND_API_KEY`, the response includes `verificationUrl`.

## Verify Email

`POST /auth/verify`

Request:

```json
{ "token": "verify_..." }
```

Marks the user email as verified, creates a dashboard session, and sets the `acg_session` HTTP-only cookie.

`GET /auth/verify?token=verify_...`

The email link can also be opened directly. It performs the same activation and session-cookie behavior.

## Current User

`GET /auth/me`

Returns the authenticated user and organization from the session cookie.

## API Keys

`POST /api-keys`

Requires a verified dashboard session. Returns the API key secret once.

```json
{
  "name": "Production key",
  "scopes": ["tasks:write", "tasks:read"]
}
```

`GET /api-keys`

Lists organization keys without secret values.

`POST /api-keys/:id/revoke`

Revokes a key for the current organization.

API keys are hashed at rest and the secret is returned only once. Supported scopes:

- `tasks:write` for `POST /tasks`, low-level call creation, cancellation, and MCP task start.
- `tasks:read` for task/call listing, task fetch, and MCP task fetch.

## Dashboard and Landing Pages

`GET /`

Returns the public developer landing page with product positioning, curl example, pricing teaser, and links to the dashboard/docs.

`GET /dashboard`

Returns the self-serve dashboard for the first-call path: onboarding checklist, pricing, billing state, API key creation, caller ID mode, dedicated-number upsell, first-call starter, and recent calls. Phone numbers, webhooks, and compliance remain available as secondary/advanced sections.

`GET /admin`

Returns the internal backoffice shell. Admin API data routes require `ADMIN_API_KEY` in production.

## Compliance and Spend Controls

Controls enforced before provider dialing:

- `DESTINATION_ALLOWED_COUNTRIES`: allowlist checked against `countryCode` or inferred destination country.
- `BLOCKED_DESTINATION_PATTERNS`: comma-separated regular expressions checked against the destination E.164 number.
- `DEFAULT_DAILY_CALL_LIMIT`: per-organization started-call cap.
- `DEFAULT_DAILY_SPEND_LIMIT_CENTS`: per-organization daily prepaid spend cap.
- `DEFAULT_MAX_COST_PER_CALL_CENTS`: blocks estimated call cost above the configured max.
- `RECORDING_DISCLOSURE_MODE`: `none`, `agent`, or `provider`.
- manual organization disable and compliance flags from backoffice.

Policy blocks return HTTP `402` before a provider call is started.

Launch policy is US-only by default: `DESTINATION_ALLOWED_COUNTRIES=US`, `COUNTRY_CODES=US`, and number inventory/purchase endpoints reject non-US numbers before provider calls.

## Internal Admin

Admin routes accept `Authorization: Bearer <ADMIN_API_KEY>` or `x-admin-key: <ADMIN_API_KEY>`.

`GET /api/admin/overview`

Returns organization and operations totals: orgs, calls, tasks, phone numbers, and webhook deliveries.

`GET /api/admin/organizations`

Lists organizations with subscription status, wallet balance, disabled state, and compliance flags.

`GET /api/admin/organizations/:id`

Returns one organization plus calls, tasks, wallet ledger, phone numbers, and webhook deliveries.

`PATCH /api/admin/organizations/:id`

Updates manual controls.

```json
{
  "disabled": true,
  "disabledReason": "fraud review",
  "complianceFlags": ["blocked"],
  "spendLimitCents": 5000,
  "dailyCallLimit": 25,
  "maxCostPerCallCents": 500,
  "recordingDisclosureMode": "agent"
}
```

## Billing and Wallet

`GET /billing/summary`

Requires a verified dashboard session. Returns organization billing state, subscription status, wallet balance, currency, enforcement flag, minimum required balance, and platform call fee.

`GET /billing/ledger`

Requires a verified dashboard session. Returns immutable prepaid wallet ledger rows newest first.

`GET /pricing`

Returns the customer-facing launch pricing model: base subscription, prepaid minimum, platform per-call fee, shared caller ID fee, dedicated number monthly rental, and the ledger categories used for each charge.

`POST /billing/checkout/subscription`

Requires a verified dashboard session and `STRIPE_PLATFORM_PRICE_ID`. Creates a Stripe subscription Checkout Session.

`POST /billing/checkout/top-up`

Requires a verified dashboard session and `STRIPE_TOPUP_PRICE_ID`. Creates a prepaid wallet top-up Checkout Session.

```json
{ "quantity": 1 }
```

`POST /billing/portal`

Requires a verified dashboard session and an attached Stripe customer id. Creates a Stripe customer portal session.

`POST /webhooks/stripe`

Processes Stripe billing events. Webhook ids are recorded so repeat deliveries do not double-credit prepaid wallet top-ups. `checkout.session.completed` activates subscriptions or credits wallet top-ups; `customer.subscription.deleted` marks the organization canceled.

Signature verification uses the original raw JSON request body. Do not put this route behind middleware that reparses or reformats the payload before Fastify receives it.

When `BILLING_ENFORCED=true`, production calls made with organization API keys require:

- verified account owner email
- active or trialing subscription
- prepaid wallet balance >= `max(WALLET_MIN_BALANCE_CENTS, PLATFORM_CALL_FEE_CENTS)`

After the provider accepts a call, the gateway writes a `platform_call_fee` debit ledger row linked to the task/call. When shared caller ID has a configured per-call fee, it also writes `shared_caller_id_call`. Dedicated number purchases write `number_rental_dedicated`.

## Usage and Call History

`GET /usage/summary`

Requires a verified dashboard session. Returns total customer spend, provider cost, call counts, task counts, and spend grouped by ledger category.

`GET /usage/calls.csv`

Requires a verified dashboard session. Exports organization call history as CSV with status, destination, provider ids, duration, recording URL, failure reason, and creation time.

`GET /session/calls`

Requires a verified dashboard session. Returns recent organization calls for the dashboard without requiring the reveal-once API key secret to be present in local storage.

## Phone Numbers

`GET /onboarding/status`

Requires a verified dashboard session. Returns the simple customer checklist: subscription, wallet, API key, caller ID readiness, first successful call status, launch market, `nextAction`, `blockingReason`, `platformReady`, and `availableActions`.

When the platform shared caller ID is not configured, `blockingReason` is `shared_caller_id_not_configured` and the dashboard should show a support/configuration state instead of letting the user hit a dead test-call path.

`POST /phone-numbers/dedicated`

Requires a verified dashboard session. One-click dedicated US number upsell: searches US inventory when only `areaCode` is supplied, purchases the number, configures it with `TELNYX_CONNECTION_ID` / `TELNYX_MESSAGING_PROFILE_ID`, bills `number_rental_dedicated`, and makes it the organization's default caller ID.

Provisioning states:

- `purchasing`: provider order is not ready yet.
- `configuring`: the number is being attached to the platform voice config.
- `ready`: calls can use it as the organization's caller ID.
- `failed`: provider purchase or configuration failed.

```json
{ "areaCode": "415" }
```

`GET /phone-numbers/search?countryCode=US&areaCode=415&features=voice&limit=10`

Requires a verified dashboard session. Searches Telnyx available-number inventory by country, region, locality, area code, and requested features. Launch search is restricted to `countryCode=US`. When `TELNYX_API_KEY` is unset, the API returns deterministic sandbox numbers for local development.

`POST /phone-numbers/purchase`

Requires a verified dashboard session. Advanced/backoffice path that creates a Telnyx number order, auto-configures completed US purchases with the platform Telnyx voice settings, stores the purchased number, creates a number order row, and charges the prepaid wallet for the quoted monthly rental when `monthlyCostCents` is provided. Public onboarding should use `/phone-numbers/dedicated`.

```json
{
  "phoneNumber": "+14155551234",
  "countryCode": "US",
  "capabilities": ["voice"],
  "monthlyCostCents": 250
}
```

`GET /phone-numbers`

Lists organization numbers including provider status, default outbound flag, monthly cost, capabilities, Telnyx connection id, and release state.

`GET /phone-number-orders`

Lists number orders and provider status, including regulatory `requires_action` and failed-order details.

`POST /phone-numbers/:id/configure`

Advanced/backoffice endpoint. Assigns the number to Telnyx voice configuration. Uses explicit `connectionId` / `messagingProfileId` from the request or `TELNYX_CONNECTION_ID` / `TELNYX_MESSAGING_PROFILE_ID` from env.

```json
{ "connectionId": "conn_123" }
```

`POST /phone-numbers/:id/default`

Sets the organization default outbound number.

`POST /phone-numbers/:id/release`

Releases/cancels the number at Telnyx and marks it as released locally.

Telnyx is used here as the number and telephony provisioning layer. The real-time AI voice runtime is still selected by `CALL_PROVIDER` (`mock`, `twilio`, or `elevenlabs`).

## Webhook Endpoints and Delivery Logs

`POST /webhook-endpoints`

Requires a verified dashboard session. Creates a per-organization webhook endpoint and returns the `whsec_...` signing secret once.

```json
{
  "url": "https://app.example.com/webhooks/calls",
  "description": "Production events"
}
```

`GET /webhook-endpoints`

Lists endpoints without secret values.

`POST /webhook-endpoints/:id/disable`

Disables an endpoint without deleting delivery history.

`GET /webhook-deliveries`

Lists delivery attempts, status, attempts count, last HTTP status/error, next retry time, and replay lineage.

`POST /webhook-deliveries/:id/replay`

Creates and immediately attempts a replay delivery from the original payload.

## Create Task

`POST /tasks`

This is the primary product API. It accepts the developer's task JSON, renders voice-provider instructions, runs compliance, starts the call, and returns a tracked task.

Send `Idempotency-Key: <stable-key>` on client retries. If a task already exists for the same organization/key, the API returns that task with `idempotent: true` and does not create a second paid call.

Request:

```json
{
  "externalId": "agent_run_123",
  "to": "+15551234567",
  "from": "+15557654321",
  "objective": "Call the business and confirm whether they can accept a booking tomorrow at 10:00.",
  "instructions": "If 10:00 is not available, ask for the closest available time before noon.",
  "locale": "en-US",
  "timezone": "America/New_York",
  "countryCode": "US",
  "voice": {
    "openingLine": "Hi, I am calling on behalf of Mike.",
    "disclosure": "This is an automated assistant.",
    "tone": "concise and polite"
  },
  "requiredOutput": [
    {
      "key": "available",
      "type": "boolean",
      "description": "Whether the requested slot is available."
    }
  ],
  "metadata": {
    "tenantId": "tenant_abc"
  },
  "callbackUrl": "https://your-app.example.com/webhooks/calls"
}
```

Response:

```json
{
  "status": "ok",
  "data": {
    "id": "task_...",
    "callId": "call_...",
    "providerCallId": "mock_call_...",
    "status": "dialing"
  }
}
```

## Get Task

`GET /tasks/:id`

Returns the task, including `result` after provider completion.

## List Tasks

`GET /tasks`

Returns organization tasks sorted newest first when called with a DB-backed API key.

## Cancel Task

`POST /tasks/:id/cancel`

Cancels the linked provider call if possible and marks the task as `canceled`.

## MCP-Friendly Tools

`GET /mcp/tools`

Returns tool descriptors for:

- `start_call_task`
- `get_call_task`

`POST /mcp/tools/start_call_task`

Accepts the same payload as `POST /tasks`.

`POST /mcp/tools/get_call_task`

Request:

```json
{ "id": "task_..." }
```

## Create Call

`POST /calls`

Low-level API for already-rendered provider instructions.

Request:

```json
{
  "to": "+15551234567",
  "from": "+15557654321",
  "instructions": "Provider-specific instructions or opening text.",
  "countryCode": "US",
  "metadata": {
    "agentRunId": "run_123",
    "tenantId": "tenant_abc"
  }
}
```

## Provider Webhook

`POST /webhooks/:provider`

Provider-specific payloads are normalized into:

```json
{
  "provider": "mock",
  "providerCallId": "mock_call_123",
  "status": "completed",
  "transcript": "Optional transcript",
  "recordingUrl": "https://recordings.example/call.mp3",
  "structuredResult": {
    "available": true
  },
  "durationSec": 42,
  "failureReason": "Optional reason"
}
```

## Agent Webhooks

Call updates are forwarded to `AGENT_WEBHOOK_URL` when configured:

```json
{
  "type": "call.updated",
  "call": {
    "id": "call_123",
    "status": "completed"
  }
}
```

Task updates use `task.callbackUrl` first, then fall back to `AGENT_WEBHOOK_URL`:

```json
{
  "type": "task.updated",
  "task": {
    "id": "task_123",
    "status": "completed",
    "result": {
      "structured": {
        "available": true
      },
      "transcript": "agent: ...",
      "recordingUrl": "https://..."
    }
  }
}
```

If `AGENT_WEBHOOK_SECRET` is set, forwarded requests include:

```text
x-agent-call-gateway-signature: sha256=<hmac>
```
