API reference - Conversations, messages, and attachments
API reference · OpenAPI specification
Endpoint definitions below are generated from the API contract. Request and response blocks describe schemas, not example payloads. required lists mandatory fields; $ref refers to a linked model. Send JSON bodies with Content-Type: application/json unless the endpoint specifies binary content.
List accessible external conversation messages
GET /v1/messages
Requires messages.read. Every inbox in the conversation must be an allowed, current inbox. Internal-only conversations are excluded. Returns individual existing Waveline message records, not the logical recipient-group aggregation proposed in the full plan. external_number matches a participant in the conversation, not a claimed per-message recipient.
Parameters
| Name | Location | Required | Description and constraints |
|---|---|---|---|
inbox_id | query | No | Filter to activity involving this accessible inbox. Does not change connection access or the returned resource projection. Stable existing Waveline record ID. Keep it as a string; it may exceed JavaScript's safe integer range. Type: string maxLength: 19 pattern: "^[1-9][0-9]*$" |
direction | query | No | Filter by external call or message direction. Type: string enum: ["inbound","outbound"] |
limit | query | No | Type: integer default: 50 minimum: 1 maximum: 100 |
cursor | query | No | Opaque cursor from next_cursor. Reuse the same credentials, endpoint, filters, ordering, and limit. Results may change between requests; pagination does not freeze the dataset. Type: string |
created_after | query | No | Inclusive lower time bound. Applies to start_time for calls and created_at for contacts/messages. Include a time zone. Type: string format: "date-time" |
created_before | query | No | Exclusive upper time bound; must be later than created_after. Include a time zone. Type: string format: "date-time" |
external_number | query | No | Exact external conversation participant phone number. Type: string pattern: "^\+[1-9][0-9]{1,14}$" |
conversation_id | query | No | Only messages from this authorized conversation. All filters combine with AND. Stable existing Waveline record ID. Keep it as a string; it may exceed JavaScript's safe integer range. Type: string maxLength: 19 pattern: "^[1-9][0-9]*$" |
order | query | No | oldest (the default) pages forward by ascending stable ID. newest returns the most recent records first and next_cursor continues with older records. The order is bound to the cursor. Type: string default: "oldest" enum: ["oldest","newest"] |
include_labels | query | No | true adds display labels to each record: inbox_labels for accessible inboxes, contacts matched to external numbers (requires contacts.read; omitted otherwise) and, for messages, sender_name. The default record shape and webhook payloads are unchanged. Type: string default: "false" enum: ["true","false"] |
Responses
200 — Messages in the requested order (ascending stable ID by default).
application/json
allOf:
- $ref: '#/components/schemas/Page'
- properties:
data:
type: array
items:
$ref: '#/components/schemas/Message'
Errors — 400 invalid input, 401 expired/revoked/missing key, 403 scope or access denied, 404 missing/inaccessible, 409 request/state conflict, 413 oversized request or response, 415 wrong content type, 429 rate limited, or 503 unavailable. 429 includes Retry-After. All responses include X-Request-Id.
Error format, status codes, and retry guidance.
Send one billed external recipient message
POST /v1/messages
Requires messages.write and messages.read, a selected inbox with a registered sending number, and an invoice-backed subscription. Each accepted recipient send costs US$0.01 before tax, including sends that later fail delivery. Segments, attachment parts, and retries under the same Idempotency-Key do not add integration sending fees. Send deduplication is permanent. Each request supports one E.164 external recipient and must include text or attachments. Messaging registration, opt-out, and recipient-blocking requirements still apply. Acceptance does not confirm delivery.
Parameters
| Name | Location | Required | Description and constraints |
|---|---|---|---|
Idempotency-Key | header | Yes | Use a stable request identifier. Contact writes replay the saved response for seven days, subject to current resource access. Subscription creation retains its identity for seven days but returns current metadata and signing secret; a disabled/deleted subscription rejects replay. Reusing those keys with a different operation, path or exact JSON body returns 409. Subscription status and delete operations are repeatable state changes. Type: string minLength: 8 maxLength: 128 pattern: "^[!-~]+$" |
Request body (required)
application/json
type: object
required:
- inbox_id
- to
properties:
inbox_id:
$ref: '#/components/schemas/RecordId'
to:
type: string
pattern: ^\+[1-9][0-9]{1,14}$
body:
type: string
maxLength: 1600
from:
type: string
attachment_ids:
type: array
maxItems: 10
items:
$ref: '#/components/schemas/RecordId'
description: >-
Finalized upload attachment IDs for this connection/inbox or attachments on
currently accessible messages. Total<=5MiB. Each upload is consumed by one logical
send.
additionalProperties: false
Models: RecordId.
Responses
202 — Authorized result.
application/json
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/MessageSend'
Models: MessageSend.
Errors — 400 invalid input, 401 expired/revoked/missing key, 403 scope or access denied, 404 missing/inaccessible, 409 request/state conflict, 413 oversized request or response, 415 wrong content type, 429 rate limited, or 503 unavailable. 429 includes Retry-After. All responses include X-Request-Id.
Error format, status codes, and retry guidance.
Get one accessible message
GET /v1/messages/{id}
Requires messages.read. Includes completed attachment metadata; download URLs require a separate authorized request.
Parameters
| Name | Location | Required | Description and constraints |
|---|---|---|---|
id | path | Yes | Stable existing Waveline record ID. Keep it as a string; it may exceed JavaScript's safe integer range. Type: string maxLength: 19 pattern: "^[1-9][0-9]*$" |
include_labels | query | No | true adds display labels to each record: inbox_labels for accessible inboxes, contacts matched to external numbers (requires contacts.read; omitted otherwise) and, for messages, sender_name. The default record shape and webhook payloads are unchanged. Type: string default: "false" enum: ["true","false"] |
Responses
200 — Message record.
application/json
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/Message'
rate_limit:
$ref: '#/components/schemas/RateLimit'
Errors — 400 invalid input, 401 expired/revoked/missing key, 403 scope or access denied, 404 missing/inaccessible, 409 request/state conflict, 413 oversized request or response, 415 wrong content type, 429 rate limited, or 503 unavailable. 429 includes Retry-After. All responses include X-Request-Id.
Error format, status codes, and retry guidance.
List accessible conversations by recent activity
GET /v1/conversations
Requires messages.read. Returns one row per readable conversation, most recent activity first, using the same visibility rules as listMessages. Each row carries a preview of the last message and awaiting_reply (the external party sent the last message). Personal assistant (OAuth) connections also receive the member's own unread state; API keys receive null and cannot use unread_only. The date filters apply to the last activity. The cursor is anchored to the last returned conversation's current activity, so a conversation that receives a new message while you page can appear again; none are skipped.
Parameters
| Name | Location | Required | Description and constraints |
|---|---|---|---|
inbox_id | query | No | Filter to activity involving this accessible inbox. Does not change connection access or the returned resource projection. Stable existing Waveline record ID. Keep it as a string; it may exceed JavaScript's safe integer range. Type: string maxLength: 19 pattern: "^[1-9][0-9]*$" |
limit | query | No | Type: integer default: 50 minimum: 1 maximum: 100 |
cursor | query | No | Opaque cursor from next_cursor. Reuse the same credentials, endpoint, filters, ordering, and limit. Results may change between requests; pagination does not freeze the dataset. Type: string |
created_after | query | No | Inclusive lower time bound. Applies to start_time for calls and created_at for contacts/messages. Include a time zone. Type: string format: "date-time" |
created_before | query | No | Exclusive upper time bound; must be later than created_after. Include a time zone. Type: string format: "date-time" |
include_labels | query | No | true adds display labels to each record: inbox_labels for accessible inboxes, contacts matched to external numbers (requires contacts.read; omitted otherwise) and, for messages, sender_name. The default record shape and webhook payloads are unchanged. Type: string default: "false" enum: ["true","false"] |
external_number | query | No | Exact external conversation participant phone number. Type: string pattern: "^\+[1-9][0-9]{1,14}$" |
unread_only | query | No | true returns only conversations unread by the authenticated member. Personal assistant connections only. Type: string enum: ["true","false"] |
Responses
200 — Conversations ordered by most recent activity.
application/json
allOf:
- $ref: '#/components/schemas/Page'
- properties:
data:
type: array
items:
$ref: '#/components/schemas/Conversation'
Models: Page, Conversation.
Errors — 400 invalid input, 401 expired/revoked/missing key, 403 scope or access denied, 404 missing/inaccessible, 409 request/state conflict, 413 oversized request or response, 415 wrong content type, 429 rate limited, or 503 unavailable. 429 includes Retry-After. All responses include X-Request-Id.
Error format, status codes, and retry guidance.
List completed message attachments
GET /v1/messages/{id}/attachments
Requires messages.read. Missing and inaccessible parent resources return 404.
Parameters
| Name | Location | Required | Description and constraints |
|---|---|---|---|
id | path | Yes | Stable existing Waveline record ID. Keep it as a string; it may exceed JavaScript's safe integer range. Type: string maxLength: 19 pattern: "^[1-9][0-9]*$" |
limit | query | No | Type: integer default: 50 minimum: 1 maximum: 100 |
cursor | query | No | Opaque cursor from next_cursor. Reuse the same credentials, endpoint, filters, ordering, and limit. Results may change between requests; pagination does not freeze the dataset. Type: string |
Responses
200 — Page ordered by ascending ID.
application/json
type: object
required:
- data
- next_cursor
properties:
data:
type: array
items:
$ref: '#/components/schemas/MessageAttachment'
next_cursor:
type:
- string
- 'null'
Models: MessageAttachment.
Errors — 400 invalid input, 401 expired/revoked/missing key, 403 scope or access denied, 404 missing/inaccessible, 409 request/state conflict, 413 oversized request or response, 415 wrong content type, 429 rate limited, or 503 unavailable. 429 includes Retry-After. All responses include X-Request-Id.
Error format, status codes, and retry guidance.
Get a private message attachment download URL
GET /v1/attachments/{id}/download
Requires messages.read and current access to the associated message. The URL expires after five minutes.
Parameters
| Name | Location | Required | Description and constraints |
|---|---|---|---|
id | path | Yes | Stable existing Waveline record ID. Keep it as a string; it may exceed JavaScript's safe integer range. Type: string maxLength: 19 pattern: "^[1-9][0-9]*$" |
Responses
200 — Authorized result.
application/json
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/MediaDownload'
Models: MediaDownload.
Errors — 400 invalid input, 401 expired/revoked/missing key, 403 scope or access denied, 404 missing/inaccessible, 409 request/state conflict, 413 oversized request or response, 415 wrong content type, 429 rate limited, or 503 unavailable. 429 includes Retry-After. All responses include X-Request-Id.
Error format, status codes, and retry guidance.
Reserve a message media upload
POST /v1/message-uploads
Requires messages.read+messages.write and an allowed inbox. Expires in 24 hours; at most 25 unused unexpired reservations/connection. Uploading does not incur the sending fee. Only ready media can be sent, once per upload.
Parameters
| Name | Location | Required | Description and constraints |
|---|---|---|---|
Idempotency-Key | header | Yes | Use a stable request identifier. Contact writes replay the saved response for seven days, subject to current resource access. Subscription creation retains its identity for seven days but returns current metadata and signing secret; a disabled/deleted subscription rejects replay. Reusing those keys with a different operation, path or exact JSON body returns 409. Subscription status and delete operations are repeatable state changes. Type: string minLength: 8 maxLength: 128 pattern: "^[!-~]+$" |
Request body (required)
application/json
type: object
required:
- inbox_id
- file_name
- mime_type
- size_bytes
- sha256
properties:
inbox_id:
$ref: '#/components/schemas/RecordId'
file_name:
type: string
pattern: ^[A-Za-z0-9][A-Za-z0-9._ -]{0,119}$
mime_type:
type: string
enum:
- image/jpeg
- image/png
- image/gif
- image/webp
- application/pdf
- audio/mpeg
- audio/mp4
- video/mp4
size_bytes:
type: integer
minimum: 1
maximum: 5242880
sha256:
type: string
pattern: ^[a-f0-9]{64}$
additionalProperties: false
Models: RecordId.
Responses
201 — Authorized result.
application/json
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/MessageUpload'
Models: MessageUpload.
Errors — 400 invalid input, 401 expired/revoked/missing key, 403 scope or access denied, 404 missing/inaccessible, 409 request/state conflict, 413 oversized request or response, 415 wrong content type, 429 rate limited, or 503 unavailable. 429 includes Retry-After. All responses include X-Request-Id.
Error format, status codes, and retry guidance.
Upload exactly the reserved media bytes
PUT /v1/message-uploads/{upload_id}/content
Use reserved Content-Type, same Bearer credentials, exact size and SHA-256. Content-Encoding is rejected. Retry identical bytes at this URL; existing objects are never overwritten. Returns ready attachment_id. The upload ID provides idempotency; no separate Idempotency-Key is needed.
Parameters
| Name | Location | Required | Description and constraints |
|---|---|---|---|
upload_id | path | Yes | Type: string format: "uuid" |
Request body (required)
image/jpeg, image/png, image/gif, image/webp, application/pdf, audio/mpeg, audio/mp4, video/mp4
type: string
format: binary
Responses
200 — Authorized result.
application/json
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/MessageUpload'
Models: MessageUpload.
Errors — 400 invalid input, 401 expired/revoked/missing key, 403 scope or access denied, 404 missing/inaccessible, 409 request/state conflict, 413 oversized request or response, 415 wrong content type, 429 rate limited, or 503 unavailable. 429 includes Retry-After. All responses include X-Request-Id.