API reference

SimpleVox REST API

Loading interactive API reference…

Browse the static API reference

Base URL: https://api.simplevox.app/v1. Download OpenAPI JSON.

Search contacts

GET /contacts

Finds contacts in the workspace's shared address book by name, company, email address or phone number, sorted by name. Searching never uses call or text content. Check `meta.resolution`: `resolved` means one contact matched, `disambiguation_required` means several did and `not_found` means none did. When several match, let the person choose rather than picking one.

Parameters

NameLocationRequiredSchema and description
qqueryYesText to look for in contacts' names, companies, email addresses and phone numbers, 1–100 characters. Case doesn't matter.
{
  "type": "string"
}
limitqueryNoHow many contacts to return, from 1 to 100.
{
  "type": "integer",
  "default": 50,
  "minimum": 1,
  "maximum": 100
}
cursorqueryNoThe `next_cursor` from the previous page. Send the same `q` and `limit` with it.
{
  "type": "string"
}

Responses

200 The contacts that match, sorted by name.
{
  "description": "The contacts that match, sorted by name.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": [
              {
                "id": "77777777-7777-4777-8777-777777777777",
                "name": "Jordan Example",
                "portrait_url": null,
                "company": "Example Garden Services",
                "phones": [
                  {
                    "label": "Mobile",
                    "value": "+12025550177"
                  }
                ],
                "emails": [
                  {
                    "label": "Work",
                    "value": "jordan@example.com"
                  }
                ],
                "addresses": [
                  {
                    "label": "Office",
                    "street": "100 Main Street",
                    "locality": "Austin",
                    "region": "TX",
                    "postal_code": "78701",
                    "country": "US"
                  }
                ],
                "notes": "Prefers texts in the morning.",
                "revision": 3
              }
            ],
            "next_cursor": null,
            "meta": {
              "resolution": "resolved"
            }
          }
        ],
        "properties": {
          "data": {
            "type": "array",
            "description": "Up to `limit` contacts that match, sorted by name.",
            "items": {
              "$ref": "#/components/schemas/Contact"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass it as `cursor` to get the next page, or `null` on the last page."
          },
          "meta": {
            "type": "object",
            "description": "Whether the search found one contact, several or none.",
            "properties": {
              "resolution": {
                "type": "string",
                "description": "`resolved` when exactly one contact matched, `disambiguation_required` when several did and `not_found` when none did.",
                "enum": [
                  "disambiguation_required",
                  "not_found",
                  "resolved"
                ]
              }
            },
            "required": [
              "resolution"
            ]
          }
        },
        "required": [
          "data",
          "next_cursor",
          "meta"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Create a contact

POST /contacts

Saves a new shared contact and returns it with its `revision` and an `ETag` header. Requires the **Edit shared contacts** write permission (`contacts.manage`) on the Boost plan; only admins can create tokens with it. Send a new `Idempotency-Key` for each contact. If the response is lost, retry with the same key and body to get the same contact back.

Parameters

NameLocationRequiredSchema and description
Idempotency-KeyheaderYesA new UUID for each contact you create. If the response is lost, retry with the same key and body to get the same contact back.
{
  "type": "string",
  "format": "uuid"
}

Request body

{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/CreateContactRequest"
      }
    }
  }
}

Responses

201 The new contact.
{
  "description": "The new contact.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "id": "77777777-7777-4777-8777-777777777777",
              "name": "Jordan Example",
              "portrait_url": null,
              "company": "Example Garden Services",
              "phones": [
                {
                  "label": "Mobile",
                  "value": "+12025550177"
                }
              ],
              "emails": [
                {
                  "label": "Work",
                  "value": "jordan@example.com"
                }
              ],
              "addresses": [
                {
                  "label": "Office",
                  "street": "100 Main Street",
                  "locality": "Austin",
                  "region": "TX",
                  "postal_code": "78701",
                  "country": "US"
                }
              ],
              "notes": "Prefers texts in the morning.",
              "revision": 3
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The new contact.",
            "$ref": "#/components/schemas/Contact"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    },
    "ETag": {
      "description": "The contact's `revision`, quoted. Send it as `If-Match` when you update the contact.",
      "schema": {
        "type": "string"
      },
      "example": "\"3\""
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
403 `api_writes_unavailable`: the workspace isn't on Boost. `api_write_permission_required` or `not_authorized`: the token doesn't have `contacts.manage`.
{
  "description": "`api_writes_unavailable`: the workspace isn't on Boost. `api_write_permission_required` or `not_authorized`: the token doesn't have `contacts.manage`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `idempotency_conflict`: the key was already used with a different body. `contact_operation_conflict`: another change is in progress; retry shortly.
{
  "description": "`idempotency_conflict`: the key was already used with a different body. `contact_operation_conflict`: another change is in progress; retry shortly.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Retrieve a contact

GET /contacts/{contact}

Returns one contact and its current `revision`. The `ETag` header carries the same revision; send it back as `If-Match` when you update the contact.

Parameters

NameLocationRequiredSchema and description
contactpathYesThe contact's ID.
{
  "type": "string",
  "format": "uuid"
}

Responses

200 The contact.
{
  "description": "The contact.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "id": "77777777-7777-4777-8777-777777777777",
              "name": "Jordan Example",
              "portrait_url": null,
              "company": "Example Garden Services",
              "phones": [
                {
                  "label": "Mobile",
                  "value": "+12025550177"
                }
              ],
              "emails": [
                {
                  "label": "Work",
                  "value": "jordan@example.com"
                }
              ],
              "addresses": [
                {
                  "label": "Office",
                  "street": "100 Main Street",
                  "locality": "Austin",
                  "region": "TX",
                  "postal_code": "78701",
                  "country": "US"
                }
              ],
              "notes": "Prefers texts in the morning.",
              "revision": 3
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The contact.",
            "$ref": "#/components/schemas/Contact"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    },
    "ETag": {
      "description": "The contact's `revision`, quoted. Send it as `If-Match` when you update the contact.",
      "schema": {
        "type": "string"
      },
      "example": "\"3\""
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Update a contact

PATCH /contacts/{contact}

Changes only the fields you send. A list you send, such as `phones`, replaces the whole list; send `[]` to clear it. Send the revision from the contact's `ETag` in `If-Match`, quoted, like `"3"`. If the contact changed since you read it, you get `412` with `contact_revision_conflict`: read it again and retry with the new `ETag`. Requires the **Edit shared contacts** write permission (`contacts.manage`) on the Boost plan. If the response is lost, retry with the same `Idempotency-Key`, body and `If-Match`.

Parameters

NameLocationRequiredSchema and description
Idempotency-KeyheaderYesA new UUID for each change. If the response is lost, retry with the same key, body and `If-Match`.
{
  "type": "string",
  "format": "uuid"
}
If-MatchheaderYesThe contact's `ETag`: its `revision`, quoted.
{
  "type": "string"
}
contactpathYesThe contact's ID.
{
  "type": "string",
  "format": "uuid"
}

Request body

{
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ContactWriteRequest"
      }
    }
  }
}

Responses

200 The updated contact.
{
  "description": "The updated contact.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "id": "77777777-7777-4777-8777-777777777777",
              "name": "Jordan Example",
              "portrait_url": null,
              "company": "Example Garden Services",
              "phones": [
                {
                  "label": "Mobile",
                  "value": "+12025550177"
                }
              ],
              "emails": [
                {
                  "label": "Work",
                  "value": "jordan@example.com"
                }
              ],
              "addresses": [
                {
                  "label": "Office",
                  "street": "100 Main Street",
                  "locality": "Austin",
                  "region": "TX",
                  "postal_code": "78701",
                  "country": "US"
                }
              ],
              "notes": "Prefers texts in the morning.",
              "revision": 3
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The updated contact.",
            "$ref": "#/components/schemas/Contact"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    },
    "ETag": {
      "description": "The contact's `revision`, quoted. Send it as `If-Match` when you update the contact.",
      "schema": {
        "type": "string"
      },
      "example": "\"3\""
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
403 `api_writes_unavailable`: the workspace isn't on Boost. `api_write_permission_required` or `not_authorized`: the token doesn't have `contacts.manage`.
{
  "description": "`api_writes_unavailable`: the workspace isn't on Boost. `api_write_permission_required` or `not_authorized`: the token doesn't have `contacts.manage`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `idempotency_conflict`: the key was already used with a different body. `contact_operation_conflict` or `contact_destination_changed`: retry after reading the contact again.
{
  "description": "`idempotency_conflict`: the key was already used with a different body. `contact_operation_conflict` or `contact_destination_changed`: retry after reading the contact again.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
412 `contact_revision_conflict`: the contact changed since that revision. `contact_precondition_invalid`: `If-Match` isn't a quoted revision number.
{
  "description": "`contact_revision_conflict`: the contact changed since that revision. `contact_precondition_invalid`: `If-Match` isn't a quoted revision number.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
428 `contact_precondition_required`: send `If-Match` with the contact's current revision.
{
  "description": "`contact_precondition_required`: send `If-Match` with the contact's current revision.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

List recent activity

GET /activity

Returns calls and texts on the token's phone numbers within a window of up to 31 days, newest first, including callers who aren't saved as contacts. Each entry includes its conversation's current status; reading never changes it. Send `occurred_from` and `occurred_until` as ISO 8601 timestamps with seconds and a time zone offset. `meta.coverage.has_more` tells you whether to fetch the next page with `cursor`.

Parameters

NameLocationRequiredSchema and description
occurred_fromqueryYesThe start of the window: calls and texts at or after this time, in ISO 8601 with seconds and a time zone offset. The window can be up to 31 days long.
{
  "type": "string",
  "format": "date-time"
}
occurred_untilqueryYesThe end of the window: calls and texts before this time, in ISO 8601 with seconds and a time zone offset. It must be after `occurred_from`.
{
  "type": "string",
  "format": "date-time"
}
phone_number_idqueryNoOnly calls and texts on this workspace phone number. It must be one of the token's phone numbers.
{
  "type": "string",
  "format": "uuid"
}
channelqueryNo`call` for calls only, `message` for texts only. Leave it out for both.
{
  "type": "string",
  "enum": [
    "call",
    "message"
  ]
}
call_filterqueryNoOnly calls that match an inbox filter: `missed`, `voicemail`, `incoming`, `outgoing`, `any`, or `unresponded` for missed calls and voicemails nobody has returned yet with a connected call or a text the carrier accepted in the same conversation, even after `occurred_until`. Can't be combined with `channel=message`.
{
  "type": "string",
  "enum": [
    "any",
    "missed",
    "voicemail",
    "unresponded",
    "incoming",
    "outgoing"
  ]
}
limitqueryNoHow many calls and texts to return, from 1 to 100.
{
  "type": "integer",
  "default": 50,
  "minimum": 1,
  "maximum": 100
}
cursorqueryNoThe `next_cursor` from the previous page. Send the same filters and `limit` with it.
{
  "type": "string"
}

Responses

200 A page of calls and texts in the window, newest first.
{
  "description": "A page of calls and texts in the window, newest first.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "contact": null,
              "entries": [
                {
                  "synthetic": false,
                  "id": "88888888-8888-4888-8888-888888888888",
                  "kind": "call",
                  "occurred_at": "2026-10-01T15:04:05.000000Z",
                  "phone_number": {
                    "id": "11111111-1111-4111-8111-111111111111",
                    "label": "Office"
                  },
                  "conversation_id": "55555555-5555-4555-8555-555555555555",
                  "contact_id": "77777777-7777-4777-8777-777777777777",
                  "contact": {
                    "id": "77777777-7777-4777-8777-777777777777",
                    "name": "Jordan Example",
                    "portrait_url": null
                  },
                  "other_party_number": "+12025550123",
                  "from": "+12025550123",
                  "to": "+12025550100",
                  "direction": "inbound",
                  "outcome": "answered",
                  "original_content": null,
                  "state": "completed",
                  "content_state": "available",
                  "source_version": 7,
                  "call_content": {
                    "state": "available",
                    "generation": 1,
                    "cleanup_state": "not_requested",
                    "recordings": [
                      {
                        "id": "22222222-2222-4222-8222-222222222222",
                        "kind": "live",
                        "state": "available",
                        "version": 1,
                        "duration_seconds": 94,
                        "channels": 2,
                        "playback_url_endpoint": "https://api.simplevox.app/v1/recordings/22222222-2222-4222-8222-222222222222/playback",
                        "transcript_id": "33333333-3333-4333-8333-333333333333",
                        "transcript_version": 1,
                        "transcript_state": "available",
                        "analysis_id": "99999999-9999-4999-8999-999999999999",
                        "analysis_state": "available",
                        "summary": "The customer asked to move Thursday's appointment to 10 AM.",
                        "summary_segments": [
                          0
                        ],
                        "facts": [
                          {
                            "text": "The appointment moves to Thursday at 10 AM.",
                            "segments": [
                              0
                            ]
                          }
                        ],
                        "uncertainties": [],
                        "freshness": "current",
                        "source_url": "https://my.simplevox.app/sources/call/88888888-8888-4888-8888-888888888888?version=7"
                      }
                    ],
                    "recording_count": 1,
                    "has_more_recordings": false
                  },
                  "source": {
                    "kind": "call",
                    "id": "88888888-8888-4888-8888-888888888888",
                    "source_version": 7,
                    "url": "https://my.simplevox.app/sources/call/88888888-8888-4888-8888-888888888888?version=7"
                  },
                  "conversation_status": {
                    "status": "unread",
                    "unread_count": 1,
                    "revision": 0,
                    "activity_through": "2026-10-01T15:04:05.000000+00:00",
                    "status_change": null
                  }
                }
              ]
            },
            "next_cursor": null,
            "meta": {
              "coverage": {
                "scope": "authorized_lines_only",
                "filters": {
                  "contact_id": null,
                  "phone_number_id": null,
                  "channel": null,
                  "occurred_from": "2026-10-01T00:00:00.000000+00:00",
                  "occurred_until": "2026-10-02T00:00:00.000000+00:00",
                  "limit": 50,
                  "call_filter": null
                },
                "snapshot_at": "2026-10-02T08:00:00.000000Z",
                "authorized_total": 1,
                "returned": 1,
                "has_more": false,
                "fresh_query_required_for_new_records": true,
                "content": "original_sources_and_current_source_linked_analysis"
              }
            }
          }
        ],
        "properties": {
          "data": {
            "type": "object",
            "description": "Calls and texts in the window.",
            "properties": {
              "contact": {
                "type": "null",
                "description": "Always `null` here. Use `GET /contacts/{contact}/history` for one contact's calls and texts."
              },
              "entries": {
                "type": "array",
                "description": "Up to `limit` calls and texts, newest first, including ones with people who aren't saved contacts.",
                "items": {
                  "$ref": "#/components/schemas/ActivityEntry"
                }
              }
            },
            "required": [
              "contact",
              "entries"
            ]
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass it as `cursor` to get the next page, or `null` on the last page."
          },
          "meta": {
            "type": "object",
            "description": "What this page covers.",
            "properties": {
              "coverage": {
                "description": "What this page covers, so you can tell a complete list from a partial one.",
                "$ref": "#/components/schemas/HistoryCoverage"
              }
            },
            "required": [
              "coverage"
            ]
          }
        },
        "required": [
          "data",
          "next_cursor",
          "meta"
        ]
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 The request conflicts with the resource’s current state.
{
  "description": "The request conflicts with the resource’s current state.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

List a contact's history

GET /contacts/{contact}/history

Returns the contact's calls and texts on the token's phone numbers, newest first. Each entry includes its conversation's current status; reading never changes it. Use `call_filter` to list only calls that match an inbox filter, such as missed calls or voicemails. Pass `next_cursor` back as `cursor` with the same filters and `limit`. To pick up newer activity, start again without `cursor`.

Parameters

NameLocationRequiredSchema and description
phone_number_idqueryNoOnly calls and texts on this workspace phone number. It must be one of the token's phone numbers.
{
  "type": "string",
  "format": "uuid"
}
channelqueryNo`call` for calls only, `message` for texts only. Leave it out for both.
{
  "type": "string",
  "enum": [
    "call",
    "message"
  ]
}
call_filterqueryNoOnly calls that match an inbox filter: `missed`, `voicemail`, `incoming`, `outgoing`, `any`, or `unresponded` for missed calls and voicemails nobody has returned yet with a connected call or a text the carrier accepted in the same conversation, even after `occurred_until`. Can't be combined with `channel=message`.
{
  "type": "string",
  "enum": [
    "any",
    "missed",
    "voicemail",
    "unresponded",
    "incoming",
    "outgoing"
  ]
}
occurred_fromqueryNoOnly calls and texts at or after this time, in ISO 8601 with seconds and a time zone offset.
{
  "type": "string",
  "format": "date-time"
}
occurred_untilqueryNoOnly calls and texts before this time, in ISO 8601 with seconds and a time zone offset. It can't be before `occurred_from`.
{
  "type": "string",
  "format": "date-time"
}
limitqueryNoHow many calls and texts to return, from 1 to 100.
{
  "type": "integer",
  "default": 50,
  "minimum": 1,
  "maximum": 100
}
cursorqueryNoThe `next_cursor` from the previous page. Send the same filters and `limit` with it.
{
  "type": "string"
}
contactpathYesThe contact's ID.
{
  "type": "string",
  "format": "uuid"
}

Responses

200 The contact and a page of their calls and texts, newest first.
{
  "description": "The contact and a page of their calls and texts, newest first.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "contact": {
                "id": "77777777-7777-4777-8777-777777777777",
                "name": "Jordan Example",
                "portrait_url": null
              },
              "entries": [
                {
                  "synthetic": false,
                  "id": "88888888-8888-4888-8888-888888888888",
                  "kind": "call",
                  "occurred_at": "2026-10-01T15:04:05.000000Z",
                  "phone_number": {
                    "id": "11111111-1111-4111-8111-111111111111",
                    "label": "Office"
                  },
                  "conversation_id": "55555555-5555-4555-8555-555555555555",
                  "contact_id": "77777777-7777-4777-8777-777777777777",
                  "contact": {
                    "id": "77777777-7777-4777-8777-777777777777",
                    "name": "Jordan Example",
                    "portrait_url": null
                  },
                  "other_party_number": "+12025550123",
                  "from": "+12025550123",
                  "to": "+12025550100",
                  "direction": "inbound",
                  "outcome": "answered",
                  "original_content": null,
                  "state": "completed",
                  "content_state": "available",
                  "source_version": 7,
                  "call_content": {
                    "state": "available",
                    "generation": 1,
                    "cleanup_state": "not_requested",
                    "recordings": [
                      {
                        "id": "22222222-2222-4222-8222-222222222222",
                        "kind": "live",
                        "state": "available",
                        "version": 1,
                        "duration_seconds": 94,
                        "channels": 2,
                        "playback_url_endpoint": "https://api.simplevox.app/v1/recordings/22222222-2222-4222-8222-222222222222/playback",
                        "transcript_id": "33333333-3333-4333-8333-333333333333",
                        "transcript_version": 1,
                        "transcript_state": "available",
                        "analysis_id": "99999999-9999-4999-8999-999999999999",
                        "analysis_state": "available",
                        "summary": "The customer asked to move Thursday's appointment to 10 AM.",
                        "summary_segments": [
                          0
                        ],
                        "facts": [
                          {
                            "text": "The appointment moves to Thursday at 10 AM.",
                            "segments": [
                              0
                            ]
                          }
                        ],
                        "uncertainties": [],
                        "freshness": "current",
                        "source_url": "https://my.simplevox.app/sources/call/88888888-8888-4888-8888-888888888888?version=7"
                      }
                    ],
                    "recording_count": 1,
                    "has_more_recordings": false
                  },
                  "source": {
                    "kind": "call",
                    "id": "88888888-8888-4888-8888-888888888888",
                    "source_version": 7,
                    "url": "https://my.simplevox.app/sources/call/88888888-8888-4888-8888-888888888888?version=7"
                  },
                  "conversation_status": {
                    "status": "unread",
                    "unread_count": 1,
                    "revision": 0,
                    "activity_through": "2026-10-01T15:04:05.000000+00:00",
                    "status_change": null
                  }
                }
              ]
            },
            "next_cursor": null,
            "meta": {
              "coverage": {
                "scope": "authorized_lines_only",
                "filters": {
                  "contact_id": null,
                  "phone_number_id": null,
                  "channel": null,
                  "occurred_from": "2026-10-01T00:00:00.000000+00:00",
                  "occurred_until": "2026-10-02T00:00:00.000000+00:00",
                  "limit": 50,
                  "call_filter": null
                },
                "snapshot_at": "2026-10-02T08:00:00.000000Z",
                "authorized_total": 1,
                "returned": 1,
                "has_more": false,
                "fresh_query_required_for_new_records": true,
                "content": "original_sources_and_current_source_linked_analysis"
              }
            }
          }
        ],
        "properties": {
          "data": {
            "type": "object",
            "description": "The contact and their calls and texts.",
            "properties": {
              "contact": {
                "description": "The contact whose history this is.",
                "$ref": "#/components/schemas/ContactSummary"
              },
              "entries": {
                "type": "array",
                "description": "Up to `limit` calls and texts, newest first.",
                "items": {
                  "$ref": "#/components/schemas/ActivityEntry"
                }
              }
            },
            "required": [
              "contact",
              "entries"
            ]
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass it as `cursor` to get the next page, or `null` on the last page."
          },
          "meta": {
            "type": "object",
            "description": "What this page covers.",
            "properties": {
              "coverage": {
                "description": "What this page covers, so you can tell a complete history from a partial one.",
                "$ref": "#/components/schemas/HistoryCoverage"
              }
            },
            "required": [
              "coverage"
            ]
          }
        },
        "required": [
          "data",
          "next_cursor",
          "meta"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 The request conflicts with the resource’s current state.
{
  "description": "The request conflicts with the resource’s current state.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Retrieve a call or text

GET /sources/{kind}/{source}

Returns one call or text by `kind` and ID, with its current content and conversation status. Entries, events and summaries carry a `source_version`. To read exactly the revision you saw before, for example before quoting it, send that number as `version`. If the record has changed since, you get `409` with `source_changed`; leave `version` out to read the current revision.

Parameters

NameLocationRequiredSchema and description
sourcepathYesThe call's or text's ID.
{
  "type": "string",
  "format": "uuid"
}
kindpathYes`call` or `message`, matching the ID.
{
  "type": "string",
  "enum": [
    "call",
    "message"
  ]
}
versionqueryNoThe `source_version` you saw before. If the call or text has changed since, you get `409` instead of the newer revision.
{
  "type": "integer",
  "minimum": 1
}

Responses

200 The call or text.
{
  "description": "The call or text.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "phone_number": {
                "id": "11111111-1111-4111-8111-111111111111",
                "label": "Office"
              },
              "conversation_id": "55555555-5555-4555-8555-555555555555",
              "contact": {
                "id": "77777777-7777-4777-8777-777777777777",
                "name": "Jordan Example",
                "portrait_url": null
              },
              "other_party_number": "+12025550123",
              "contact_id": "77777777-7777-4777-8777-777777777777",
              "id": "44444444-4444-4444-8444-444444444444",
              "kind": "message",
              "from": "+12025550123",
              "to": "+12025550100",
              "direction": "inbound",
              "original_content": "Can we move Thursday's appointment to 10?",
              "state": "received",
              "content_state": "available",
              "source_version": 1,
              "occurred_at": "2026-10-01T15:04:05.000000Z",
              "call_content": null,
              "conversation_status": {
                "status": "unread",
                "unread_count": 1,
                "revision": 0,
                "activity_through": "2026-10-01T15:04:05.000000+00:00",
                "status_change": null
              }
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The call or text.",
            "$ref": "#/components/schemas/CallOrText"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 The call or text doesn't exist, or isn't on one of the token's phone numbers.
{
  "description": "The call or text doesn't exist, or isn't on one of the token's phone numbers.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `source_changed`: the record changed since `version`. Read it again without `version`.
{
  "description": "`source_changed`: the record changed since `version`. Read it again without `version`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

List changes

GET /events

Returns changes to calls and texts on the token's phone numbers, oldest first, so you can keep your own copy up to date. Without `cursor`, the feed starts with the last 30 days. Events carry IDs and states, not content: fetch content with `GET /sources/{kind}/{source}` and the call endpoints. Delivery is at least once, so deduplicate by event `id`. Pass `next_cursor` back as `cursor` with the same `limit`. It's returned even when nothing is new, so you can poll with it later.

Parameters

NameLocationRequiredSchema and description
cursorqueryNoThe `next_cursor` from your last request. Leave it out to start with the last 30 days. If the token's phone numbers or permissions have changed since, you get `409` and must start again without it.
{
  "type": "string"
}
limitqueryNoHow many events to return, from 1 to 100.
{
  "type": "integer",
  "default": 50,
  "minimum": 1,
  "maximum": 100
}

Responses

200 Events after the cursor, oldest first.
{
  "description": "Events after the cursor, oldest first.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": [
              {
                "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
                "type": "message.status_changed",
                "source": {
                  "kind": "message",
                  "id": "44444444-4444-4444-8444-444444444444",
                  "version": 1
                },
                "revision": 3,
                "occurred_at": "2026-10-01T15:04:07.123456Z",
                "facts": {
                  "state": "delivered",
                  "content_state": "available"
                }
              }
            ],
            "next_cursor": "eyJpdiI6Ik1v…",
            "meta": {
              "has_more": false,
              "retention_days": 30,
              "delivery": "at_least_once_polling",
              "resync": "Retrieve current authorized communications after cursor expiration or scope changes."
            }
          }
        ],
        "properties": {
          "data": {
            "type": "array",
            "description": "Up to `limit` events, oldest first.",
            "items": {
              "$ref": "#/components/schemas/Event"
            }
          },
          "next_cursor": {
            "type": "string",
            "description": "Send it as `cursor` next time. It's returned even when there are no new events, so you can keep polling with it."
          },
          "meta": {
            "type": "object",
            "description": "How to keep polling.",
            "properties": {
              "has_more": {
                "type": "boolean",
                "description": "Whether more events are waiting. If so, request again with `next_cursor` right away."
              },
              "retention_days": {
                "type": "integer",
                "description": "How many days events are kept. A cursor older than this returns `410`.",
                "const": 30,
                "examples": [
                  30
                ]
              },
              "delivery": {
                "type": "string",
                "description": "Always `at_least_once_polling`: you poll for events, and the same event can come more than once.",
                "const": "at_least_once_polling"
              },
              "resync": {
                "type": "string",
                "description": "A reminder of what to do after `409` or `410`: read the calls and texts you keep again, then poll without `cursor`.",
                "const": "Retrieve current authorized communications after cursor expiration or scope changes.",
                "examples": [
                  "Retrieve current authorized communications after cursor expiration or scope changes."
                ]
              }
            },
            "required": [
              "has_more",
              "retention_days",
              "delivery",
              "resync"
            ]
          }
        },
        "required": [
          "data",
          "next_cursor",
          "meta"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
409 `event_scope_changed_resync_required`: the token's phone numbers or permissions changed. Restart without `cursor` and reconcile your copy.
{
  "description": "`event_scope_changed_resync_required`: the token's phone numbers or permissions changed. Restart without `cursor` and reconcile your copy.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
410 `event_cursor_expired_resync_required`: the cursor is older than 30 days. Restart without `cursor` and reconcile your copy.
{
  "description": "`event_cursor_expired_resync_required`: the cursor is older than 30 days. Restart without `cursor` and reconcile your copy.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

List a call's recordings

GET /calls/{call}/recordings

Returns the call's recordings, 20 per page, with each recording's transcript and summary state. A recording is playable only when its `state` is `available`. Use `playback_url_endpoint` to get a link you can play. Reading never starts transcription or analysis.

Parameters

NameLocationRequiredSchema and description
callpathYesThe call's ID.
{
  "type": "string",
  "format": "uuid"
}
source_versionqueryNoThe call's `source_version` you saw before. If the call has changed since, you get `409` instead.
{
  "type": "integer",
  "minimum": 1
}
cursorqueryNoThe `next_cursor` from the previous page.
{
  "type": [
    "string",
    "null"
  ],
  "maxLength": 4096
}

Responses

200 A page of the call's recordings, newest first.
{
  "description": "A page of the call's recordings, newest first.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "state": "available",
              "generation": 1,
              "cleanup_state": "not_requested",
              "recordings": [
                {
                  "id": "22222222-2222-4222-8222-222222222222",
                  "kind": "live",
                  "state": "available",
                  "version": 1,
                  "duration_seconds": 94,
                  "channels": 2,
                  "playback_url_endpoint": "https://api.simplevox.app/v1/recordings/22222222-2222-4222-8222-222222222222/playback",
                  "transcript_id": "33333333-3333-4333-8333-333333333333",
                  "transcript_version": 1,
                  "transcript_state": "available",
                  "analysis_id": "99999999-9999-4999-8999-999999999999",
                  "analysis_state": "available",
                  "summary": "The customer asked to move Thursday's appointment to 10 AM.",
                  "summary_segments": [
                    0
                  ],
                  "facts": [
                    {
                      "text": "The appointment moves to Thursday at 10 AM.",
                      "segments": [
                        0
                      ]
                    }
                  ],
                  "uncertainties": [],
                  "freshness": "current",
                  "source_url": "https://my.simplevox.app/sources/call/88888888-8888-4888-8888-888888888888?version=7"
                }
              ],
              "recording_count": 1,
              "has_more_recordings": false
            },
            "next_cursor": null
          }
        ],
        "properties": {
          "data": {
            "description": "The call's recordings.",
            "$ref": "#/components/schemas/CallContent"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass it as `cursor` to get the next 20 recordings, or `null` on the last page."
          }
        },
        "required": [
          "data",
          "next_cursor"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `source_changed`: the call changed since `source_version`. Read it again without `source_version`.
{
  "description": "`source_changed`: the call changed since `source_version`. Read it again without `source_version`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 Validation error
{
  "description": "Validation error",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Retrieve a transcript

GET /calls/{call}/transcript

Returns the transcript of the call's latest recording, or of `recording_id`, 100 segments per page. Speaker labels come from the audio; they aren't verified identities. To fetch exact segments you quoted, send `recording_id`, `transcript_version` and 1–20 `segment_indices`, without `cursor`. If the transcript has changed since that version, you get `409`.

Parameters

NameLocationRequiredSchema and description
callpathYesThe call's ID.
{
  "type": "string",
  "format": "uuid"
}
source_versionqueryNoThe call's `source_version` you saw before. If the call has changed since, you get `409` instead.
{
  "type": "integer",
  "minimum": 1
}
recording_idqueryNoRead this recording's transcript instead of the latest one. Required with `segment_indices`.
{
  "type": [
    "string",
    "null"
  ],
  "format": "uuid"
}
kindqueryNoRead the latest `live` recording or the latest `voicemail` instead of the latest recording of either kind.
{
  "type": [
    "string",
    "null"
  ],
  "enum": [
    "live",
    "voicemail",
    null
  ]
}
cursorqueryNoThe `next_cursor` from the previous page. Send the same `recording_id` and `kind` with it, and leave out `segment_indices`.
{
  "type": [
    "string",
    "null"
  ],
  "maxLength": 4096
}
transcript_versionqueryNoThe transcript `version` you quoted. Required with `segment_indices`; if the transcript has changed since, you get `409`.
{
  "type": [
    "integer",
    "null"
  ],
  "minimum": 1
}
segment_indices[]queryNoThe `index` of each segment to return, 1–20 of them, for example `segment_indices[]=4&segment_indices[]=5`. Send with `recording_id` and `transcript_version`, without `cursor`.
{
  "type": [
    "array",
    "null"
  ],
  "items": {
    "type": "integer",
    "minimum": 0,
    "maximum": 9999
  },
  "minItems": 1,
  "maxItems": 20,
  "uniqueItems": true
}

Responses

200 A page of the transcript, or its state when it isn't available.
{
  "description": "A page of the transcript, or its state when it isn't available.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "state": "available",
              "recording_id": "22222222-2222-4222-8222-222222222222",
              "transcript_id": "33333333-3333-4333-8333-333333333333",
              "version": 1,
              "source_version": 7,
              "external_contact": {
                "name": "Jordan Example",
                "role": "caller",
                "portrait_url": null
              },
              "speaker_identity": "audio_labels_not_verified_people",
              "error_code": null,
              "provenance": null,
              "segments": [
                {
                  "index": 0,
                  "text": "Thursday at ten works for me.",
                  "speaker": "speaker_0",
                  "channel_index": null,
                  "start_seconds": 0.5,
                  "end_seconds": 2.4
                }
              ],
              "total_segments": 1
            },
            "next_cursor": null
          }
        ],
        "properties": {
          "data": {
            "description": "The transcript page.",
            "$ref": "#/components/schemas/Transcript"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass it as `cursor` to get the next 100 segments, or `null` after the last one."
          }
        },
        "required": [
          "data",
          "next_cursor"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `source_changed`: the call or transcript changed since the version you sent. Read it again without the version.
{
  "description": "`source_changed`: the call or transcript changed since the version you sent. Read it again without the version.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 Invalid fields, or `segment_out_of_range`, `citation_requires_recording_and_version_without_cursor` or `invalid_cursor`.
{
  "description": "Invalid fields, or `segment_out_of_range`, `citation_requires_recording_and_version_without_cursor` or `invalid_cursor`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Retrieve a call summary

GET /calls/{call}/summary

Returns the saved AI summary of a call, with the facts it found and the transcript segments each one is based on. Check `freshness` to see whether the summary covers the latest transcript. Reading never starts analysis, creates tasks or updates contacts. When you quote a summary, keep its `source_version` and segment indices so you can show where it came from.

Parameters

NameLocationRequiredSchema and description
callpathYesThe call's ID.
{
  "type": "string",
  "format": "uuid"
}
source_versionqueryNoThe call's `source_version` you saw before. If the call has changed since, you get `409` instead.
{
  "type": "integer",
  "minimum": 1
}
recording_idqueryNoRead the summary of this recording instead of the call's latest one.
{
  "type": [
    "string",
    "null"
  ],
  "format": "uuid"
}

Responses

200 The call's summary, or its state when there's no current summary.
{
  "description": "The call's summary, or its state when there's no current summary.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "call_id": "88888888-8888-4888-8888-888888888888",
              "source_version": 7,
              "recording_id": "22222222-2222-4222-8222-222222222222",
              "transcript_version": 1,
              "analysis_id": "99999999-9999-4999-8999-999999999999",
              "state": "available",
              "freshness": "current",
              "summary": "The customer asked to move Thursday's appointment to 10 AM, and the time was confirmed.",
              "summary_segments": [
                0
              ],
              "facts": [
                {
                  "text": "The appointment moves to Thursday at 10 AM.",
                  "segments": [
                    0
                  ]
                }
              ],
              "historical_facts": [],
              "uncertainties": [],
              "current_call": {
                "data": {
                  "state": "available",
                  "recording_id": "22222222-2222-4222-8222-222222222222",
                  "transcript_id": "33333333-3333-4333-8333-333333333333",
                  "version": 1,
                  "source_version": 7,
                  "external_contact": {
                    "name": "Jordan Example",
                    "role": "caller",
                    "portrait_url": null
                  },
                  "speaker_identity": "audio_labels_not_verified_people",
                  "error_code": null,
                  "provenance": null,
                  "segments": [
                    {
                      "index": 0,
                      "text": "Thursday at ten works for me.",
                      "speaker": "speaker_0",
                      "channel_index": null,
                      "start_seconds": 0.5,
                      "end_seconds": 2.4
                    }
                  ],
                  "total_segments": 1
                },
                "next_cursor": null
              },
              "historical_context": {
                "entries": [],
                "coverage": {
                  "scope": "lines_shared_by_all_current_call_readers",
                  "lookback_days": 90,
                  "entry_limit": 10,
                  "segments_per_call": 100,
                  "recording_selection": "latest_per_historical_call",
                  "characters_per_entry": 4000,
                  "contact_association": "persisted_source_contact_only",
                  "before": "2026-10-01T15:04:05.000000Z",
                  "authorized_total": 0,
                  "returned": 0,
                  "has_more": false,
                  "state": "bounded"
                }
              },
              "history_endpoint": "https://api.simplevox.app/v1/contacts/77777777-7777-4777-8777-777777777777/history",
              "audience": "all_current_call_line_readers_intersected_with_request_grant"
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The call's summary.",
            "$ref": "#/components/schemas/CallSummary"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `source_changed`: the call changed since `source_version`. Read it again without `source_version`.
{
  "description": "`source_changed`: the call changed since `source_version`. Read it again without `source_version`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Get a playback link

GET /recordings/{recording}/playback

Returns a short-lived link to a recording's audio. Fetch it when the person presses play, and request a new one after `expires_at`. Anyone with the link can play the audio until it expires, so don't log or share it.

Parameters

NameLocationRequiredSchema and description
recordingpathYesThe recording's ID, from a recording's `id`.
{
  "type": "string",
  "format": "uuid"
}
source_versionqueryNoThe call's `source_version` you saw before. If the call has changed since, you get `409` instead.
{
  "type": "integer",
  "minimum": 1
}

Responses

200 A link to the recording's audio.
{
  "description": "A link to the recording's audio.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "recording_id": "22222222-2222-4222-8222-222222222222",
              "version": 1,
              "url": "https://media.simplevox.app/recordings/22222222-2222-4222-8222-222222222222.mp3?signature=…",
              "expires_at": "2026-10-01T15:09:05+00:00"
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The link.",
            "$ref": "#/components/schemas/PlaybackLink"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  },
  "headers": {
    "Cache-Control": {
      "schema": {
        "type": "string",
        "const": "private, no-store"
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `media_unavailable`: the audio isn't available yet, or failed to process. `source_changed`: the call changed since `source_version`.
{
  "description": "`media_unavailable`: the audio isn't available yet, or failed to process. `source_changed`: the call changed since `source_version`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
410 `content_deleted`: the recording was deleted.
{
  "description": "`content_deleted`: the recording was deleted.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Send a text

POST /messages

Sends an SMS from one of the token's phone numbers to a number in E.164 format, such as `+12025550123`. Pictures and other attachments aren't supported. A `202` response means SimpleVox saved the text, not that it was delivered: poll `GET /messages/{message}` for its `state`. Requires the **Send SMS** write permission (`messages.send`) on the Boost plan, and the number must be approved for business texting. If the response is lost, retry with the same `Idempotency-Key` and body; the text is sent once.

Parameters

NameLocationRequiredSchema and description
Idempotency-KeyheaderYesA new UUID for each text. If the response is lost, retry with the same key and body; the text is sent once.
{
  "type": "string",
  "format": "uuid"
}

Request body

{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/PublicSendTextMessageRequest"
      }
    }
  }
}

Responses

202 SimpleVox saved the text and is sending it.
{
  "description": "SimpleVox saved the text and is sending it.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "id": "44444444-4444-4444-8444-444444444444",
              "conversation_id": "55555555-5555-4555-8555-555555555555",
              "phone_number_id": "11111111-1111-4111-8111-111111111111",
              "operation_id": "66666666-6666-4666-8666-666666666666",
              "state": "queued",
              "content_state": "available",
              "from": "+12025550100",
              "to": "+12025550123",
              "body": "Our appointment is confirmed for Thursday.",
              "error_code": null,
              "media_count": 0,
              "provider_segments": null
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The text. Poll `GET /messages/{message}` for its delivery `state`.",
            "$ref": "#/components/schemas/Message"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
403 Nothing was sent. `api_writes_unavailable`: the workspace isn't on Boost. `api_write_permission_required` or `not_authorized`: the token doesn't have `messages.send`. `account_restricted`: texting is paused. `messaging_allowance_reached`: the allowance is used up until `error.retry_at`. `sms_recipient_opted_out`, `sms_registration_unavailable` or `sms_entitlement_unavailable`: this number can't text this recipient.
{
  "description": "Nothing was sent. `api_writes_unavailable`: the workspace isn't on Boost. `api_write_permission_required` or `not_authorized`: the token doesn't have `messages.send`. `account_restricted`: texting is paused. `messaging_allowance_reached`: the allowance is used up until `error.retry_at`. `sms_recipient_opted_out`, `sms_registration_unavailable` or `sms_entitlement_unavailable`: this number can't text this recipient.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
404 `line_not_found_or_unavailable`: `phone_number_id` isn't one of the token's phone numbers.
{
  "description": "`line_not_found_or_unavailable`: `phone_number_id` isn't one of the token's phone numbers.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `sms_intent_conflict`: the `Idempotency-Key` was already used for a different text.
{
  "description": "`sms_intent_conflict`: the `Idempotency-Key` was already used for a different text.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 `messaging_rate_limited`: too many texts recently. Nothing was sent; retry after `error.retry_at`.
{
  "description": "`messaging_rate_limited`: too many texts recently. Nothing was sent; retry after `error.retry_at`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
503 `usage_check_unavailable`: usage couldn't be checked, so nothing was sent. Retry after the `Retry-After` header.
{
  "description": "`usage_check_unavailable`: usage couldn't be checked, so nothing was sent. Retry after the `Retry-After` header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}

Retrieve a message

GET /messages/{message}

Returns a text on one of the token's phone numbers with its current `state`, including texts customers sent you. You can keep checking texts the token sent after the workspace leaves Boost. `media` lists the text's pictures and files once SimpleVox has saved them. Each one's `download_url` and `preview_url` are signed links that work without your token until `expires_at`, about 15 minutes after the read. Read the text again for fresh ones.

Parameters

NameLocationRequiredSchema and description
messagepathYesThe text's ID.
{
  "type": "string",
  "format": "uuid"
}

Responses

200 The text, its current state and its pictures and files.
{
  "description": "The text, its current state and its pictures and files.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "id": "44444444-4444-4444-8444-444444444444",
              "conversation_id": "55555555-5555-4555-8555-555555555555",
              "phone_number_id": "11111111-1111-4111-8111-111111111111",
              "operation_id": "66666666-6666-4666-8666-666666666666",
              "state": "delivered",
              "content_state": "available",
              "from": "+12025550100",
              "to": "+12025550123",
              "body": "Our appointment is confirmed for Thursday.",
              "error_code": null,
              "media_count": 0,
              "provider_segments": 1,
              "media": {
                "state": "none",
                "count": 0,
                "error_code": null,
                "attachments": []
              }
            }
          },
          {
            "data": {
              "id": "44444444-4444-4444-8444-444444444444",
              "conversation_id": "55555555-5555-4555-8555-555555555555",
              "phone_number_id": "11111111-1111-4111-8111-111111111111",
              "operation_id": null,
              "state": "received",
              "content_state": "media_available",
              "from": "+12025550123",
              "to": "+12025550100",
              "body": "Here's the photo of the leak.",
              "error_code": null,
              "media_count": 1,
              "provider_segments": 1,
              "media": {
                "state": "available",
                "count": 1,
                "error_code": null,
                "attachments": [
                  {
                    "id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
                    "mime": "image/jpeg",
                    "bytes": 248113,
                    "download_url": "https://media.simplevox.app/messages/44444444-4444-4444-8444-444444444444/dddddddd-dddd-4ddd-8ddd-dddddddddddd?response-content-disposition=attachment&X-Amz-Signature=…",
                    "preview_url": "https://media.simplevox.app/messages/44444444-4444-4444-8444-444444444444/dddddddd-dddd-4ddd-8ddd-dddddddddddd?response-content-disposition=inline&X-Amz-Signature=…",
                    "expires_at": "2026-10-01T15:19:05+00:00"
                  }
                ]
              }
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The text.",
            "$ref": "#/components/schemas/Message"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
403 This token can’t perform this operation.
{
  "description": "This token can’t perform this operation.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Download an attachment

GET /messages/{message}/attachments/{attachment}

Redirects with `302` to a fresh signed link for a picture or file sent with a text. The link serves the file with its stored content type, works without your token and expires after about 15 minutes. `GET /messages/{message}` already includes these links in `media.attachments`, so you only need this endpoint for a fresh one. Follow the redirect, for example with `curl -L`. Add `inline=1` for a link that displays a JPEG, PNG, GIF or WebP image instead of downloading it.

Parameters

NameLocationRequiredSchema and description
messagepathYesThe text's ID.
{
  "type": "string",
  "format": "uuid"
}
attachmentpathYesThe attachment's ID, from `media.attachments`.
{
  "type": "string",
  "format": "uuid"
}
inlinequeryNoSend `1` for a link that shows a JPEG, PNG, GIF or WebP image in the browser instead of downloading it. Other files return `404` with it.
{
  "type": "boolean"
}
source_versionqueryNoThe text's `source_version` you saw before. If the text has changed since, you get `409` instead.
{
  "type": "integer",
  "minimum": 1
}

Responses

302 Go to the signed link in `Location` to download the file. SimpleVox doesn't send the file itself.
{
  "description": "Go to the signed link in `Location` to download the file. SimpleVox doesn't send the file itself.",
  "headers": {
    "Location": {
      "description": "The signed link. It works without your token until it expires, about 15 minutes after this response, so don't log or share it.",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 The message or attachment doesn't exist or isn't available to this token, or `inline=1` was sent for a file that isn't a supported image.
{
  "description": "The message or attachment doesn't exist or isn't available to this token, or `inline=1` was sent for a file that isn't a supported image.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `source_changed`: the message changed since `source_version`.
{
  "description": "`source_changed`: the message changed since `source_version`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

List calling options

GET /calling-options

Returns the numbers you can call from and the devices that can ring for your side of the call, default first. Check it before you show a **Call** button: a browser is listed only while SimpleVox is open with the browser phone on, and the phone app only while calls are allowed on it. Requires the **Start assisted calls** write permission (`calls.start`) on the Boost plan.

Responses

200 The numbers and devices you can use for a call.
{
  "description": "The numbers and devices you can use for a call.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "lines": [
                {
                  "id": "11111111-1111-4111-8111-111111111111",
                  "label": "Office",
                  "number": "+12025550100",
                  "available": true
                }
              ],
              "calling_devices": [
                {
                  "id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
                  "label": "Chrome on MacBook",
                  "type": "browser",
                  "available": true,
                  "default": true,
                  "acceptance": "Answer in the browser.",
                  "modes": [
                    "user_first"
                  ]
                }
              ],
              "modes": [
                "user_first"
              ]
            }
          }
        ],
        "properties": {
          "data": {
            "type": "object",
            "description": "What you can call from and with.",
            "properties": {
              "lines": {
                "type": "array",
                "description": "The token's phone numbers. Check `available` before you offer one.",
                "items": {
                  "$ref": "#/components/schemas/CallingNumber"
                }
              },
              "calling_devices": {
                "type": "array",
                "description": "Your devices that can take a call now, default first. SimpleVox rings one of them for your side of the call.",
                "items": {
                  "$ref": "#/components/schemas/CallingDevice"
                }
              },
              "modes": {
                "type": "array",
                "description": "The call orders you can send as `mode`.",
                "items": {
                  "type": "string",
                  "enum": [
                    "user_first",
                    "recipient_first"
                  ]
                }
              }
            },
            "required": [
              "lines",
              "calling_devices",
              "modes"
            ]
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
403 An error
{
  "description": "An error",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Start a call

POST /assisted-calls

Sets up a call between you and a customer. SimpleVox rings your device, says who it's calling when you answer, then dials the customer from your business number. You hear it ring, and you're connected when they pick up, as on any call you place. Nobody presses 1. A `202` response means the call is being set up, not that anyone answered: poll `GET /assisted-calls/{assistedCall}` for its progress. Requires the **Start assisted calls** write permission (`calls.start`) on the Boost plan. If the response is lost, retry with the same `Idempotency-Key` and body; the call is set up once.

Parameters

NameLocationRequiredSchema and description
Idempotency-KeyheaderYesA new UUID for each call. If the response is lost, retry with the same key and body; the call is set up once.
{
  "type": "string",
  "format": "uuid"
}

Request body

{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/PublicStartCallRequest"
      }
    }
  }
}

Responses

202 SimpleVox is setting up the call.
{
  "description": "SimpleVox is setting up the call.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
              "mode": "user_first",
              "setup_state": "pending",
              "connection_state": "not_verified",
              "outcome": null,
              "reason": null,
              "revision": 2,
              "expires_at": "2026-10-01T15:05:35.000000Z",
              "connected_observed_at": null,
              "ended_observed_at": null,
              "cleanup": "not_requested",
              "participants": [
                {
                  "role": "user",
                  "state": "ringing",
                  "accepted": false,
                  "joined": false,
                  "left": false
                },
                {
                  "role": "recipient",
                  "state": "prepared",
                  "accepted": false,
                  "joined": false,
                  "left": false
                }
              ],
              "communication": {
                "kind": "call",
                "id": "88888888-8888-4888-8888-888888888888",
                "source_version": 1,
                "url": "https://my.simplevox.app/sources/call/88888888-8888-4888-8888-888888888888?version=1"
              },
              "limits": {
                "ring_seconds": 20,
                "accept_seconds": 0,
                "setup_seconds": 90,
                "max_call_seconds": 14400
              },
              "recording_enabled": true,
              "next_step": "Poll GET /v1/assisted-calls/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb; POST /v1/assisted-calls/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/end to cancel or hang up. Cleanup remains unconfirmed until both parties end."
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The call. Poll `GET /assisted-calls/{assistedCall}` for its progress.",
            "$ref": "#/components/schemas/AssistedCall"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
403 `api_writes_unavailable`: the workspace isn't on Boost. `api_write_permission_required` or `not_authorized`: the token doesn't have `calls.start`. `calling_line_unavailable`, `endpoint_unavailable`, `account_restricted` or `calling_allowance_reached`: this call can't be placed now.
{
  "description": "`api_writes_unavailable`: the workspace isn't on Boost. `api_write_permission_required` or `not_authorized`: the token doesn't have `calls.start`. `calling_line_unavailable`, `endpoint_unavailable`, `account_restricted` or `calling_allowance_reached`: this call can't be placed now.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
404 `line_not_found_or_unavailable`: `from` isn't one of the token's phone numbers.
{
  "description": "`line_not_found_or_unavailable`: `from` isn't one of the token's phone numbers.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
409 `call_intent_conflict`: the `Idempotency-Key` was used for a different call. `user_already_in_call`: you're already on a call.
{
  "description": "`call_intent_conflict`: the `Idempotency-Key` was used for a different call. `user_already_in_call`: you're already on a call.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Retrieve a call

GET /assisted-calls/{assistedCall}

Returns a call this token started: its setup progress, each participant's state, whether you were connected, and whether cleanup finished. You can keep checking calls after the workspace leaves Boost.

Parameters

NameLocationRequiredSchema and description
assistedCallpathYesThe assisted call's ID, from `id` when you started it.
{
  "type": "string",
  "format": "uuid"
}

Responses

200 The call's progress.
{
  "description": "The call's progress.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
              "mode": "user_first",
              "setup_state": "connected",
              "connection_state": "verified",
              "outcome": null,
              "reason": null,
              "revision": 5,
              "expires_at": "2026-10-01T15:05:35.000000Z",
              "connected_observed_at": "2026-10-01T15:04:41.000000Z",
              "ended_observed_at": null,
              "cleanup": "not_requested",
              "participants": [
                {
                  "role": "user",
                  "state": "in-progress",
                  "accepted": true,
                  "joined": true,
                  "left": false
                },
                {
                  "role": "recipient",
                  "state": "in-progress",
                  "accepted": true,
                  "joined": true,
                  "left": false
                }
              ],
              "communication": {
                "kind": "call",
                "id": "88888888-8888-4888-8888-888888888888",
                "source_version": 1,
                "url": "https://my.simplevox.app/sources/call/88888888-8888-4888-8888-888888888888?version=1"
              },
              "limits": {
                "ring_seconds": 20,
                "accept_seconds": 0,
                "setup_seconds": 90,
                "max_call_seconds": 14400
              },
              "recording_enabled": true,
              "next_step": "Poll GET /v1/assisted-calls/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb; POST /v1/assisted-calls/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/end to cancel or hang up. Cleanup remains unconfirmed until both parties end."
            }
          }
        ],
        "properties": {
          "data": {
            "description": "The call.",
            "$ref": "#/components/schemas/AssistedCall"
          }
        },
        "required": [
          "data"
        ]
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

End a call

POST /assisted-calls/{assistedCall}/end

Cancels a call that's still being set up, or hangs up a connected one. Only calls this token started can be ended, including after the workspace leaves Boost. A `202` response means SimpleVox asked the phone network to end the call, not that it has ended: poll `GET /assisted-calls/{assistedCall}` until cleanup is confirmed. Requires the **End assisted calls** write permission (`calls.control`).

Parameters

NameLocationRequiredSchema and description
assistedCallpathYesThe assisted call's ID, from `id` when you started it.
{
  "type": "string",
  "format": "uuid"
}

Responses

202 SimpleVox asked the phone network to end the call.
{
  "description": "SimpleVox asked the phone network to end the call.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "examples": [
          {
            "data": {
              "id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
              "mode": "user_first",
              "setup_state": "cleanup",
              "connection_state": "not_verified",
              "outcome": null,
              "reason": null,
              "revision": 3,
              "expires_at": "2026-10-01T15:05:35.000000Z",
              "connected_observed_at": null,
              "ended_observed_at": null,
              "cleanup": "pending",
              "participants": [
                {
                  "role": "user",
                  "state": "ringing",
                  "accepted": false,
                  "joined": false,
                  "left": false
                },
                {
                  "role": "recipient",
                  "state": "prepared",
                  "accepted": false,
                  "joined": false,
                  "left": false
                }
              ],
              "communication": {
                "kind": "call",
                "id": "88888888-8888-4888-8888-888888888888",
                "source_version": 1,
                "url": "https://my.simplevox.app/sources/call/88888888-8888-4888-8888-888888888888?version=1"
              },
              "limits": {
                "ring_seconds": 20,
                "accept_seconds": 0,
                "setup_seconds": 90,
                "max_call_seconds": 14400
              },
              "recording_enabled": true,
              "next_step": "Poll GET /v1/assisted-calls/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb; POST /v1/assisted-calls/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/end to cancel or hang up. Cleanup remains unconfirmed until both parties end."
            },
            "action": "cancel_setup"
          }
        ],
        "properties": {
          "data": {
            "description": "The call after your request.",
            "$ref": "#/components/schemas/AssistedCall"
          },
          "action": {
            "type": "string",
            "description": "What SimpleVox did. `cancel_setup`: it stopped setting up the call. `end_call`: it's hanging up a connected call. `already_ended`: the call had already ended. `connection_observed`: you were connected while it was cancelling setup; check the call and end it again.",
            "enum": [
              "cancel_setup",
              "end_call",
              "already_ended",
              "connection_observed"
            ]
          }
        },
        "required": [
          "data",
          "action"
        ]
      }
    }
  }
}
401 Send a valid personal API token in the Authorization header.
{
  "description": "Send a valid personal API token in the Authorization header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "WWW-Authenticate": {
      "description": "Bearer challenge naming the `api:read` scope.",
      "schema": {
        "type": "string"
      }
    }
  }
}
403 `api_write_permission_required` or `not_authorized`: the token doesn't have `calls.control`.
{
  "description": "`api_write_permission_required` or `not_authorized`: the token doesn't have `calls.control`.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
404 Not found, or not available to this token.
{
  "description": "Not found, or not available to this token.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
422 One or more fields are invalid.
{
  "description": "One or more fields are invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  }
}
429 Too many requests. Retry after the Retry-After header.
{
  "description": "Too many requests. Retry after the Retry-After header.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/Error"
      }
    }
  },
  "headers": {
    "Retry-After": {
      "description": "Seconds until retry.",
      "schema": {
        "type": "integer"
      }
    }
  }
}

Schemas

References such as #/components/schemas/Error refer to these generated definitions. Every error response uses Error, and ApiErrorCode lists each error.code.

ActivityEntry
{
  "type": "object",
  "properties": {
    "synthetic": {
      "type": "boolean",
      "description": "`true` for sample calls and texts in demo or test data, never for real ones."
    },
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The call's or text's ID.",
      "examples": [
        "88888888-8888-4888-8888-888888888888"
      ]
    },
    "kind": {
      "type": "string",
      "description": "Whether it's a call or a text.",
      "enum": [
        "call",
        "message"
      ]
    },
    "occurred_at": {
      "type": "string",
      "format": "date-time",
      "description": "When the call started or the text was sent or received."
    },
    "phone_number": {
      "description": "The workspace phone number it happened on.",
      "$ref": "#/components/schemas/PhoneNumberSummary"
    },
    "conversation_id": {
      "type": "string",
      "format": "uuid",
      "description": "The conversation it belongs to: everything between this workspace phone number and the other party."
    },
    "contact_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The saved contact it belongs to, or `null` when the other party isn't a saved contact."
    },
    "contact": {
      "type": [
        "object",
        "null"
      ],
      "description": "The saved contact it belongs to, or `null` when the other party isn't a saved contact.",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "The contact's ID. Use it with `GET /contacts/{contact}`.",
          "examples": [
            "77777777-7777-4777-8777-777777777777"
          ]
        },
        "name": {
          "type": "string",
          "description": "The contact's name.",
          "examples": [
            "Jordan Example"
          ]
        },
        "portrait_url": {
          "type": [
            "string",
            "null"
          ],
          "format": "uri",
          "description": "A link to the contact's picture, or `null` when it has none."
        }
      },
      "required": [
        "id",
        "name",
        "portrait_url"
      ]
    },
    "other_party_number": {
      "type": [
        "string",
        "null"
      ],
      "description": "The customer's phone number: `from` for incoming calls and texts, `to` for outgoing ones.",
      "examples": [
        "+12025550123"
      ]
    },
    "from": {
      "type": [
        "string",
        "null"
      ],
      "description": "The number that placed the call or sent the text, in E.164 format.",
      "examples": [
        "+12025550123"
      ]
    },
    "to": {
      "type": [
        "string",
        "null"
      ],
      "description": "The number that was called or texted, in E.164 format.",
      "examples": [
        "+12025550100"
      ]
    },
    "direction": {
      "type": "string",
      "description": "`inbound` when the customer called or texted you, `outbound` when you called or texted them.",
      "enum": [
        "inbound",
        "outbound"
      ]
    },
    "outcome": {
      "type": [
        "string",
        "null"
      ],
      "description": "How a call ended, or `null` for texts and calls that haven't ended. `answered`: someone picked up. `voicemail`: the caller left a voicemail. `missed`, `busy`, `no-answer` or `not_connected`: nobody was connected. `declined`, `expired` and `completed` come from assisted calls that ended before anyone was connected.",
      "enum": [
        "answered",
        "voicemail",
        "missed",
        "not_connected",
        "busy",
        "no-answer",
        "failed",
        "canceled",
        "declined",
        "expired",
        "completed",
        null
      ]
    },
    "original_content": {
      "type": [
        "string",
        "null"
      ],
      "description": "The text as it was sent or received. Calls usually have `null`; read what was said from the transcript.",
      "examples": [
        "Can we move Thursday's appointment to 10?"
      ]
    },
    "state": {
      "type": "string",
      "description": "Where the call or text is. Calls use `prepared`, `queued`, `initiated`, `ringing`, `in-progress`, `completed`, `busy`, `no-answer`, `failed` and `canceled`. Texts use the `MessageState` values.",
      "enum": [
        "prepared",
        "queued",
        "initiated",
        "ringing",
        "in-progress",
        "completed",
        "busy",
        "no-answer",
        "submitting",
        "sending",
        "sent",
        "delivered",
        "undelivered",
        "uncertain",
        "receiving",
        "received",
        "failed",
        "canceled"
      ]
    },
    "content_state": {
      "type": "string",
      "description": "Whether the content is ready. For calls: `not_recorded`, `pending`, `available`, `failed`, `unavailable` or `deleted`, as in `call_content.state`. For texts: `available`, or `media_pending`, `media_available` or `unsupported_media` for texts with pictures or files, which `GET /messages/{message}` lists.",
      "enum": [
        "available",
        "pending",
        "unavailable",
        "not_recorded",
        "failed",
        "deleted",
        "media_pending",
        "media_available",
        "unsupported_media"
      ]
    },
    "source_version": {
      "type": "integer",
      "description": "Goes up whenever the call or text changes, for example when a recording is saved or it's linked to a different contact. Send it as `version` to read exactly this revision later.",
      "examples": [
        2
      ]
    },
    "call_content": {
      "type": [
        "object",
        "null"
      ],
      "description": "For calls, the recordings and the state of their transcripts and AI summaries; `null` for texts.",
      "properties": {
        "state": {
          "type": "string",
          "description": "Whether the call has recorded content. `not_recorded`: nothing was recorded. `pending`: recordings are still being saved. `available`: recordings were saved. `failed` or `unavailable`: they couldn't be saved. `deleted`: someone deleted the call's recorded content.",
          "enum": [
            "available",
            "pending",
            "not_recorded",
            "failed",
            "unavailable",
            "deleted"
          ]
        },
        "generation": {
          "type": "integer",
          "description": "Starts at `1` and goes up when the call's recorded content is deleted, which makes its transcripts and summaries out of date.",
          "examples": [
            1
          ]
        },
        "cleanup_state": {
          "type": "string",
          "description": "After a deletion, whether the stored audio and transcripts have been removed. `not_requested` until someone deletes the content.",
          "enum": [
            "not_requested",
            "pending",
            "processing",
            "reconciling",
            "failed",
            "completed"
          ]
        },
        "recordings": {
          "type": "array",
          "description": "Up to 20 recordings, newest first.",
          "items": {
            "$ref": "#/components/schemas/Recording"
          }
        },
        "recording_count": {
          "type": "integer",
          "description": "How many recordings the call has in total.",
          "examples": [
            1
          ]
        },
        "has_more_recordings": {
          "type": "boolean",
          "description": "Whether there are more recordings after this page."
        }
      },
      "required": [
        "state",
        "generation",
        "cleanup_state",
        "recordings",
        "recording_count",
        "has_more_recordings"
      ]
    },
    "source": {
      "description": "Where to read this call or text again, at this revision.",
      "$ref": "#/components/schemas/CallOrTextLink"
    },
    "conversation_status": {
      "description": "The conversation's shared inbox status.",
      "$ref": "#/components/schemas/ConversationStatus"
    }
  },
  "required": [
    "synthetic",
    "id",
    "kind",
    "occurred_at",
    "phone_number",
    "conversation_id",
    "contact_id",
    "contact",
    "other_party_number",
    "from",
    "to",
    "direction",
    "outcome",
    "original_content",
    "state",
    "content_state",
    "source_version",
    "call_content",
    "source",
    "conversation_status"
  ],
  "title": "ActivityEntry"
}
ApiErrorCode
{
  "type": "string",
  "description": "Machine-readable `error.code` values returned by the REST API. Branch on the code, not the message.\n| |\n|---|\n| `bad_request` <br/> The request could not be understood. |\n| `not_authorized` <br/> The API token is missing, malformed, expired or revoked, or your workspace membership ended. Create a new personal API token. |\n| `forbidden` <br/> The token can't perform this operation. |\n| `not_found` <br/> The resource doesn't exist, or the token can't access it. Both return the same response. |\n| `method_not_allowed` <br/> The endpoint doesn't support this HTTP method. |\n| `conflict` <br/> The request conflicts with the resource's current state. |\n| `gone` <br/> The resource is no longer available. |\n| `precondition_failed` <br/> A precondition header didn't match. |\n| `validation_failed` <br/> One or more fields are invalid. `error.fields` lists the messages for each field. |\n| `precondition_required` <br/> The request needs a precondition header. |\n| `rate_limited` <br/> Too many requests. Wait for the `Retry-After` header before retrying. |\n| `server_error` <br/> Something went wrong on SimpleVox's side. Retry with the same `Idempotency-Key`. |\n| `service_unavailable` <br/> The service is temporarily unavailable. Wait for `Retry-After` when present, then retry. |\n| `api_write_permission_required` <br/> The token has no write permissions. Create a token with the write permission this operation needs. |\n| `api_writes_unavailable` <br/> Writes need the Boost plan. Reads keep working, and you can still check and end calls the token started. |\n| `line_not_found_or_unavailable` <br/> The phone number doesn't exist or the token can't use it. |\n| `invalid_cursor` <br/> The cursor is invalid, or the filters or limit changed since it was issued. Restart without `cursor`. |\n| `maximum_window_31_days` <br/> The time window is longer than 31 days. |\n| `segment_out_of_range` <br/> A requested transcript segment doesn't exist in this transcript version. |\n| `citation_requires_recording_and_version_without_cursor` <br/> Citation reads need `recording_id`, `transcript_version` and `segment_indices`, and no `cursor`. |\n| `source_changed` <br/> The record changed since the `source_version` or `version` you sent. Read it again before citing it. |\n| `event_scope_changed_resync_required` <br/> The token's numbers or permissions changed since the cursor was issued. Restart replay without `cursor`. |\n| `event_cursor_expired_resync_required` <br/> The event cursor is older than 30 days. Restart replay without `cursor`. |\n| `content_deleted` <br/> The recording, transcript or attachment was deleted. |\n| `media_unavailable` <br/> The media isn't available yet, or failed to process. |\n| `idempotency_conflict` <br/> This `Idempotency-Key` was already used with a different request body. Use a new key for a new request. |\n| `contact_operation_conflict` <br/> Another write to this contact is in progress. Retry shortly. |\n| `contact_destination_changed` <br/> A phone number on the contact changed while the request was processed. Read the contact and retry. |\n| `contact_precondition_required` <br/> Updating a contact needs an `If-Match` header with the contact's current revision. |\n| `contact_precondition_invalid` <br/> `If-Match` must be a quoted revision number, such as `\"3\"`. |\n| `contact_revision_conflict` <br/> The contact changed since the revision in `If-Match`. Read it again, then retry with the new `ETag`. |\n| `sms_intent_conflict` <br/> This `Idempotency-Key` was already used to send a different text. |\n| `sms_line_identity_changed` <br/> The sending number changed while the text was being prepared. Nothing was sent. |\n| `sms_cannot_send_to_same_line` <br/> The recipient is the sending number itself. |\n| `sms_recipient_opted_out` <br/> The recipient replied STOP to this number. Nothing was sent. |\n| `sms_registration_unavailable` <br/> The sending number isn't approved for business texting (10DLC) yet. |\n| `sms_entitlement_unavailable` <br/> The workspace's subscription doesn't currently allow texting. |\n| `sms_execution_disabled` <br/> Texting is turned off on this deployment. |\n| `account_restricted` <br/> Texting or calling is paused for the workspace or member. |\n| `messaging_allowance_reached` <br/> The plan's texting allowance is used up. `error.retry_at` is when earlier usage starts to age out. |\n| `messaging_rate_limited` <br/> Too many texts recently. Nothing was sent. Retry after `error.retry_at`. |\n| `calling_allowance_reached` <br/> The plan's calling allowance is used up. `error.retry_at` is when earlier usage starts to age out. |\n| `calling_rate_limited` <br/> Too many call attempts recently. Retry after `error.retry_at`. |\n| `usage_check_unavailable` <br/> Usage couldn't be checked, so the text or call was refused. Retry after `Retry-After`. |\n| `calling_line_unavailable` <br/> The number can't place calls for this token. Pick one from `GET /calling-options`. |\n| `calling_authority_lost` <br/> You lost access to the number while the call was being set up. |\n| `endpoint_unavailable` <br/> The browser phone isn't available. Pick one from `GET /calling-options`. |\n| `endpoint_authority_lost` <br/> The browser phone stopped belonging to you while the call was being set up. |\n| `call_intent_conflict` <br/> This `Idempotency-Key` was already used to start a different call. |\n| `user_already_in_call` <br/> You're already on a call. |\n| `participant_not_accepted` <br/> A participant declined or didn't answer, so the call can't continue. |\n| `cannot_call_same_line` <br/> The recipient is the calling number itself. |\n| `calling_execution_disabled` <br/> Calling is turned off on this deployment. |",
  "enum": [
    "bad_request",
    "not_authorized",
    "forbidden",
    "not_found",
    "method_not_allowed",
    "conflict",
    "gone",
    "precondition_failed",
    "validation_failed",
    "precondition_required",
    "rate_limited",
    "server_error",
    "service_unavailable",
    "api_write_permission_required",
    "api_writes_unavailable",
    "line_not_found_or_unavailable",
    "invalid_cursor",
    "maximum_window_31_days",
    "segment_out_of_range",
    "citation_requires_recording_and_version_without_cursor",
    "source_changed",
    "event_scope_changed_resync_required",
    "event_cursor_expired_resync_required",
    "content_deleted",
    "media_unavailable",
    "idempotency_conflict",
    "contact_operation_conflict",
    "contact_destination_changed",
    "contact_precondition_required",
    "contact_precondition_invalid",
    "contact_revision_conflict",
    "sms_intent_conflict",
    "sms_line_identity_changed",
    "sms_cannot_send_to_same_line",
    "sms_recipient_opted_out",
    "sms_registration_unavailable",
    "sms_entitlement_unavailable",
    "sms_execution_disabled",
    "account_restricted",
    "messaging_allowance_reached",
    "messaging_rate_limited",
    "calling_allowance_reached",
    "calling_rate_limited",
    "usage_check_unavailable",
    "calling_line_unavailable",
    "calling_authority_lost",
    "endpoint_unavailable",
    "endpoint_authority_lost",
    "call_intent_conflict",
    "user_already_in_call",
    "participant_not_accepted",
    "cannot_call_same_line",
    "calling_execution_disabled"
  ],
  "title": "ApiErrorCode"
}
AssistedCall
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The assisted call's ID. Use it to check on the call or end it.",
      "examples": [
        "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
      ]
    },
    "mode": {
      "type": "string",
      "description": "Who's called first. Always `user_first`: SimpleVox rings your device, says who it's calling when you answer, then dials the customer.",
      "enum": [
        "user_first",
        "recipient_first"
      ]
    },
    "setup_state": {
      "type": "string",
      "description": "`pending` while SimpleVox rings both sides, `connected` once you're talking, `cleanup` while it hangs up both sides, `terminal` when the call is over.",
      "enum": [
        "pending",
        "connected",
        "cleanup",
        "terminal"
      ]
    },
    "connection_state": {
      "type": "string",
      "description": "`verified` once the customer's line answered and the two calls were joined. That can be their voicemail.",
      "enum": [
        "not_verified",
        "verified"
      ]
    },
    "outcome": {
      "type": [
        "string",
        "null"
      ],
      "description": "How the call ended, once `setup_state` is `terminal`; `null` before. `completed`: you were connected. `declined`: you answered, then hung up before the customer was dialed. `expired`: setup took too long. `cancelled`: you ended it before anyone was connected. `busy`, `no-answer`, `canceled` and `failed` come from the phone network.",
      "enum": [
        "completed",
        "declined",
        "expired",
        "cancelled",
        "busy",
        "no-answer",
        "canceled",
        "failed",
        null
      ]
    },
    "reason": {
      "type": [
        "string",
        "null"
      ],
      "description": "Why something went wrong, or `null`. `authority_lost`: the token or your access changed during setup. `provider_rejected`: the phone network refused the call. `provider_outcome_unknown`: SimpleVox couldn't confirm a call was placed. `cleanup_unconfirmed`: hanging up isn't confirmed yet and SimpleVox keeps trying. `recording_delivery_pending`: the recording is still being saved. `conference_timing_conflict`: the connection times reported for the call disagreed.",
      "enum": [
        "authority_lost",
        "provider_rejected",
        "provider_outcome_unknown",
        "cleanup_unconfirmed",
        "recording_delivery_pending",
        "conference_timing_conflict",
        null
      ]
    },
    "revision": {
      "type": "integer",
      "description": "Goes up each time the call changes.",
      "examples": [
        2
      ]
    },
    "expires_at": {
      "type": "string",
      "format": "date-time",
      "description": "If you aren't connected by then, SimpleVox stops setting up the call."
    },
    "connected_observed_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "description": "When SimpleVox saw you connected, or `null`."
    },
    "ended_observed_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "description": "When SimpleVox saw the call end, or `null`."
    },
    "cleanup": {
      "type": "string",
      "description": "After you end the call or setup stops: `pending` until both sides have hung up, then `confirmed`. `not_requested` before that.",
      "enum": [
        "not_requested",
        "pending",
        "confirmed"
      ]
    },
    "participants": {
      "type": "array",
      "description": "You and the customer.",
      "items": {
        "$ref": "#/components/schemas/AssistedCallParticipant"
      }
    },
    "communication": {
      "description": "The call this creates in your workspace's history. Use its `id` with the recordings, transcript and summary endpoints.",
      "$ref": "#/components/schemas/CallOrTextLink"
    },
    "limits": {
      "type": "object",
      "description": "How long each step of setup can take.",
      "properties": {
        "ring_seconds": {
          "type": "integer",
          "description": "How long your device rings before giving up. The customer's phone rings for up to 30 seconds, long enough to reach their voicemail.",
          "examples": [
            20
          ]
        },
        "accept_seconds": {
          "type": "integer",
          "description": "Always `0`: nobody presses 1. Answering your device starts the call to the customer.",
          "examples": [
            10
          ]
        },
        "setup_seconds": {
          "type": "integer",
          "description": "How long setup can take in all.",
          "examples": [
            90
          ]
        },
        "max_call_seconds": {
          "type": "integer",
          "description": "The longest a connected call can last.",
          "examples": [
            14400
          ]
        }
      },
      "required": [
        "ring_seconds",
        "accept_seconds",
        "setup_seconds",
        "max_call_seconds"
      ]
    },
    "recording_enabled": {
      "type": "boolean",
      "description": "Whether the call is recorded, as set on the phone number it's placed from."
    },
    "next_step": {
      "type": "string",
      "description": "What to do next, in a sentence."
    }
  },
  "required": [
    "id",
    "mode",
    "setup_state",
    "connection_state",
    "outcome",
    "reason",
    "revision",
    "expires_at",
    "connected_observed_at",
    "ended_observed_at",
    "cleanup",
    "participants",
    "communication",
    "limits",
    "recording_enabled",
    "next_step"
  ],
  "title": "AssistedCall"
}
AssistedCallParticipant
{
  "type": "object",
  "properties": {
    "role": {
      "type": "string",
      "description": "`user` for you, `recipient` for the customer.",
      "enum": [
        "user",
        "recipient"
      ]
    },
    "state": {
      "type": "string",
      "description": "Where this side's phone call is. `prepared`, `submitting` and `queued` come before dialing; `uncertain` means SimpleVox couldn't confirm the call was placed.",
      "enum": [
        "prepared",
        "submitting",
        "queued",
        "initiated",
        "ringing",
        "in-progress",
        "completed",
        "busy",
        "no-answer",
        "failed",
        "canceled",
        "uncertain"
      ]
    },
    "accepted": {
      "type": "boolean",
      "description": "Whether this side answered."
    },
    "joined": {
      "type": "boolean",
      "description": "Whether this side joined the call."
    },
    "left": {
      "type": "boolean",
      "description": "Whether this side has left the call."
    }
  },
  "required": [
    "role",
    "state",
    "accepted",
    "joined",
    "left"
  ],
  "title": "AssistedCallParticipant"
}
CallContent
{
  "type": "object",
  "properties": {
    "state": {
      "type": "string",
      "description": "Whether the call has recorded content. `not_recorded`: nothing was recorded. `pending`: recordings are still being saved. `available`: recordings were saved. `failed` or `unavailable`: they couldn't be saved. `deleted`: someone deleted the call's recorded content.",
      "enum": [
        "available",
        "pending",
        "not_recorded",
        "failed",
        "unavailable",
        "deleted"
      ]
    },
    "generation": {
      "type": "integer",
      "description": "Starts at `1` and goes up when the call's recorded content is deleted, which makes its transcripts and summaries out of date.",
      "examples": [
        1
      ]
    },
    "cleanup_state": {
      "type": "string",
      "description": "After a deletion, whether the stored audio and transcripts have been removed. `not_requested` until someone deletes the content.",
      "enum": [
        "not_requested",
        "pending",
        "processing",
        "reconciling",
        "failed",
        "completed"
      ]
    },
    "recordings": {
      "type": "array",
      "description": "Up to 20 recordings, newest first.",
      "items": {
        "$ref": "#/components/schemas/Recording"
      }
    },
    "recording_count": {
      "type": "integer",
      "description": "How many recordings the call has in total.",
      "examples": [
        1
      ]
    },
    "has_more_recordings": {
      "type": "boolean",
      "description": "Whether there are more recordings after this page."
    }
  },
  "required": [
    "state",
    "generation",
    "cleanup_state",
    "recordings",
    "recording_count",
    "has_more_recordings"
  ],
  "title": "CallContent"
}
CallOrText
{
  "type": "object",
  "properties": {
    "phone_number": {
      "description": "The workspace phone number it happened on.",
      "$ref": "#/components/schemas/PhoneNumberSummary"
    },
    "conversation_id": {
      "type": "string",
      "format": "uuid",
      "description": "The conversation it belongs to: everything between this workspace phone number and the other party."
    },
    "contact": {
      "type": [
        "object",
        "null"
      ],
      "description": "The saved contact it belongs to, or `null` when the other party isn't a saved contact.",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "The contact's ID. Use it with `GET /contacts/{contact}`.",
          "examples": [
            "77777777-7777-4777-8777-777777777777"
          ]
        },
        "name": {
          "type": "string",
          "description": "The contact's name.",
          "examples": [
            "Jordan Example"
          ]
        },
        "portrait_url": {
          "type": [
            "string",
            "null"
          ],
          "format": "uri",
          "description": "A link to the contact's picture, or `null` when it has none."
        }
      },
      "required": [
        "id",
        "name",
        "portrait_url"
      ]
    },
    "other_party_number": {
      "type": [
        "string",
        "null"
      ],
      "description": "The customer's phone number: `from` for incoming calls and texts, `to` for outgoing ones.",
      "examples": [
        "+12025550123"
      ]
    },
    "contact_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The saved contact's ID, or `null` when the other party isn't a saved contact."
    },
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The call's or text's ID.",
      "examples": [
        "88888888-8888-4888-8888-888888888888"
      ]
    },
    "kind": {
      "type": "string",
      "description": "Whether it's a call or a text.",
      "enum": [
        "call",
        "message"
      ]
    },
    "from": {
      "type": [
        "string",
        "null"
      ],
      "description": "The number that placed the call or sent the text, in E.164 format.",
      "examples": [
        "+12025550123"
      ]
    },
    "to": {
      "type": [
        "string",
        "null"
      ],
      "description": "The number that was called or texted, in E.164 format.",
      "examples": [
        "+12025550100"
      ]
    },
    "direction": {
      "type": "string",
      "description": "`inbound` when the customer called or texted you, `outbound` when you called or texted them.",
      "enum": [
        "inbound",
        "outbound"
      ]
    },
    "original_content": {
      "type": [
        "string",
        "null"
      ],
      "description": "The text as it was sent or received. Calls usually have `null`; read what was said from the transcript.",
      "examples": [
        "Can we move Thursday's appointment to 10?"
      ]
    },
    "state": {
      "type": "string",
      "description": "Where the call or text is. Calls use `prepared`, `queued`, `initiated`, `ringing`, `in-progress`, `completed`, `busy`, `no-answer`, `failed` and `canceled`. Texts use the `MessageState` values.",
      "enum": [
        "prepared",
        "queued",
        "initiated",
        "ringing",
        "in-progress",
        "completed",
        "busy",
        "no-answer",
        "submitting",
        "sending",
        "sent",
        "delivered",
        "undelivered",
        "uncertain",
        "receiving",
        "received",
        "failed",
        "canceled"
      ]
    },
    "content_state": {
      "type": "string",
      "description": "Whether the content is ready. For calls: `not_recorded`, `pending`, `available`, `failed`, `unavailable` or `deleted`, as in `call_content.state`. For texts: `available`, or `media_pending`, `media_available` or `unsupported_media` for texts with pictures or files, which `GET /messages/{message}` lists.",
      "enum": [
        "available",
        "pending",
        "unavailable",
        "not_recorded",
        "failed",
        "deleted",
        "media_pending",
        "media_available",
        "unsupported_media"
      ]
    },
    "source_version": {
      "type": "integer",
      "description": "Goes up whenever the call or text changes, for example when a recording is saved or it's linked to a different contact. Send it as `version` to read exactly this revision again.",
      "examples": [
        2
      ]
    },
    "occurred_at": {
      "type": "string",
      "format": "date-time",
      "description": "When the call started or the text was sent or received."
    },
    "call_content": {
      "type": [
        "object",
        "null"
      ],
      "description": "For calls, the recordings and the state of their transcripts and AI summaries; `null` for texts.",
      "properties": {
        "state": {
          "type": "string",
          "description": "Whether the call has recorded content. `not_recorded`: nothing was recorded. `pending`: recordings are still being saved. `available`: recordings were saved. `failed` or `unavailable`: they couldn't be saved. `deleted`: someone deleted the call's recorded content.",
          "enum": [
            "available",
            "pending",
            "not_recorded",
            "failed",
            "unavailable",
            "deleted"
          ]
        },
        "generation": {
          "type": "integer",
          "description": "Starts at `1` and goes up when the call's recorded content is deleted, which makes its transcripts and summaries out of date.",
          "examples": [
            1
          ]
        },
        "cleanup_state": {
          "type": "string",
          "description": "After a deletion, whether the stored audio and transcripts have been removed. `not_requested` until someone deletes the content.",
          "enum": [
            "not_requested",
            "pending",
            "processing",
            "reconciling",
            "failed",
            "completed"
          ]
        },
        "recordings": {
          "type": "array",
          "description": "Up to 20 recordings, newest first.",
          "items": {
            "$ref": "#/components/schemas/Recording"
          }
        },
        "recording_count": {
          "type": "integer",
          "description": "How many recordings the call has in total.",
          "examples": [
            1
          ]
        },
        "has_more_recordings": {
          "type": "boolean",
          "description": "Whether there are more recordings after this page."
        }
      },
      "required": [
        "state",
        "generation",
        "cleanup_state",
        "recordings",
        "recording_count",
        "has_more_recordings"
      ]
    },
    "conversation_status": {
      "description": "The conversation's shared inbox status.",
      "$ref": "#/components/schemas/ConversationStatus"
    }
  },
  "required": [
    "phone_number",
    "conversation_id",
    "contact",
    "other_party_number",
    "contact_id",
    "id",
    "kind",
    "from",
    "to",
    "direction",
    "original_content",
    "state",
    "content_state",
    "source_version",
    "occurred_at",
    "call_content",
    "conversation_status"
  ],
  "title": "CallOrText"
}
CallSummary
{
  "type": "object",
  "properties": {
    "call_id": {
      "type": "string",
      "format": "uuid",
      "description": "The call's ID.",
      "examples": [
        "88888888-8888-4888-8888-888888888888"
      ]
    },
    "source_version": {
      "type": "integer",
      "description": "The call's revision when you read it. Keep it with anything you quote.",
      "examples": [
        7
      ]
    },
    "recording_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The recording that was summarized: the latest one, or the one you asked for with `recording_id`. `null` when the call has none."
    },
    "transcript_version": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The version of the transcript that was summarized, or `null`.",
      "examples": [
        1
      ]
    },
    "analysis_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The summary's ID, or `null` before there's one."
    },
    "state": {
      "type": "string",
      "description": "Whether the summary can be shown. Only `available` returns a summary. `stale_or_restricted`: the transcript, the earlier calls and texts it used, or who can read the call changed since it was made. `audience_unavailable`: SimpleVox couldn't work out who can read the call. `unavailable`: there's no summary. `deleted`: the call's recorded content was deleted.",
      "enum": [
        "available",
        "pending",
        "processing",
        "stale",
        "stale_or_restricted",
        "audience_unavailable",
        "unavailable",
        "failed",
        "unknown",
        "deleted"
      ]
    },
    "freshness": {
      "type": "string",
      "description": "`current` when the summary matches the latest transcript and the earlier calls and texts it used; otherwise `unavailable`, and the summary fields are empty.",
      "enum": [
        "current",
        "unavailable"
      ]
    },
    "summary": {
      "type": [
        "string",
        "null"
      ],
      "description": "A short summary of the call, or `null` unless `freshness` is `current`.",
      "examples": [
        "The customer asked to move Thursday's appointment to 10 AM, and the time was confirmed."
      ]
    },
    "summary_segments": {
      "type": "array",
      "description": "The `index` of each segment of `current_call` the summary is based on.",
      "examples": [
        [
          0
        ]
      ],
      "items": {
        "type": "integer"
      }
    },
    "facts": {
      "type": "array",
      "description": "Facts and decisions from this call, up to 10.",
      "items": {
        "$ref": "#/components/schemas/SummaryFact"
      }
    },
    "historical_facts": {
      "type": "array",
      "description": "Facts the summary took from earlier calls and texts with the same contact, each with where it came from.",
      "items": {
        "$ref": "#/components/schemas/HistoricalFact"
      }
    },
    "uncertainties": {
      "type": "array",
      "description": "Up to three open points the people on the call left unclear, such as a time or an address.",
      "examples": [
        [
          "Which entrance the technician should use."
        ]
      ],
      "items": {
        "type": "string"
      }
    },
    "current_call": {
      "type": "object",
      "description": "The first page of the summarized recording's transcript, as `GET /calls/{call}/transcript` returns it.",
      "properties": {
        "data": {
          "description": "The transcript page.",
          "$ref": "#/components/schemas/Transcript"
        },
        "next_cursor": {
          "type": [
            "string",
            "null"
          ],
          "description": "Pass it as `cursor` to `GET /calls/{call}/transcript` with the same `recording_id` to read the next segments, or `null` when there are none."
        }
      },
      "required": [
        "data",
        "next_cursor"
      ]
    },
    "historical_context": {
      "type": "object",
      "description": "Earlier calls and texts with the same contact that the summary could use.",
      "properties": {
        "entries": {
          "type": "array",
          "description": "Up to 10 calls and texts from the 90 days before this call, newest first.",
          "items": {
            "$ref": "#/components/schemas/SummaryContextEntry"
          }
        },
        "coverage": {
          "type": "object",
          "description": "What `entries` covers, so you can tell a complete history from a partial one.",
          "properties": {
            "scope": {
              "type": "string",
              "description": "Always `lines_shared_by_all_current_call_readers`: only calls and texts on phone numbers that everyone who can read this call can also read, so the summary never shows anyone something they couldn't open.",
              "const": "lines_shared_by_all_current_call_readers"
            },
            "lookback_days": {
              "type": "integer",
              "description": "How many days before the call are included.",
              "examples": [
                90
              ]
            },
            "entry_limit": {
              "type": "integer",
              "description": "The most calls and texts included.",
              "examples": [
                10
              ]
            },
            "segments_per_call": {
              "type": "integer",
              "description": "The most transcript segments read from each earlier call.",
              "examples": [
                100
              ]
            },
            "recording_selection": {
              "type": "string",
              "description": "Always `latest_per_historical_call`: only each earlier call's latest recording is used.",
              "const": "latest_per_historical_call"
            },
            "characters_per_entry": {
              "type": "integer",
              "description": "The most characters of text or transcript included from each call or text.",
              "examples": [
                4000
              ]
            },
            "contact_association": {
              "type": "string",
              "description": "Always `persisted_source_contact_only`: only calls and texts saved with this call's contact count, not ones that just share a phone number.",
              "const": "persisted_source_contact_only"
            },
            "before": {
              "type": "string",
              "format": "date-time",
              "description": "Only calls and texts before this time, when this call started, are included."
            },
            "authorized_total": {
              "type": "integer",
              "description": "How many earlier calls and texts matched.",
              "examples": [
                3
              ]
            },
            "returned": {
              "type": "integer",
              "description": "How many are in `entries`.",
              "examples": [
                3
              ]
            },
            "has_more": {
              "type": "boolean",
              "description": "Whether more matched than `entry_limit` allows."
            },
            "state": {
              "type": "string",
              "description": "`bounded` when earlier calls and texts were looked up, `no_linked_contact` when the call has no saved contact to look them up by.",
              "enum": [
                "bounded",
                "no_linked_contact"
              ]
            }
          },
          "required": [
            "scope",
            "lookback_days",
            "entry_limit",
            "segments_per_call",
            "recording_selection",
            "characters_per_entry",
            "contact_association",
            "before",
            "authorized_total",
            "returned",
            "has_more",
            "state"
          ]
        }
      },
      "required": [
        "entries",
        "coverage"
      ]
    },
    "history_endpoint": {
      "type": [
        "string",
        "null"
      ],
      "format": "uri",
      "description": "The contact's full history: `GET /contacts/{contact}/history` for the call's contact, or `null` when the call has no saved contact.",
      "examples": [
        "https://api.simplevox.app/v1/contacts/77777777-7777-4777-8777-777777777777/history"
      ]
    },
    "audience": {
      "type": "string",
      "description": "Always `all_current_call_line_readers_intersected_with_request_grant`: the summary only uses what everyone who can read this call can see, narrowed to what your token can see.",
      "const": "all_current_call_line_readers_intersected_with_request_grant"
    }
  },
  "required": [
    "call_id",
    "source_version",
    "recording_id",
    "transcript_version",
    "analysis_id",
    "state",
    "freshness",
    "summary",
    "summary_segments",
    "facts",
    "historical_facts",
    "uncertainties",
    "current_call",
    "historical_context",
    "history_endpoint",
    "audience"
  ],
  "title": "CallSummary"
}
CallingDevice
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The device's ID. Send it as `user_endpoint_id` to ring this device instead of the default.",
      "examples": [
        "cccccccc-cccc-4ccc-8ccc-cccccccccccc"
      ]
    },
    "label": {
      "type": "string",
      "description": "The device's name.",
      "examples": [
        "Chrome on MacBook"
      ]
    },
    "type": {
      "type": "string",
      "description": "`browser` for the browser phone, `mobile` for the phone app.",
      "enum": [
        "browser",
        "mobile"
      ]
    },
    "available": {
      "type": "boolean",
      "description": "Always `true`: only devices that can take a call now are listed."
    },
    "default": {
      "type": "boolean",
      "description": "Whether SimpleVox rings this device when you leave out `user_endpoint_id`. The first device listed is the default: a browser with the browser phone on, otherwise the phone app used most recently."
    },
    "acceptance": {
      "type": "string",
      "description": "How you take the call on this device.",
      "examples": [
        "Answer in the browser."
      ]
    },
    "modes": {
      "type": "array",
      "description": "The call orders this device supports. Only `user_first` is available.",
      "items": {
        "type": "string",
        "enum": [
          "user_first",
          "recipient_first"
        ]
      }
    }
  },
  "required": [
    "id",
    "label",
    "type",
    "available",
    "default",
    "acceptance",
    "modes"
  ],
  "title": "CallingDevice"
}
CallingNumber
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The phone number's ID. Send it as `from` when you start a call.",
      "examples": [
        "11111111-1111-4111-8111-111111111111"
      ]
    },
    "label": {
      "type": "string",
      "description": "The name the workspace gave the number.",
      "examples": [
        "Office"
      ]
    },
    "number": {
      "type": "string",
      "description": "The phone number in E.164 format.",
      "examples": [
        "+12025550100"
      ]
    },
    "available": {
      "type": "boolean",
      "description": "Whether you can start a call from this number now."
    }
  },
  "required": [
    "id",
    "label",
    "number",
    "available"
  ],
  "title": "CallingNumber"
}
Contact
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The contact's ID.",
      "examples": [
        "77777777-7777-4777-8777-777777777777"
      ]
    },
    "name": {
      "type": "string",
      "description": "The contact's name.",
      "examples": [
        "Jordan Example"
      ]
    },
    "portrait_url": {
      "type": [
        "string",
        "null"
      ],
      "format": "uri",
      "description": "A link to the contact's picture, or `null` when it has none. It loads without a token."
    },
    "company": {
      "type": [
        "string",
        "null"
      ],
      "description": "The company the contact works for.",
      "examples": [
        "Example Garden Services"
      ]
    },
    "phones": {
      "type": "array",
      "description": "The contact's phone numbers. Calls and texts with these numbers appear in the contact's history.",
      "items": {
        "$ref": "#/components/schemas/ContactPhone"
      }
    },
    "emails": {
      "type": "array",
      "description": "The contact's email addresses.",
      "items": {
        "$ref": "#/components/schemas/ContactEmail"
      }
    },
    "addresses": {
      "type": "array",
      "description": "The contact's postal addresses.",
      "items": {
        "$ref": "#/components/schemas/ContactAddress"
      }
    },
    "notes": {
      "type": [
        "string",
        "null"
      ],
      "description": "Notes about the contact, visible to everyone in the workspace.",
      "examples": [
        "Prefers texts in the morning."
      ]
    },
    "revision": {
      "type": "integer",
      "description": "Goes up each time the contact changes. Send it back in `If-Match` when you update the contact.",
      "examples": [
        3
      ]
    }
  },
  "required": [
    "id",
    "name",
    "portrait_url",
    "company",
    "phones",
    "emails",
    "addresses",
    "notes",
    "revision"
  ],
  "title": "Contact"
}
ContactAddress
{
  "type": "object",
  "properties": {
    "label": {
      "type": "string",
      "description": "A label for the address, such as `Office`.",
      "examples": [
        "Office"
      ]
    },
    "street": {
      "type": "string",
      "description": "The street address, including any unit or suite.",
      "examples": [
        "100 Main Street"
      ]
    },
    "locality": {
      "type": "string",
      "description": "The city or town.",
      "examples": [
        "Austin"
      ]
    },
    "region": {
      "type": [
        "string",
        "null"
      ],
      "description": "The state, province or region, if the address has one.",
      "examples": [
        "TX"
      ]
    },
    "postal_code": {
      "type": [
        "string",
        "null"
      ],
      "description": "The postal or ZIP code, if the address has one.",
      "examples": [
        "78701"
      ]
    },
    "country": {
      "type": "string",
      "description": "The two-letter ISO 3166-1 country code.",
      "examples": [
        "US"
      ]
    }
  },
  "required": [
    "label",
    "street",
    "locality",
    "region",
    "postal_code",
    "country"
  ],
  "title": "ContactAddress"
}
ContactEmail
{
  "type": "object",
  "properties": {
    "label": {
      "type": "string",
      "description": "A label for the address, such as `Work`.",
      "examples": [
        "Work"
      ]
    },
    "value": {
      "type": "string",
      "format": "email",
      "description": "The email address.",
      "examples": [
        "jordan@example.com"
      ]
    }
  },
  "required": [
    "label",
    "value"
  ],
  "title": "ContactEmail"
}
ContactPhone
{
  "type": "object",
  "properties": {
    "label": {
      "type": [
        "string",
        "null"
      ],
      "description": "A label for the number, such as `Mobile`.",
      "examples": [
        "Mobile"
      ]
    },
    "value": {
      "type": "string",
      "description": "The phone number in E.164 format.",
      "examples": [
        "+12025550177"
      ]
    }
  },
  "required": [
    "label",
    "value"
  ],
  "title": "ContactPhone"
}
ContactSummary
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The contact's ID. Use it with `GET /contacts/{contact}`.",
      "examples": [
        "77777777-7777-4777-8777-777777777777"
      ]
    },
    "name": {
      "type": "string",
      "description": "The contact's name.",
      "examples": [
        "Jordan Example"
      ]
    },
    "portrait_url": {
      "type": [
        "string",
        "null"
      ],
      "format": "uri",
      "description": "A link to the contact's picture, or `null` when it has none."
    }
  },
  "required": [
    "id",
    "name",
    "portrait_url"
  ],
  "title": "ContactSummary"
}
ContactWriteRequest
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "The contact's name, 1–150 characters.",
      "examples": [
        "Jordan Example"
      ],
      "minLength": 1,
      "maxLength": 150
    },
    "company": {
      "type": [
        "string",
        "null"
      ],
      "description": "The company the contact works for, up to 150 characters. Send `null` to clear it.",
      "examples": [
        "Example Garden Services"
      ],
      "maxLength": 150
    },
    "notes": {
      "type": [
        "string",
        "null"
      ],
      "description": "Notes about the contact, up to 4,000 characters, visible to everyone in the workspace. Send `null` to clear them.",
      "examples": [
        "Prefers texts in the morning."
      ],
      "maxLength": 4000
    },
    "phones": {
      "type": "array",
      "description": "Up to 10 phone numbers, each different. Calls and texts with these numbers are added to the contact's history.",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "A label for the number, up to 40 characters.",
            "examples": [
              "Mobile"
            ],
            "maxLength": 40
          },
          "value": {
            "type": "string",
            "description": "The phone number in E.164 format.",
            "pattern": "^\\+[1-9][0-9]{6,14}$",
            "examples": [
              "+12025550177"
            ]
          }
        },
        "required": [
          "label",
          "value"
        ]
      },
      "maxItems": 10,
      "uniqueItems": true
    },
    "emails": {
      "type": "array",
      "description": "Up to 10 email addresses, each different.",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "A label for the address, up to 40 characters.",
            "examples": [
              "Work"
            ],
            "maxLength": 40
          },
          "value": {
            "type": "string",
            "format": "email",
            "description": "The email address, up to 254 characters.",
            "examples": [
              "jordan@example.com"
            ],
            "maxLength": 254
          }
        },
        "required": [
          "label",
          "value"
        ]
      },
      "maxItems": 10,
      "uniqueItems": true
    },
    "addresses": {
      "type": "array",
      "description": "Up to 10 postal addresses.",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "A label for the address, up to 40 characters.",
            "examples": [
              "Office"
            ],
            "maxLength": 40
          },
          "street": {
            "type": "string",
            "description": "The street address, including any unit or suite, up to 250 characters.",
            "examples": [
              "100 Main Street"
            ],
            "maxLength": 250
          },
          "locality": {
            "type": "string",
            "description": "The city or town, up to 100 characters.",
            "examples": [
              "Austin"
            ],
            "maxLength": 100
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "description": "The state, province or region, up to 100 characters. Always send it; use `null` when there isn't one.",
            "examples": [
              "TX"
            ],
            "maxLength": 100
          },
          "postal_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "The postal or ZIP code, up to 30 characters. Always send it; use `null` when there isn't one.",
            "examples": [
              "78701"
            ],
            "maxLength": 30
          },
          "country": {
            "type": "string",
            "description": "The two-letter ISO 3166-1 country code, in capitals.",
            "pattern": "^[A-Z]{2}$",
            "examples": [
              "US"
            ]
          }
        },
        "required": [
          "label",
          "street",
          "locality",
          "region",
          "postal_code",
          "country"
        ]
      },
      "maxItems": 10
    }
  },
  "title": "ContactWriteRequest"
}
ConversationStatus
{
  "type": "object",
  "properties": {
    "status": {
      "type": "string",
      "description": "The status your team sees in the inbox.",
      "enum": [
        "open",
        "unread",
        "done"
      ]
    },
    "unread_count": {
      "type": "integer",
      "description": "How many incoming calls and texts arrived since the status was last set. Always `0` unless `status` is `unread`.",
      "examples": [
        0
      ]
    },
    "revision": {
      "type": "integer",
      "description": "Changes whenever someone sets the status by hand; an assistant passes it back when marking the conversation, so it never overwrites a newer choice.",
      "examples": [
        0
      ]
    },
    "activity_through": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "description": "When the newest call or text this response returned in the conversation was recorded, or `null` if it returned none. A status change sent with it leaves anything else unread."
    },
    "status_change": {
      "type": [
        "object",
        "null"
      ],
      "description": "The last time someone set the status by hand, or `null` if nobody has.",
      "properties": {
        "status": {
          "type": "string",
          "description": "The status they set.",
          "enum": [
            "open",
            "unread",
            "done"
          ]
        },
        "by": {
          "type": [
            "string",
            "null"
          ],
          "description": "The name of the person who set it, or `null` if they're no longer in the workspace.",
          "examples": [
            "Alex Rivera"
          ]
        },
        "at": {
          "type": "string",
          "format": "date-time",
          "description": "When they set it."
        }
      },
      "required": [
        "status",
        "by",
        "at"
      ]
    }
  },
  "required": [
    "status",
    "unread_count",
    "revision",
    "activity_through",
    "status_change"
  ],
  "title": "ConversationStatus"
}
CreateContactRequest
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "The contact's name, 1–150 characters.",
      "examples": [
        "Jordan Example"
      ],
      "minLength": 1,
      "maxLength": 150
    },
    "company": {
      "type": [
        "string",
        "null"
      ],
      "description": "The company the contact works for, up to 150 characters. Send `null` to clear it.",
      "examples": [
        "Example Garden Services"
      ],
      "maxLength": 150
    },
    "notes": {
      "type": [
        "string",
        "null"
      ],
      "description": "Notes about the contact, up to 4,000 characters, visible to everyone in the workspace. Send `null` to clear them.",
      "examples": [
        "Prefers texts in the morning."
      ],
      "maxLength": 4000
    },
    "phones": {
      "type": "array",
      "description": "Up to 10 phone numbers, each different. Calls and texts with these numbers are added to the contact's history.",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "A label for the number, up to 40 characters.",
            "examples": [
              "Mobile"
            ],
            "maxLength": 40
          },
          "value": {
            "type": "string",
            "description": "The phone number in E.164 format.",
            "pattern": "^\\+[1-9][0-9]{6,14}$",
            "examples": [
              "+12025550177"
            ]
          }
        },
        "required": [
          "label",
          "value"
        ]
      },
      "maxItems": 10,
      "uniqueItems": true
    },
    "emails": {
      "type": "array",
      "description": "Up to 10 email addresses, each different.",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "A label for the address, up to 40 characters.",
            "examples": [
              "Work"
            ],
            "maxLength": 40
          },
          "value": {
            "type": "string",
            "format": "email",
            "description": "The email address, up to 254 characters.",
            "examples": [
              "jordan@example.com"
            ],
            "maxLength": 254
          }
        },
        "required": [
          "label",
          "value"
        ]
      },
      "maxItems": 10,
      "uniqueItems": true
    },
    "addresses": {
      "type": "array",
      "description": "Up to 10 postal addresses.",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "A label for the address, up to 40 characters.",
            "examples": [
              "Office"
            ],
            "maxLength": 40
          },
          "street": {
            "type": "string",
            "description": "The street address, including any unit or suite, up to 250 characters.",
            "examples": [
              "100 Main Street"
            ],
            "maxLength": 250
          },
          "locality": {
            "type": "string",
            "description": "The city or town, up to 100 characters.",
            "examples": [
              "Austin"
            ],
            "maxLength": 100
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "description": "The state, province or region, up to 100 characters. Always send it; use `null` when there isn't one.",
            "examples": [
              "TX"
            ],
            "maxLength": 100
          },
          "postal_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "The postal or ZIP code, up to 30 characters. Always send it; use `null` when there isn't one.",
            "examples": [
              "78701"
            ],
            "maxLength": 30
          },
          "country": {
            "type": "string",
            "description": "The two-letter ISO 3166-1 country code, in capitals.",
            "pattern": "^[A-Z]{2}$",
            "examples": [
              "US"
            ]
          }
        },
        "required": [
          "label",
          "street",
          "locality",
          "region",
          "postal_code",
          "country"
        ]
      },
      "maxItems": 10
    }
  },
  "required": [
    "name"
  ],
  "title": "CreateContactRequest"
}
Error
{
  "type": "object",
  "examples": [
    {
      "error": {
        "code": "not_found",
        "message": "Not found, or not available to this token."
      }
    }
  ],
  "properties": {
    "error": {
      "type": "object",
      "description": "What went wrong.",
      "properties": {
        "code": {
          "$ref": "#/components/schemas/ApiErrorCode"
        },
        "message": {
          "type": "string",
          "description": "A short explanation for people. It may change; branch on `code` instead."
        },
        "fields": {
          "type": "object",
          "description": "Messages for each invalid field. Present only with `validation_failed`.",
          "additionalProperties": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "retry_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "When usage allows another attempt. Present only with usage-limit codes."
        }
      },
      "required": [
        "code",
        "message"
      ]
    }
  },
  "required": [
    "error"
  ],
  "title": "Error"
}
Event
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The event's ID. Events can be delivered more than once, so skip IDs you've already handled.",
      "examples": [
        "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
      ]
    },
    "type": {
      "description": "What changed.\n| |\n|---|\n| `message.received` <br/> A text arrived. |\n| `message.status_changed` <br/> A text's state or error changed, or a text you sent was saved. |\n| `call.status_changed` <br/> A call's state, connection or outcome changed. |\n| `call.connected` <br/> SimpleVox confirmed that the people on a call were connected. |\n| `communication.content_changed` <br/> A call's or text's content changed, such as a recording or a picture, so its `source_version` went up. |\n| `call.transcript_ready` <br/> A transcript finished and can be read. |\n| `call.transcript_failed` <br/> A transcript couldn't be made. |\n| `call.setup_started` <br/> SimpleVox started setting up a call you requested. |\n| `call.recipient_ready` <br/> The customer answered and pressed 1 on a call you requested. |\n| `call.setup_cancelled` <br/> A call you requested was canceled before anyone was connected. |\n| `call.setup_failed` <br/> A call you requested ended before anyone was connected, for example because nobody answered. |\n| `call.ended` <br/> A connected call you requested ended. |",
      "$ref": "#/components/schemas/EventType"
    },
    "source": {
      "type": "object",
      "description": "The call or text that changed. Read it with `GET /sources/{kind}/{source}`.",
      "properties": {
        "kind": {
          "type": "string",
          "description": "Whether it's a call or a text.",
          "enum": [
            "call",
            "message"
          ]
        },
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "The call's or text's ID."
        },
        "version": {
          "type": "integer",
          "description": "Its `source_version` after the change.",
          "examples": [
            1
          ]
        }
      },
      "required": [
        "kind",
        "id",
        "version"
      ]
    },
    "revision": {
      "type": "integer",
      "description": "Goes up with each change to the same call or text, so you can ignore an event older than one you've already applied.",
      "examples": [
        3
      ]
    },
    "occurred_at": {
      "type": "string",
      "format": "date-time",
      "description": "When the change happened."
    },
    "facts": {
      "type": "object",
      "description": "The new state. Which fields are present depends on `type`.",
      "properties": {
        "state": {
          "type": "string",
          "description": "The new state. For events from assisted calls, which have `session_id`, the assisted call's `setup_state`. For `call.transcript_*`, the transcript's state. Otherwise the call's or text's `state`.",
          "enum": [
            "prepared",
            "queued",
            "initiated",
            "ringing",
            "in-progress",
            "completed",
            "busy",
            "no-answer",
            "submitting",
            "sending",
            "sent",
            "delivered",
            "undelivered",
            "uncertain",
            "receiving",
            "received",
            "failed",
            "canceled",
            "available",
            "pending",
            "connected",
            "cleanup",
            "terminal"
          ]
        },
        "outcome": {
          "type": [
            "string",
            "null"
          ],
          "description": "For events from assisted calls, the assisted call's `outcome`; `null` until it ends.",
          "enum": [
            "completed",
            "declined",
            "expired",
            "cancelled",
            "busy",
            "no-answer",
            "canceled",
            "failed",
            null
          ]
        },
        "session_id": {
          "type": "string",
          "format": "uuid",
          "description": "For events from assisted calls, the assisted call's ID. Read it with `GET /assisted-calls/{assistedCall}`."
        },
        "content_state": {
          "type": "string",
          "description": "For call and text changes, the new `content_state`.",
          "enum": [
            "available",
            "pending",
            "unavailable",
            "not_recorded",
            "failed",
            "deleted",
            "media_pending",
            "media_available",
            "unsupported_media"
          ]
        },
        "transcript_id": {
          "type": "string",
          "format": "uuid",
          "description": "For `call.transcript_*` events, the transcript's ID."
        },
        "transcript_version": {
          "type": "integer",
          "description": "For `call.transcript_*` events, the transcript's version.",
          "examples": [
            1
          ]
        }
      }
    }
  },
  "required": [
    "id",
    "type",
    "source",
    "revision",
    "occurred_at",
    "facts"
  ],
  "title": "Event"
}
EventType
{
  "type": "string",
  "description": "What changed. Events say that something changed, not the new content: read the call or text again to get it.\n| |\n|---|\n| `message.received` <br/> A text arrived. |\n| `message.status_changed` <br/> A text's state or error changed, or a text you sent was saved. |\n| `call.status_changed` <br/> A call's state, connection or outcome changed. |\n| `call.connected` <br/> SimpleVox confirmed that the people on a call were connected. |\n| `communication.content_changed` <br/> A call's or text's content changed, such as a recording or a picture, so its `source_version` went up. |\n| `call.transcript_ready` <br/> A transcript finished and can be read. |\n| `call.transcript_failed` <br/> A transcript couldn't be made. |\n| `call.setup_started` <br/> SimpleVox started setting up a call you requested. |\n| `call.recipient_ready` <br/> The customer answered and pressed 1 on a call you requested. |\n| `call.setup_cancelled` <br/> A call you requested was canceled before anyone was connected. |\n| `call.setup_failed` <br/> A call you requested ended before anyone was connected, for example because nobody answered. |\n| `call.ended` <br/> A connected call you requested ended. |",
  "enum": [
    "message.received",
    "message.status_changed",
    "call.status_changed",
    "call.connected",
    "communication.content_changed",
    "call.transcript_ready",
    "call.transcript_failed",
    "call.setup_started",
    "call.recipient_ready",
    "call.setup_cancelled",
    "call.setup_failed",
    "call.ended"
  ],
  "title": "EventType"
}
HistoricalFact
{
  "type": "object",
  "properties": {
    "text": {
      "type": "string",
      "description": "The fact, in a sentence.",
      "examples": [
        "The customer booked a Thursday appointment last week."
      ]
    },
    "citation": {
      "type": "object",
      "description": "The earlier call or text the fact comes from.",
      "properties": {
        "kind": {
          "type": "string",
          "description": "Whether it's a call or a text.",
          "enum": [
            "call",
            "message"
          ]
        },
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "The call's or text's ID."
        },
        "source_version": {
          "type": "integer",
          "description": "The revision the fact was taken from.",
          "examples": [
            3
          ]
        },
        "recording_id": {
          "type": [
            "string",
            "null"
          ],
          "format": "uuid",
          "description": "For a call, the recording the fact was taken from; `null` for a text or a call without a recording."
        },
        "transcript_version": {
          "type": [
            "integer",
            "null"
          ],
          "description": "The version of that recording's transcript, or `null`.",
          "examples": [
            1
          ]
        },
        "segment_indices": {
          "type": "array",
          "description": "The transcript segments the fact is based on. Empty when it comes from a text.",
          "examples": [
            [
              4
            ]
          ],
          "items": {
            "type": "integer"
          }
        },
        "url": {
          "type": "string",
          "format": "uri",
          "description": "A link that opens the call or text in SimpleVox for people signed in to the workspace."
        }
      },
      "required": [
        "kind",
        "id",
        "source_version",
        "recording_id",
        "transcript_version",
        "segment_indices",
        "url"
      ]
    }
  },
  "required": [
    "text",
    "citation"
  ],
  "title": "HistoricalFact"
}
HistoryCoverage
{
  "type": "object",
  "properties": {
    "scope": {
      "type": "string",
      "description": "Always `authorized_lines_only`: only calls and texts on the token's phone numbers are included.",
      "const": "authorized_lines_only"
    },
    "filters": {
      "type": "object",
      "description": "The filters this page was read with.",
      "properties": {
        "contact_id": {
          "type": [
            "string",
            "null"
          ],
          "format": "uuid",
          "description": "The contact, for a contact's history; `null` for activity."
        },
        "phone_number_id": {
          "type": [
            "string",
            "null"
          ],
          "format": "uuid",
          "description": "The `phone_number_id` filter, or `null`."
        },
        "channel": {
          "type": [
            "string",
            "null"
          ],
          "description": "The `channel` filter, or `null` for both calls and texts.",
          "enum": [
            "call",
            "message",
            null
          ]
        },
        "occurred_from": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "The start of the window, in UTC, or `null`."
        },
        "occurred_until": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "The end of the window, in UTC, or `null`."
        },
        "limit": {
          "type": "integer",
          "description": "The page size.",
          "examples": [
            50
          ]
        },
        "call_filter": {
          "type": [
            "string",
            "null"
          ],
          "description": "The `call_filter` filter, or `null`.",
          "enum": [
            "any",
            "missed",
            "voicemail",
            "unresponded",
            "incoming",
            "outgoing",
            null
          ]
        }
      },
      "required": [
        "contact_id",
        "phone_number_id",
        "channel",
        "occurred_from",
        "occurred_until",
        "limit",
        "call_filter"
      ]
    },
    "snapshot_at": {
      "type": "string",
      "format": "date-time",
      "description": "When the first page was read. Later pages of the same list leave out anything newer, so start again without `cursor` to see it."
    },
    "authorized_total": {
      "type": "integer",
      "description": "How many calls and texts match across all pages.",
      "examples": [
        12
      ]
    },
    "returned": {
      "type": "integer",
      "description": "How many are on this page.",
      "examples": [
        12
      ]
    },
    "has_more": {
      "type": "boolean",
      "description": "Whether there's another page. Fetch it with `next_cursor`."
    },
    "fresh_query_required_for_new_records": {
      "type": "boolean",
      "description": "Always `true`: to see calls and texts newer than `snapshot_at`, start again without `cursor`."
    },
    "content": {
      "type": "string",
      "description": "Always `original_sources_and_current_source_linked_analysis`: entries carry the original calls and texts, plus AI summaries only when they match the current recording.",
      "const": "original_sources_and_current_source_linked_analysis"
    }
  },
  "required": [
    "scope",
    "filters",
    "snapshot_at",
    "authorized_total",
    "returned",
    "has_more",
    "fresh_query_required_for_new_records",
    "content"
  ],
  "title": "HistoryCoverage"
}
Message
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The text's ID. Use it with `GET /messages/{message}` and to match events.",
      "examples": [
        "44444444-4444-4444-8444-444444444444"
      ]
    },
    "conversation_id": {
      "type": "string",
      "format": "uuid",
      "description": "The conversation the text belongs to."
    },
    "phone_number_id": {
      "type": "string",
      "format": "uuid",
      "description": "The workspace phone number the text was sent from or received on."
    },
    "operation_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "Identifies the request that sent the text. SimpleVox derives it from the `Idempotency-Key`, so retrying with the same key returns this text. `null` for incoming texts."
    },
    "state": {
      "description": "Where the text is in sending or receiving.\n| |\n|---|\n| `prepared` <br/> SimpleVox saved the text and hasn't handed it to the carrier yet. |\n| `submitting` <br/> SimpleVox is handing the text to the carrier. |\n| `queued` <br/> The carrier accepted the text and queued it. |\n| `sending` <br/> The carrier is sending the text. |\n| `sent` <br/> The carrier sent the text and is waiting for the recipient's carrier to confirm delivery. |\n| `delivered` <br/> The recipient's carrier confirmed delivery. |\n| `undelivered` <br/> The carrier couldn't deliver the text. |\n| `failed` <br/> The text couldn't be sent. Check `error_code`. |\n| `canceled` <br/> The text was canceled before it was sent. |\n| `uncertain` <br/> SimpleVox couldn't confirm whether the carrier accepted the text. It keeps checking; don't send it again. |\n| `receiving` <br/> An incoming text is still arriving. |\n| `received` <br/> An incoming text arrived. |",
      "$ref": "#/components/schemas/MessageState"
    },
    "content_state": {
      "type": "string",
      "description": "Whether the text's pictures and files are ready. `available`: the text has none. `media_pending`: an incoming text's attachments are still being saved. `media_available`: they're saved, and `media.attachments` lists them when you retrieve the text. `unsupported_media`: SimpleVox couldn't save them; the text itself is still readable.",
      "enum": [
        "available",
        "media_pending",
        "media_available",
        "unsupported_media"
      ]
    },
    "from": {
      "type": [
        "string",
        "null"
      ],
      "description": "The number the text came from, in E.164 format.",
      "examples": [
        "+12025550100"
      ]
    },
    "to": {
      "type": [
        "string",
        "null"
      ],
      "description": "The number the text went to, in E.164 format.",
      "examples": [
        "+12025550123"
      ]
    },
    "body": {
      "type": [
        "string",
        "null"
      ],
      "description": "The text as it was sent or received.",
      "examples": [
        "Our appointment is confirmed for Thursday."
      ]
    },
    "error_code": {
      "type": [
        "string",
        "null"
      ],
      "description": "Why sending failed, or `null`. Either `provider_outcome_unknown` (SimpleVox couldn't confirm the result), `provider_status_conflict` (the carrier reported conflicting states) or the carrier's numeric error code, such as `30007`. The set of carrier codes is open.",
      "examples": [
        "30007"
      ]
    },
    "media_count": {
      "type": "integer",
      "description": "How many pictures or files the text had.",
      "examples": [
        0
      ]
    },
    "provider_segments": {
      "type": [
        "integer",
        "null"
      ],
      "description": "How many SMS segments the carrier split the text into, once it reports them; `null` until then.",
      "examples": [
        1
      ]
    },
    "media": {
      "description": "The text's pictures and files, with signed links to download them. Included when you retrieve a text; the response to sending one leaves it out, since texts sent through the API have none.",
      "$ref": "#/components/schemas/MessageMedia"
    }
  },
  "required": [
    "id",
    "conversation_id",
    "phone_number_id",
    "operation_id",
    "state",
    "content_state",
    "from",
    "to",
    "body",
    "error_code",
    "media_count",
    "provider_segments"
  ],
  "title": "Message"
}
MessageAttachment
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The attachment's ID. `GET /messages/{message}/attachments/{attachment}` redirects to a fresh link for it.",
      "examples": [
        "dddddddd-dddd-4ddd-8ddd-dddddddddddd"
      ]
    },
    "mime": {
      "type": "string",
      "description": "The file's content type, such as `image/jpeg`, `application/pdf` or `video/mp4`. Downloads come back with this `Content-Type`.",
      "examples": [
        "image/jpeg"
      ]
    },
    "bytes": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The file's size in bytes, or `null` when it isn't known.",
      "examples": [
        248113
      ]
    },
    "download_url": {
      "type": "string",
      "format": "uri",
      "description": "A signed link that downloads the file. It works without your token until `expires_at`, so don't log or share it. Read the text again for a fresh link."
    },
    "preview_url": {
      "type": [
        "string",
        "null"
      ],
      "format": "uri",
      "description": "A signed link that displays a JPEG, PNG, GIF or WebP image instead of downloading it, until `expires_at`. `null` for other files."
    },
    "expires_at": {
      "type": "string",
      "format": "date-time",
      "description": "When `download_url` and `preview_url` stop working, about 15 minutes after the read.",
      "examples": [
        "2026-10-01T15:19:05+00:00"
      ]
    }
  },
  "required": [
    "id",
    "mime",
    "bytes",
    "download_url",
    "preview_url",
    "expires_at"
  ],
  "title": "MessageAttachment"
}
MessageMedia
{
  "type": "object",
  "properties": {
    "state": {
      "type": "string",
      "description": "Whether the pictures and files are ready to download. `none`: the text had none. `pending`: SimpleVox is still saving them. `failed`: saving them failed and SimpleVox will try again. `available`: they're saved and listed in `attachments`. `unsupported`: SimpleVox can't save them, for example because a file is too large or of a type it doesn't accept. `exhausted`: SimpleVox gave up after repeated failures. `unavailable`: SimpleVox doesn't have them. The text itself is readable in every state.",
      "enum": [
        "none",
        "pending",
        "failed",
        "available",
        "unsupported",
        "exhausted",
        "unavailable"
      ]
    },
    "count": {
      "type": "integer",
      "description": "How many pictures or files the text had, including any SimpleVox couldn't save. The same as the text's `media_count`.",
      "examples": [
        1
      ]
    },
    "error_code": {
      "type": [
        "string",
        "null"
      ],
      "description": "Why SimpleVox couldn't save them, such as `media_too_large` or `media_type_mismatch`, or `null`. The set of codes is open.",
      "examples": [
        "media_too_large"
      ]
    },
    "attachments": {
      "type": "array",
      "description": "The saved pictures and files. Empty unless `state` is `available`.",
      "items": {
        "$ref": "#/components/schemas/MessageAttachment"
      }
    }
  },
  "required": [
    "state",
    "count",
    "error_code",
    "attachments"
  ],
  "title": "MessageMedia"
}
MessageState
{
  "type": "string",
  "description": "Where a text is in sending or receiving. `delivered` is the only state that confirms the recipient's carrier accepted a text you sent; `failed`, `undelivered` and `canceled` mean it wasn't delivered.\n| |\n|---|\n| `prepared` <br/> SimpleVox saved the text and hasn't handed it to the carrier yet. |\n| `submitting` <br/> SimpleVox is handing the text to the carrier. |\n| `queued` <br/> The carrier accepted the text and queued it. |\n| `sending` <br/> The carrier is sending the text. |\n| `sent` <br/> The carrier sent the text and is waiting for the recipient's carrier to confirm delivery. |\n| `delivered` <br/> The recipient's carrier confirmed delivery. |\n| `undelivered` <br/> The carrier couldn't deliver the text. |\n| `failed` <br/> The text couldn't be sent. Check `error_code`. |\n| `canceled` <br/> The text was canceled before it was sent. |\n| `uncertain` <br/> SimpleVox couldn't confirm whether the carrier accepted the text. It keeps checking; don't send it again. |\n| `receiving` <br/> An incoming text is still arriving. |\n| `received` <br/> An incoming text arrived. |",
  "enum": [
    "prepared",
    "submitting",
    "queued",
    "sending",
    "sent",
    "delivered",
    "undelivered",
    "failed",
    "canceled",
    "uncertain",
    "receiving",
    "received"
  ],
  "title": "MessageState"
}
PhoneNumberSummary
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The phone number's ID. Use it as `phone_number_id` to filter activity or send a text from this number.",
      "examples": [
        "11111111-1111-4111-8111-111111111111"
      ]
    },
    "label": {
      "type": "string",
      "description": "The name the workspace gave the number.",
      "examples": [
        "Office"
      ]
    }
  },
  "required": [
    "id",
    "label"
  ],
  "title": "PhoneNumberSummary"
}
PublicSendTextMessageRequest
{
  "type": "object",
  "properties": {
    "phone_number_id": {
      "type": "string",
      "format": "uuid",
      "description": "The workspace phone number to send from: the `id` of one of the token's phone numbers. It must be approved for business texting.",
      "examples": [
        "11111111-1111-4111-8111-111111111111"
      ]
    },
    "to": {
      "type": "string",
      "description": "The number to text, in E.164 format.",
      "pattern": "^\\+[1-9][0-9]{6,14}$",
      "examples": [
        "+12025550123"
      ]
    },
    "body": {
      "type": "string",
      "description": "What to send, up to 1,600 characters. Longer texts are split into several SMS segments.",
      "examples": [
        "Our appointment is confirmed for Thursday."
      ],
      "maxLength": 1600
    }
  },
  "required": [
    "phone_number_id",
    "to",
    "body"
  ],
  "title": "PublicSendTextMessageRequest"
}
PublicStartCallRequest
{
  "type": "object",
  "properties": {
    "from": {
      "type": [
        "string",
        "null"
      ],
      "description": "The workspace phone number to call from: its `id` from `GET /calling-options`, or the number itself in E.164 format. You can leave it out when only one number is available.",
      "examples": [
        "11111111-1111-4111-8111-111111111111"
      ],
      "maxLength": 64
    },
    "user_endpoint_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The device to ring for your side of the call, from `calling_devices` in `GET /calling-options`. Leave it out to ring your default device.",
      "examples": [
        "cccccccc-cccc-4ccc-8ccc-cccccccccccc"
      ]
    },
    "to": {
      "type": "string",
      "description": "The customer's number, in E.164 format.",
      "pattern": "^\\+[1-9][0-9]{6,14}$",
      "examples": [
        "+12025550123"
      ]
    },
    "mode": {
      "type": "string",
      "description": "Who's called first. Only `user_first` is available, and it's the default: SimpleVox rings your device, says who it's calling when you answer, then dials the customer. `recipient_first` is refused with `422`.",
      "enum": [
        "user_first",
        "recipient_first"
      ]
    },
    "contact_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The saved contact you're calling. It's used only if that contact has the number `to`; otherwise the call is linked to the contact that has that number, like any other call.",
      "examples": [
        "77777777-7777-4777-8777-777777777777"
      ]
    },
    "purpose": {
      "type": [
        "string",
        "null"
      ],
      "description": "A short note, up to 500 characters, that SimpleVox reads to you when you answer, before it dials the customer. The customer never hears it.",
      "examples": [
        "Confirm Thursday's appointment."
      ],
      "maxLength": 500
    }
  },
  "required": [
    "to"
  ],
  "title": "PublicStartCallRequest"
}
Recording
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The recording's ID. Send it as `recording_id` to read this recording's transcript or summary.",
      "examples": [
        "22222222-2222-4222-8222-222222222222"
      ]
    },
    "kind": {
      "type": "string",
      "description": "`live` for a recorded conversation, `voicemail` for a message the caller left.",
      "enum": [
        "live",
        "voicemail"
      ]
    },
    "state": {
      "type": "string",
      "description": "Whether the audio can be played. Only `available` recordings can be played; `pending`, `queued` and `downloading` are still being saved.",
      "enum": [
        "pending",
        "queued",
        "downloading",
        "available",
        "not_recorded",
        "failed",
        "deleted"
      ]
    },
    "version": {
      "type": "integer",
      "description": "The recording's version. Its playback link reports the same number.",
      "examples": [
        1
      ]
    },
    "duration_seconds": {
      "type": [
        "integer",
        "null"
      ],
      "description": "How long the recording is, in seconds, once known.",
      "examples": [
        94
      ]
    },
    "channels": {
      "type": [
        "integer",
        "null"
      ],
      "description": "How many audio channels the recording has: `2` when each side of the call is recorded separately, `1` when they're mixed. `null` until known.",
      "examples": [
        2
      ]
    },
    "playback_url_endpoint": {
      "type": [
        "string",
        "null"
      ],
      "format": "uri",
      "description": "Call this with your token to get a short-lived link you can play, or `null` when the recording can't be played.",
      "examples": [
        "https://api.simplevox.app/v1/recordings/22222222-2222-4222-8222-222222222222/playback"
      ]
    },
    "transcript_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The ID of the recording's latest transcript, or `null` before one exists."
    },
    "transcript_version": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The latest transcript's version, or `null` before one exists. Send it as `transcript_version` to quote exact segments.",
      "examples": [
        1
      ]
    },
    "transcript_state": {
      "type": "string",
      "description": "Where the transcript is. Only `available` transcripts can be read; `not_recorded` means there's no audio to transcribe.",
      "enum": [
        "pending",
        "preparing",
        "processing",
        "available",
        "failed",
        "unknown",
        "not_recorded",
        "deleted"
      ]
    },
    "analysis_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The ID of the AI summary of the latest transcript, or `null` before one exists."
    },
    "analysis_state": {
      "type": "string",
      "description": "Where the AI summary is. `stale` means it was made from an older transcript or recording and isn't shown.",
      "enum": [
        "pending",
        "processing",
        "available",
        "stale",
        "failed",
        "unknown",
        "unavailable",
        "not_recorded",
        "deleted"
      ]
    },
    "summary": {
      "type": [
        "string",
        "null"
      ],
      "description": "The AI summary of this recording, or `null` unless `freshness` is `current`.",
      "examples": [
        "The customer asked to move Thursday's appointment to 10 AM."
      ]
    },
    "summary_segments": {
      "type": "array",
      "description": "The `index` of each transcript segment the summary is based on.",
      "examples": [
        [
          0,
          1
        ]
      ],
      "items": {
        "type": "integer"
      }
    },
    "facts": {
      "type": "array",
      "description": "Facts the AI summary found in this recording. Empty unless `freshness` is `current`.",
      "items": {
        "$ref": "#/components/schemas/SummaryFact"
      }
    },
    "uncertainties": {
      "type": "array",
      "description": "Things the AI couldn't tell from the recording. Empty unless `freshness` is `current`.",
      "examples": [
        [
          "Whether the customer confirmed the new time."
        ]
      ],
      "items": {
        "type": "string"
      }
    },
    "freshness": {
      "type": "string",
      "description": "`current` when `summary`, `facts` and `uncertainties` come from this recording's latest transcript, `unavailable` when they're empty, `deleted` when the call's recorded content was deleted.",
      "enum": [
        "current",
        "unavailable",
        "deleted"
      ]
    },
    "source_url": {
      "type": "string",
      "format": "uri",
      "description": "A link that opens the call in SimpleVox for people signed in to the workspace."
    }
  },
  "required": [
    "id",
    "kind",
    "state",
    "version",
    "duration_seconds",
    "channels",
    "playback_url_endpoint",
    "transcript_id",
    "transcript_version",
    "transcript_state",
    "analysis_id",
    "analysis_state",
    "summary",
    "summary_segments",
    "facts",
    "uncertainties",
    "freshness",
    "source_url"
  ],
  "title": "Recording"
}
SummaryContextEntry
{
  "type": "object",
  "properties": {
    "kind": {
      "type": "string",
      "description": "Whether it's a call or a text.",
      "enum": [
        "call",
        "message"
      ]
    },
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "The call's or text's ID."
    },
    "source_version": {
      "type": "integer",
      "description": "Its revision when the summary was read.",
      "examples": [
        3
      ]
    },
    "phone_number_id": {
      "type": "string",
      "format": "uuid",
      "description": "The workspace phone number it happened on."
    },
    "conversation_id": {
      "type": "string",
      "format": "uuid",
      "description": "The conversation it belongs to."
    },
    "contact_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The saved contact it belongs to."
    },
    "occurred_at": {
      "type": "string",
      "format": "date-time",
      "description": "When it happened."
    },
    "state": {
      "type": "string",
      "description": "For a text, its `content_state`. For a call with recordings, the state of its latest transcript, or of the recording before there's a transcript; `stale` means the transcript is older than the recording. For a call without recordings, its `content_state`.",
      "enum": [
        "available",
        "pending",
        "queued",
        "downloading",
        "preparing",
        "processing",
        "stale",
        "failed",
        "unknown",
        "unavailable",
        "not_recorded",
        "deleted",
        "media_pending",
        "media_available",
        "unsupported_media"
      ]
    },
    "original_content": {
      "type": [
        "string",
        "null"
      ],
      "description": "A text's body, cut to 4,000 characters, or `null` for calls and texts whose content isn't available.",
      "examples": [
        "See you Thursday at 9."
      ]
    },
    "recording_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "For a call, its latest recording; otherwise `null`."
    },
    "transcript_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "That recording's latest transcript, or `null`."
    },
    "transcript_version": {
      "type": [
        "integer",
        "null"
      ],
      "description": "That transcript's version, or `null`.",
      "examples": [
        1
      ]
    },
    "segments": {
      "type": "array",
      "description": "The transcript's first segments, up to 4,000 characters in all. Empty for texts.",
      "items": {
        "$ref": "#/components/schemas/TranscriptSegment"
      }
    },
    "truncated": {
      "type": "boolean",
      "description": "Whether the text or transcript was cut short, or the call has more than one recording."
    },
    "source_url": {
      "type": "string",
      "format": "uri",
      "description": "A link that opens it in SimpleVox for people signed in to the workspace."
    }
  },
  "required": [
    "kind",
    "id",
    "source_version",
    "phone_number_id",
    "conversation_id",
    "contact_id",
    "occurred_at",
    "state",
    "original_content",
    "recording_id",
    "transcript_id",
    "transcript_version",
    "segments",
    "truncated",
    "source_url"
  ],
  "title": "SummaryContextEntry"
}
SummaryFact
{
  "type": "object",
  "properties": {
    "text": {
      "type": "string",
      "description": "The fact, in a sentence.",
      "examples": [
        "The customer wants to move the appointment to Thursday at 10 AM."
      ]
    },
    "segments": {
      "type": "array",
      "description": "The `index` of each transcript segment that supports the fact.",
      "examples": [
        [
          0,
          1
        ]
      ],
      "items": {
        "type": "integer"
      }
    }
  },
  "required": [
    "text",
    "segments"
  ],
  "title": "SummaryFact"
}
Transcript
{
  "type": "object",
  "properties": {
    "state": {
      "type": "string",
      "description": "Whether the transcript can be read. Segments are returned only when it's `available`. `stale` means the recording changed after it was transcribed. While there's no transcript yet, this is the recording's state, such as `queued` or `downloading`.",
      "enum": [
        "pending",
        "queued",
        "downloading",
        "preparing",
        "processing",
        "available",
        "stale",
        "failed",
        "unknown",
        "not_recorded",
        "deleted"
      ]
    },
    "recording_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The recording this transcript is of, or `null` when the call has none.",
      "examples": [
        "22222222-2222-4222-8222-222222222222"
      ]
    },
    "transcript_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The transcript's ID, or `null` before there's one."
    },
    "version": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The transcript's version. A new version replaces the old one when the recording is transcribed again. Send it as `transcript_version` to quote exact segments.",
      "examples": [
        1
      ]
    },
    "source_version": {
      "type": "integer",
      "description": "The call's revision when you read it.",
      "examples": [
        7
      ]
    },
    "external_contact": {
      "type": [
        "object",
        "null"
      ],
      "description": "The saved contact on the other end of the call, or `null` when there isn't one or the transcript isn't available.",
      "properties": {
        "name": {
          "type": "string",
          "description": "The contact's name.",
          "examples": [
            "Jordan Example"
          ]
        },
        "role": {
          "type": "string",
          "description": "`caller` when the contact called you, `called_party` when you called them.",
          "enum": [
            "caller",
            "called_party"
          ]
        },
        "portrait_url": {
          "type": [
            "string",
            "null"
          ],
          "format": "uri",
          "description": "A link to the contact's picture, or `null` when it has none."
        }
      },
      "required": [
        "name",
        "role",
        "portrait_url"
      ]
    },
    "speaker_identity": {
      "type": "string",
      "description": "Always `audio_labels_not_verified_people`: segment speakers are labels from the audio, not verified people.",
      "const": "audio_labels_not_verified_people"
    },
    "error_code": {
      "type": [
        "string",
        "null"
      ],
      "description": "Why transcription failed, or `null`.",
      "enum": [
        "diarization_unsupported",
        "transcription_input_too_large",
        "multichannel_unsupported",
        "source_read_failed",
        "generation_authority_unavailable",
        "source_preparation_failed",
        "provider_submission_rejected",
        "invalid_transcript",
        "provider_outcome_unknown",
        "invalid_or_failed_provider_transcript",
        "provider_retrieval_unavailable",
        "worker_outcome_unknown",
        null
      ]
    },
    "provenance": {
      "type": [
        "object",
        "null"
      ],
      "description": "How the speakers were separated, or `null` when the transcript isn't available.",
      "properties": {
        "mode": {
          "type": "string",
          "description": "`multichannel` when each side of the call was recorded on its own channel, `diarized` when speakers were told apart from mixed audio.",
          "enum": [
            "multichannel",
            "diarized"
          ]
        },
        "normalization_version": {
          "type": "integer",
          "description": "The version of the rules used to merge words into segments.",
          "examples": [
            1
          ]
        },
        "recording_channels": {
          "type": [
            "integer",
            "null"
          ],
          "description": "How many channels the recording has, or `null` when unknown.",
          "examples": [
            2
          ]
        },
        "identity_verified": {
          "type": "boolean",
          "description": "Always `false`: SimpleVox doesn't verify who spoke."
        },
        "channels": {
          "type": "array",
          "description": "Which side of the call each channel captured. Empty unless it's a two-channel recording of a conversation.",
          "items": {
            "$ref": "#/components/schemas/TranscriptChannel"
          }
        }
      },
      "required": [
        "mode",
        "normalization_version",
        "recording_channels",
        "identity_verified",
        "channels"
      ]
    },
    "segments": {
      "type": "array",
      "description": "Up to 100 segments, in order, or only the ones you asked for with `segment_indices`. Empty unless `state` is `available`.",
      "items": {
        "$ref": "#/components/schemas/TranscriptSegment"
      }
    },
    "total_segments": {
      "type": "integer",
      "description": "How many segments the whole transcript has. `0` unless `state` is `available`.",
      "examples": [
        1
      ]
    }
  },
  "required": [
    "state",
    "recording_id",
    "transcript_id",
    "version",
    "source_version",
    "external_contact",
    "speaker_identity",
    "error_code",
    "provenance",
    "segments",
    "total_segments"
  ],
  "title": "Transcript"
}
TranscriptChannel
{
  "type": "object",
  "properties": {
    "channel_index": {
      "type": "integer",
      "description": "The channel, matching `channel_index` on segments.",
      "examples": [
        0
      ]
    },
    "role": {
      "type": "string",
      "description": "`parent` is the side that placed the call: the caller on incoming calls, your workspace on outgoing ones. `child` is the side that was connected to it. `unknown` when SimpleVox can't tell.",
      "enum": [
        "parent",
        "child",
        "unknown"
      ]
    },
    "device_owner_name": {
      "type": [
        "string",
        "null"
      ],
      "description": "The name of the workspace member whose phone answered or placed the call, on the channel that captured your workspace's side. It's who had the phone, not a verified speaker. `null` on the customer's channel or when unknown.",
      "examples": [
        "Alex Rivera"
      ]
    }
  },
  "required": [
    "channel_index",
    "role",
    "device_owner_name"
  ],
  "title": "TranscriptChannel"
}
TranscriptSegment
{
  "type": "object",
  "properties": {
    "index": {
      "type": "integer",
      "description": "The segment's position in the transcript, from `0`. Quote it with the transcript's `version` to cite the segment.",
      "examples": [
        0
      ]
    },
    "text": {
      "type": "string",
      "description": "What was said.",
      "examples": [
        "Thursday at ten works for me."
      ]
    },
    "speaker": {
      "type": "string",
      "description": "A label that tells the speakers in this transcript apart, such as `A` or `speaker_0`. Labels come from the audio; they aren't identities.",
      "examples": [
        "speaker_0"
      ]
    },
    "channel_index": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The audio channel the segment came from in a two-channel recording, or `null` when the channels are mixed.",
      "examples": [
        0
      ]
    },
    "start_seconds": {
      "type": "number",
      "description": "When the segment starts, in seconds from the start of the recording.",
      "examples": [
        0.5
      ]
    },
    "end_seconds": {
      "type": "number",
      "description": "When the segment ends, in seconds from the start of the recording.",
      "examples": [
        2.4
      ]
    }
  },
  "required": [
    "index",
    "text",
    "speaker",
    "channel_index",
    "start_seconds",
    "end_seconds"
  ],
  "title": "TranscriptSegment"
}