# API reference

Base URL: `https://api.patchamd.com`. Authenticate with `Authorization: Bearer pamd_live_…` (or `X-API-Key`). Machine-readable specs: [OpenAPI 3.1](/openapi.json) for REST, [AsyncAPI 3.0](/asyncapi.json) for the WebSocket.

## POST /v1/streams

Mint a single-use signed stream URL (valid 10 minutes) for dialers that can't send headers.

| Field | Type | |
|---|---|---|
| `webhook_url` | string | HTTPS URL for `amd.decided` |
| `metadata` | object | Up to 10 string values (≤256 chars), echoed in the webhook. Identifiers only, not call content. |
| `encoding` | `mulaw` \| `alaw` \| `linear16` | Raw streams only |
| `sample_rate` | integer | Raw streams only; default 8000 |
| `events` | boolean | Also send events on a Telnyx/Twilio socket |

Returns `201` with `id`, `url` (with token), `base_url` (without query, for Twilio), `token`, `expires_at`.

## GET /v1/detections/{id}

```json
{ "id": "det_…", "status": "decided", "carrier": "twilio", "result": "human", "reason": "person_waiting",
  "at_ms": 1620, "billable": true, "metadata": {}, "webhook_status": "delivered",
  "created_at": "…", "decided_at": "…" }
```

`status`: `pending` (minted), `streaming`, `decided`, or `ended` (closed before a decision — not billed). The words heard are not stored, so `heard` is not here.

## GET /v1/usage

Billable detections per UTC day and key. Query: `from` (inclusive, default first of the month), `to` (exclusive, default tomorrow), `key=this` to limit to the calling key.

```json
{ "from": "2026-11-01", "to": "2026-11-11", "total": 2,
  "days": [{ "date": "2026-11-10", "key_id": "key_…", "detections": 2 }] }
```

## WebSocket /v1/stream

`wss://api.patchamd.com/v1/stream`. Accepts Telnyx media streaming, Twilio Media Streams (told apart by the `start` message), or raw binary audio when `encoding` is in the query. See the [raw guide](/docs/raw-websocket) for events and close codes.

Authentication, in order: `?token=` (signed), `Authorization` / `X-API-Key` header, `?api_key=` (discouraged), or for Twilio a `token` / `api_key` `<Parameter>`.

## Results

| result | Meaning |
|---|---|
| `human` | A live person who has stopped talking and is waiting |
| `voicemail` | A voicemail or answering-machine greeting |
| `ivr` | Phone menu, auto-attendant, hold queue, recording notice, call screening |
| `unavailable` | Not in service, cannot be completed, mailbox full or not set up |
| `silence` | Nobody spoke after the answer |
| `not_sure` | Could not tell, or the detector failed — treat as a person |

## Reasons

| reason | Meaning |
|---|---|
| `person_waiting` | A person spoke and is waiting for a reply ("Hello?") |
| `recording_detected` | What was said could only come from a recording or automated system |
| `inconclusive` | Speech that could not be placed either way (`not_sure`) |
| `no_speech` | Nobody spoke |
| `time_limit` | The listening window ended while the speaker was still talking (→ `voicemail`) |
| `detector_failed` | Something failed on our side. Not billed. |

## Limits

Per key: 600 requests and stream connects per minute, 100 concurrent streams (ask us to raise them). Over a limit: HTTP 429, or close code 4429 for a Twilio start. Revoked keys stop working within 30 seconds.

## Errors

REST errors are JSON: `{"error": "invalid_request", "message": "…"}` with status 400, 401, 403, 404 or 429.
