Documents API reference

Updated

13 operations of the Fax for Agents API, from the OpenAPI document version 1.20.0, snapshot of 2026-11-13. The base URL is https://api.faxforagents.com/v1. This is one group of the full reference.

  • A document is immutable once created, and its sha256 identifies exactly what will be sent.
  • Completing an upload checks the size and, when one is sent, the sha256 of the bytes.
  • The bytes of an upload go to a signed upload URL with no API key.

Operations

What is a document in this API?

The exact thing a fax will carry. POST /v1/documents creates it from an upload or from text, and the summary of the operation calls it immutable. The answer has a sha256, a page_count, the source and a validation object. Nothing changes those afterwards. A quote and an approval both name the hash, so a person who approves a send approves these bytes.

How does an upload session work?

Four calls, and only three of them take your key.

POST /v1/uploads opens a bounded session. The body declares the filename, the size_bytes and the content_type, which is application/pdf, image/png, image/jpeg or image/tiff. The answer has the id and a signed upload_url with its expiry.

PUT /v1/uploads/{upload_id}/content is that signed URL. It takes the bytes and no API key. The signature in the address is the credential.

POST /v1/uploads/{upload_id}/complete closes the session. The size is checked against what was declared. The body can carry a sha256, and the call fails when the bytes differ. Send an empty JSON object when you have no hash.

POST /v1/documents with the upload_id then makes the document. DELETE /v1/uploads/{upload_id} cancels a session that was not completed.

Can a document be made without a file?

Yes. The same create call takes text, markdown or html in place of upload_id, and exactly one of the four. Text is rendered to a letter PDF. Markdown renders headings, lists, emphasis and code. HTML is rendered by a headless browser with scripts off and no network, so inline CSS and data: images print and an external resource does not. A filename is optional.

The send guide has the fonts, the page size and what happens to an image.

How do I check what was made?

GET /v1/documents/{document_id} returns the hash, the pages and the validation state. GET /v1/documents/{document_id}/validation returns the warnings alone, such as a blocked resource in HTML or an image that was rotated. GET /v1/documents/{document_id}/pages returns the rendered pages with signed preview URLs, so a person can look at each page before a send.

How are the bytes downloaded?

Never through a public address. GET /v1/documents/{document_id}/download answers with a short-lived signed URL. The content routes behind such URLs, for the PDF and for one page as PNG, take exp and sig in the query and no key. When the link has expired, ask for a new one.

What does deleting do?

DELETE /v1/documents/{document_id} removes the bytes and the previews. A fax that already used the document keeps its record of the hash and the page count. GET /v1/documents lists documents newest first with cursor and limit.

Which scopes and limits apply?

Reads need document:read. Creating, deleting and the upload session need document:write. The signed routes need neither.

A file or a body over the size limit answers 413. A rendering service that is down answers 503 on the create call, and that call can be repeated. The page and size limits with their values are on the limits page.

Each operation in detail

GET /v1/documents

List documents, newest first

Credential: an API key or a signed-in session. Scope: document:read.

Parameters

GET /v1/documents parameters
NameInTypeRequiredNotes
cursorquerystring or nullno
limitqueryintegerno Default 50.

Responses

GET /v1/documents responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

GET /v1/documents response fields
FieldTypeRequiredDescription
itemsarray of object Documentyes
next_cursorstring or nullno

Example

curl https://api.faxforagents.com/v1/documents \
  -H "Authorization: Bearer ffa_test_..."

POST /v1/documents

Create an immutable document from an upload or from text

Credential: an API key or a signed-in session. Scope: document:write.

Parameters

POST /v1/documents parameters
NameInTypeRequiredNotes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Request body application/json, required

Send exactly one of text, markdown, html or upload_id.

POST /v1/documents request body
FieldTypeRequiredDescription
filenamestring or nullno
htmlstring or nullnoHTML rendered to PDF by a headless browser with scripts off and no network: inline CSS and data: images print, external resources are counted as blocked. US letter unless @page says otherwise. At most 2 MiB.
markdownstring or nullnoMarkdown (headings, lists, emphasis, code) rendered to PDF.
textstring or nullnoPlain text rendered to a letter PDF.
upload_idstring or nullno

Responses

POST /v1/documents responses
StatusMeaning
201Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
413The body is larger than the limit of this call: payload_too_large.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.
503Something this call depends on is not available or not set up here, for example billing_unavailable or provider_unavailable. error.retryable says whether to send the same call again.

Response fields

POST /v1/documents response fields
FieldTypeRequiredDescription
content_typestringyes
created_atstringyes
deleted_atstring or nullyes
environmentstring (live, test)yes
expires_atstring or nullyes
filenamestringyes
idstringyes
page_countintegeryes
render_reasonstring or nullyes
render_statusstringyes
sha256stringyes
size_bytesintegeryes
sourcestringyes
statusstringyes
upload_idstring or nullyes
validationobjectyes

Example

curl https://api.faxforagents.com/v1/documents \
  -X POST \
  -H "Authorization: Bearer ffa_test_..." \
  -H "Content-Type: application/json" \
  -d '{"text":"Hello from Fax for Agents"}'

DELETE /v1/documents/{document_id}

Delete a document (its bytes and previews)

Credential: an API key or a signed-in session. Scope: document:write.

Parameters

DELETE /v1/documents/{document_id} parameters
NameInTypeRequiredNotes
document_idpathstringyes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Responses

DELETE /v1/documents/{document_id} responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

DELETE /v1/documents/{document_id} response fields
FieldTypeRequiredDescription
deletedbooleanno
idstringyes

Example

curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID \
  -X DELETE \
  -H "Authorization: Bearer ffa_test_..."

GET /v1/documents/{document_id}

Read a document's hash, pages and validation state

Credential: an API key or a signed-in session. Scope: document:read.

Parameters

GET /v1/documents/{document_id} parameters
NameInTypeRequiredNotes
document_idpathstringyes

Responses

GET /v1/documents/{document_id} responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

GET /v1/documents/{document_id} response fields
FieldTypeRequiredDescription
content_typestringyes
created_atstringyes
deleted_atstring or nullyes
environmentstring (live, test)yes
expires_atstring or nullyes
filenamestringyes
idstringyes
page_countintegeryes
render_reasonstring or nullyes
render_statusstringyes
sha256stringyes
size_bytesintegeryes
sourcestringyes
statusstringyes
upload_idstring or nullyes
validationobjectyes

Example

curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID \
  -H "Authorization: Bearer ffa_test_..."

GET /v1/documents/{document_id}/content

The PDF bytes behind a signed URL (no API key)

Credential: the signed URL as it was given.

Parameters

GET /v1/documents/{document_id}/content parameters
NameInTypeRequiredNotes
document_idpathstringyes
expquerystring or nullno
sigquerystring or nullno

Responses

GET /v1/documents/{document_id}/content responses
StatusMeaning
200Successful Response
404not_found: no such resource for this credential. A resource of another organization answers the same.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.

Example

curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID/content

GET /v1/documents/{document_id}/download

A short-lived signed download URL

Credential: an API key or a signed-in session. Scope: document:read.

Parameters

GET /v1/documents/{document_id}/download parameters
NameInTypeRequiredNotes
document_idpathstringyes

Responses

GET /v1/documents/{document_id}/download responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

GET /v1/documents/{document_id}/download response fields
FieldTypeRequiredDescription
content_typestringyes
expires_atstringyes
urlstringyes

Example

curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID/download \
  -H "Authorization: Bearer ffa_test_..."

GET /v1/documents/{document_id}/pages

Rendered pages with signed preview URLs

Credential: an API key or a signed-in session. Scope: document:read.

Parameters

GET /v1/documents/{document_id}/pages parameters
NameInTypeRequiredNotes
document_idpathstringyes

Responses

GET /v1/documents/{document_id}/pages responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

GET /v1/documents/{document_id}/pages response fields
FieldTypeRequiredDescription
document_idstringyes
itemsarray of object PagePreviewyes
page_countintegeryes
render_reasonstring or nullyes
render_statusstringyes

Example

curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID/pages \
  -H "Authorization: Bearer ffa_test_..."

GET /v1/documents/{document_id}/pages/{page_number}/content

A page PNG behind a signed URL (no API key)

Credential: the signed URL as it was given.

Parameters

GET /v1/documents/{document_id}/pages/{page_number}/content parameters
NameInTypeRequiredNotes
document_idpathstringyes
page_numberpathintegeryes
expquerystring or nullno
sigquerystring or nullno

Responses

GET /v1/documents/{document_id}/pages/{page_number}/content responses
StatusMeaning
200Successful Response
404not_found: no such resource for this credential. A resource of another organization answers the same.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.

Example

curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID/pages/PAGE_NUMBER/content

GET /v1/documents/{document_id}/validation

Validation warnings

Credential: an API key or a signed-in session. Scope: document:read.

Parameters

GET /v1/documents/{document_id}/validation parameters
NameInTypeRequiredNotes
document_idpathstringyes

Responses

GET /v1/documents/{document_id}/validation responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

GET /v1/documents/{document_id}/validation response fields
FieldTypeRequiredDescription
idstringyes
page_countintegeryes
render_reasonstring or nullyes
render_statusstringyes
statusstringyes
warningsarray of objectyes

Example

curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID/validation \
  -H "Authorization: Bearer ffa_test_..."

POST /v1/uploads

Create a bounded upload session

Credential: an API key or a signed-in session. Scope: document:write.

Parameters

POST /v1/uploads parameters
NameInTypeRequiredNotes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Request body application/json, required

POST /v1/uploads request body
FieldTypeRequiredDescription
content_typestringnoapplication/pdf, image/png, image/jpeg or image/tiff. An image becomes 8-bit gray letter pages, since fax is monochrome. A frame wider than it is tall is rotated. A frame above 300 dpi letter is downsampled. Every TIFF frame is a page. PNG and JPEG take the first frame. The bytes must be the declared type.
filenamestringyes
size_bytesintegeryes

Responses

POST /v1/uploads responses
StatusMeaning
201Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
413The body is larger than the limit of this call: payload_too_large.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

POST /v1/uploads response fields
FieldTypeRequiredDescription
completed_atstring or nullyes
content_typestringyes
created_atstringyes
declared_sizeintegeryes
expires_atstringyes
filenamestringyes
idstringyes
received_sizeintegeryes
sha256string or nullyes
statusstringyes
upload_methodstring or nullno
upload_urlstring or nullno
upload_url_expires_atstring or nullno

Example

curl https://api.faxforagents.com/v1/uploads \
  -X POST \
  -H "Authorization: Bearer ffa_test_..." \
  -H "Content-Type: application/json" \
  -d '{"filename":"contract.pdf","size_bytes":1}'

DELETE /v1/uploads/{upload_id}

Cancel an incomplete upload

Credential: an API key or a signed-in session. Scope: document:write.

Parameters

DELETE /v1/uploads/{upload_id} parameters
NameInTypeRequiredNotes
upload_idpathstringyes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Responses

DELETE /v1/uploads/{upload_id} responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

DELETE /v1/uploads/{upload_id} response fields
FieldTypeRequiredDescription
deletedbooleanno
idstringyes

Example

curl https://api.faxforagents.com/v1/uploads/UPLOAD_ID \
  -X DELETE \
  -H "Authorization: Bearer ffa_test_..."

POST /v1/uploads/{upload_id}/complete

Finalize an upload (size and hash checked)

Credential: an API key or a signed-in session. Scope: document:write.

Parameters

POST /v1/uploads/{upload_id}/complete parameters
NameInTypeRequiredNotes
upload_idpathstringyes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Request body application/json

POST /v1/uploads/{upload_id}/complete request body
FieldTypeRequiredDescription
sha256string or nullnoOptional: fail if the bytes differ.

Responses

POST /v1/uploads/{upload_id}/complete responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

POST /v1/uploads/{upload_id}/complete response fields
FieldTypeRequiredDescription
completed_atstring or nullyes
content_typestringyes
created_atstringyes
declared_sizeintegeryes
expires_atstringyes
filenamestringyes
idstringyes
received_sizeintegeryes
sha256string or nullyes
statusstringyes
upload_methodstring or nullno
upload_urlstring or nullno
upload_url_expires_atstring or nullno

Example

curl https://api.faxforagents.com/v1/uploads/UPLOAD_ID/complete \
  -X POST \
  -H "Authorization: Bearer ffa_test_..."

PUT /v1/uploads/{upload_id}/content

Send the bytes to a signed upload URL (no API key)

Credential: the signed URL as it was given.

Parameters

PUT /v1/uploads/{upload_id}/content parameters
NameInTypeRequiredNotes
upload_idpathstringyes
expquerystring or nullno
sigquerystring or nullno
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Responses

PUT /v1/uploads/{upload_id}/content responses
StatusMeaning
200Successful Response
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
413The body is larger than the limit of this call: payload_too_large.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

PUT /v1/uploads/{upload_id}/content response fields
FieldTypeRequiredDescription
completed_atstring or nullyes
content_typestringyes
created_atstringyes
declared_sizeintegeryes
expires_atstringyes
filenamestringyes
idstringyes
received_sizeintegeryes
sha256string or nullyes
statusstringyes
upload_methodstring or nullno
upload_urlstring or nullno
upload_url_expires_atstring or nullno

Example

curl https://api.faxforagents.com/v1/uploads/UPLOAD_ID/content \
  -X PUT