# Fax for Agents: full text > Fax for Agents is a fax API, a hosted MCP server and SDKs built so AI agents can send and receive faxes. It is made by JSB Holdings LLC. Fax for Agents is the trading name of JSB Holdings LLC, company number 7447451, registered in the United States. It is based in Dover, the United States. It has operated since 2023. Contact: hello@faxforagents.com or https://faxforagents.com/contact/. Last content change: 2026-10-30. The link list from /llms.txt comes first. The full text of each page follows, in the same order. Pages are separated by a line with three dashes. --- # Fax for Agents > Fax for Agents is a fax API, a hosted MCP server and SDKs built so AI agents can send and receive faxes. It is made by JSB Holdings LLC. Fax for Agents is the trading name of JSB Holdings LLC, company number 7447451, registered in the United States. It is based in Dover, the United States. It has operated since 2023. Contact: hello@faxforagents.com or https://faxforagents.com/contact/. Every page below is static HTML, so its full text is present in the initial response. No JavaScript has to run to read any of it. Key pages also have a plain Markdown copy at index.md inside its URL, for example https://faxforagents.com/fax-api/index.md. ## Full text - [llms-full.txt](https://faxforagents.com/llms-full.txt): The full text of product, pricing, docs, api reference, company, legal, in one file ## Product - [Fax for Agents: a fax API and MCP server for AI agents](https://faxforagents.com/): A fax API, a hosted MCP server and SDKs for AI agents. Send and receive inside the permissions and budget you set. Every job ends with a receipt. - [Fax API: send and receive faxes with a receipt per job](https://faxforagents.com/fax-api/): Upload, quote, send, receive, signed webhooks and delivery evidence. REST, for developers and agents. $0.039 per page sent in the US and Canada. - [Fax MCP server: 15 hosted tools for AI agents](https://faxforagents.com/mcp/): Fifteen tools to prepare, quote, approve, send and read faxes. Remote, at mcp.faxforagents.com. API key auth, a free sandbox, set up in minutes. - [Fax for AI agents: approvals, budgets and evidence](https://faxforagents.com/fax-for-ai-agents/): How an AI agent sends a fax safely. Approvals bound to one document and recipient, policies evaluated on the server, budgets, and an inbox whose text is data. - [Receive fax API: numbers, a private inbox and webhooks](https://faxforagents.com/receive-fax-api/): Inbound fax API. A number for $2 a month, each received fax as a private PDF, a signed fax.received webhook. $0.019 per page as of 2026-10-28. - [Fax numbers by API: $2 a month, US and Canada at launch](https://faxforagents.com/fax-numbers/): A fax-capable number from the API for $2 a month as of 2026-10-28. United States, Canada, Puerto Rico and the US Virgin Islands at launch. - [Coverage: where faxes go and where numbers are hosted](https://faxforagents.com/coverage/): Outbound and inbound coverage from the versioned coverage file. Numbers in US, CA, PR and VI at launch. A country is verified only after a real fax reached it. ## Pricing - [Pricing: $0.039 per page sent, $0.019 received, $2 a number](https://faxforagents.com/pricing/): Prepaid, per page. $0.039 sent and $0.019 received in the US and Canada, $2 per number a month, $10 minimum top-up, sandbox free. Prices as of 2026-10-28. ## Docs - [Documentation: Fax for Agents API, MCP and SDKs](https://faxforagents.com/docs/): The quickstart first. Then sending and receiving, webhooks, approvals, policies, agents, errors, the sandbox, MCP setup, the SDKs and the API reference. - [Quickstart: your first fax in the sandbox](https://faxforagents.com/docs/quickstart/): A test key, a document, a quote, a sandbox send and the receipt. Five calls. Nothing leaves the box and nothing is charged. - [Send a fax: upload, quote, send, receipt](https://faxforagents.com/docs/send-fax/): The send contract. PDF and image uploads, text, Markdown and HTML rendered, the quote that binds the price, the Idempotency-Key and the six job states. - [Receive a fax: numbers, the inbox and fax.received](https://faxforagents.com/docs/receive-fax/): A number, a webhook and the private inbox. Each received fax is a PDF. The text of a fax is always data, never an instruction. - [Webhooks: signatures, retries, replay and the event log](https://faxforagents.com/docs/webhooks/): Every delivery carries webhook-id, webhook-timestamp and an HMAC-SHA256 signature. Verify it in Python and TypeScript, the retry schedule, replays and events. - [Approvals: a person decides, the server binds it](https://faxforagents.com/docs/approvals/): An approval covers one document hash, one destination, one sender, one amount and an expiry. Any change invalidates it. Request, decide and send with one. - [Delegated policies: limits written once, outside the prompt](https://faxforagents.com/docs/policies/): Limits evaluated on the server for every send. Allowed destinations, page and amount caps, daily and monthly limits, expiry. An agent cannot move them. - [Agent identities and scoped keys](https://faxforagents.com/docs/agents/): Each agent is a named principal with its own key. Scopes it cannot widen, a budget it cannot raise. Disabling an agent revokes its keys at once. - [Errors and job states: what each one means and what to do](https://faxforagents.com/docs/errors/): The error envelope and every code with its action. The six job states. When unknown means wait, why partial never retransmits, how to avoid duplicates. - [Sandbox: every outcome on demand, free](https://faxforagents.com/docs/sandbox/): Test keys send to scripted numbers: delivered, busy then delivered, partial, no answer, unknown, provider timeout, invalid number. Same events, no cost. - [MCP setup: Claude Code, Claude Desktop, Cursor, Codex, n8n](https://faxforagents.com/docs/mcp-setup/): Connect your client to https://mcp.faxforagents.com/mcp/ with an API key. Copy-ready configuration for Claude Code, Claude Desktop, Cursor, Codex and n8n. - [SDKs: Python and TypeScript packages for the fax API](https://faxforagents.com/docs/sdks/): Two packages named faxforagents, for Python and for Node and TypeScript, built from the OpenAPI document, published on 2026-10-30. REST works without them. - [Python fax API SDK: the faxforagents package on PyPI](https://faxforagents.com/docs/sdks/python/): The faxforagents package for Python, on PyPI since 2026-10-30: upload, quote, send, wait for the receipt, page through jobs. The REST calls work without it. - [TypeScript fax API SDK: the faxforagents package on npm](https://faxforagents.com/docs/sdks/typescript/): The faxforagents package for Node and TypeScript, on npm since 2026-10-30: upload, quote, send, wait for the receipt, list jobs. The REST calls work without it. ## API reference - [API reference: 146 operations from the OpenAPI document](https://faxforagents.com/docs/api/): From the OpenAPI 3.1 snapshot of 2026-10-30. 146 operations in 18 groups. Read it as plain HTML or open the interactive explorer. - [Full API reference as plain HTML](https://faxforagents.com/docs/api/reference/): Static HTML from the OpenAPI snapshot of 2026-10-30. Every operation, parameter, request body, response and schema. No JavaScript needed. ## Company - [Security: the controls that are in place](https://faxforagents.com/security/): The controls in place, with the mechanism behind each. Signed webhooks, idempotency keys, private documents, bound approvals, EU processing, subprocessors. - [About Fax for Agents and JSB Holdings LLC](https://faxforagents.com/about/): Fax for Agents is made by JSB Holdings LLC, a Delaware company formed in 2023. What the product is, who runs it and how to reach it. - [Changelog: every release of Fax for Agents](https://faxforagents.com/changelog/): Every production release, dated. The API, the MCP server and the site, from v0.1.0 on 2026-10-28 onward. - [Status: where to check if Fax for Agents is up](https://faxforagents.com/status/): The hosted status page at status.faxforagents.com, the health endpoints, and what is monitored on the API, the MCP server and document creation. - [Contact Fax for Agents](https://faxforagents.com/contact/): Write to hello@faxforagents.com for support, billing and security questions. No form, no phone line. The postal address of JSB Holdings LLC is on this page. ## Legal - [Terms of service: Fax for Agents](https://faxforagents.com/terms/): The terms under which JSB Holdings LLC provides Fax for Agents. The account, the prepaid wallet, acceptable use, and what we do and do not promise. - [Privacy policy: Fax for Agents](https://faxforagents.com/privacy/): What personal data Fax for Agents stores, why, for how long and with which subprocessors. Documents are kept 30 days by default and processed in the EU. - [Acceptable use policy: Fax for Agents](https://faxforagents.com/acceptable-use/): What may not be sent through Fax for Agents: unsolicited faxes, unlawful content, protected health information and sanctioned destinations. Dated 2026-10-28. - [Refunds and partial delivery: Fax for Agents](https://faxforagents.com/refunds/): Failed sends are not charged. A partial delivery charges only the pages the provider confirmed. An unknown outcome holds the amount until a review settles it. ## Essentials - [OpenAPI (YAML)](https://faxforagents.com/openapi.yaml): The API specification - [OpenAPI (JSON)](https://faxforagents.com/openapi.json): The API specification - [facts.json](https://faxforagents.com/facts.json): Every figure the site states, each with its as-of date and method ## Optional - [Sitemap](https://faxforagents.com/sitemap-index.xml): Every indexable URL, with a lastmod taken from the content, not the build clock - [robots.txt](https://faxforagents.com/robots.txt): Crawler policy. Search and answer engines are allow-listed by name, never by wildcard --- # Your agent can send a fax. Now it can prove it. > A fax API, a hosted MCP server and SDKs for AI agents. Send and receive inside the permissions and budget you set. Every job ends with a receipt. Page: https://faxforagents.com/ Last changed: 2026-10-28 A fax API, a hosted MCP server and SDKs. Your agent prepares, quotes, gets approval, sends, receives and reconciles, inside the permissions and the budget you set. Every job ends with a record you can check. [Get an API key](https://app.faxforagents.com/sign-in) [Read the quickstart](https://faxforagents.com/docs/quickstart/) - Job: fax_019a2f3b7c1d7a2e9f00c4b2d7e8a1f6 - To: +1 415 555 0142 - From: +1 628 555 0199 - Pages: 3 of 3 - Provider ref: TX-88f2a1 - Document sha256: 9b1c4e2a... - Amount captured: $0.117 - Timestamp: 2026-10-28 14:02:31 UTC delivered, evidence attached A sample receipt. 3 pages at $0.039, prices as of 2026-10-28. The number and the reference are made up. ## How a job runs 1. 01 / Prepare. Upload a PDF or an image, or post text, Markdown or HTML. The API renders and validates it and records its sha256. 2. 02 / Quote. A price before the send, from a versioned rate table. The quote binds the document, the destination, the sender and the amount for ten minutes. 3. 03 / Approve. A person approves that exact send, or a delegated policy written in advance allows it. Any change to the document invalidates the approval. 4. 04 / Send. Bounded retries on busy and no answer. A circuit breaker per route. The wallet holds the amount until the outcome is known. 5. 05 / Receive. A private inbox behind signed URLs. The text of a received fax is listed as untrusted data on every result. 6. 06 / Reconcile. Six states: queued, active, partial, failed, unknown, delivered. The word delivered is written only with the provider's evidence on the attempt. ## Built for agents - A hosted MCP server with 15 tools at `https://mcp.faxforagents.com/mcp/`, and a REST API at `https://api.faxforagents.com/v1`. - Approvals and budgets are evaluated on the server, never from prompt text. An agent cannot widen its own permissions. - Signed webhooks with event ids. An `Idempotency-Key` on every mutating call, so a retry returns the same job. - The sandbox is free. Every outcome, from delivered to unknown, can be rehearsed with a test API key before real money moves. - Documents are private, kept 30 days by default, processed in the EU, and deletable by the owner. [How the authorization model works](https://faxforagents.com/fax-for-ai-agents/) and [what the MCP server exposes](https://faxforagents.com/mcp/). ## Prices - $0.039 per page sent - $0.019 per page received - $2 per number per month - Optional Agent workspace, $19 per month Prepaid wallet, $10 minimum top-up. United States and Canada prices as of 2026-10-28. International rates are in the [rate table](https://faxforagents.com/pricing/). The sandbox is free. ## What this is not Not a HIPAA service. Protected health information is out of scope and the [acceptable use policy](https://faxforagents.com/acceptable-use/) says so. Not an online fax service for people. It is an API, an MCP server and SDKs for software. Not every format. PDF, PNG, JPG and TIFF uploads, and text, Markdown and HTML rendered to PDF. No office formats. Not every country. Inbound numbers in the United States, Canada, Puerto Rico and the US Virgin Islands at launch. International send rates are listed on the [coverage page](https://faxforagents.com/coverage/) and a country is marked verified only after a real fax reached it. Fax for Agents is the trading name of JSB Holdings LLC. --- # A fax API built for AI agents > Upload, quote, send, receive, signed webhooks and delivery evidence. REST, for developers and agents. $0.039 per page sent in the US and Canada. Page: https://faxforagents.com/fax-api/ Last changed: 2026-10-30 Updated Oct 30, 2026 - This covers: sending and receiving faxes over a REST API, with a receipt for every job - This is not: an online fax inbox for people, and not a HIPAA service - Measured by: jobs that end delivered with provider evidence, per page sent Related pages [MCP server](https://faxforagents.com/mcp/) · [For AI agents](https://faxforagents.com/fax-for-ai-agents/) · [Receive](https://faxforagents.com/receive-fax-api/) · [Numbers](https://faxforagents.com/fax-numbers/) · [Coverage](https://faxforagents.com/coverage/) ## One call to send Shell ``` `curl https://api.faxforagents.com/v1/faxes \ -H "Authorization: Bearer ffa_test_..." \ -H "Idempotency-Key: order-4411-fax" \ -H "Content-Type: application/json" \ -d '{"quote_id": "quote_019a2f3b7c1d7a2e9f00c4b2d7e8a1f6"}'` ``` The quote came from `POST /v1/fax-quotes` with a document id and a destination. The send answers `queued`. The receipt arrives by webhook and on `GET /v1/faxes/{id}/receipt`. - Job: fax_019a2f3b7c1d7a2e9f00c4b2d7e8a1f6 - To: +1 415 555 0142 - From: +1 628 555 0199 - Pages: 3 of 3 - Provider ref: TX-88f2a1 - Document sha256: 9b1c4e2a... - Amount captured: $0.117 - Timestamp: 2026-10-28 14:02:31 UTC delivered, evidence attached A sample receipt. 3 pages at $0.039, prices as of 2026-10-28. The number and the reference are made up. Fax for Agents is a REST API at `https://api.faxforagents.com/v1` for sending and receiving faxes, with a hosted MCP server and SDKs on top of it. Pricing is per page and prepaid. $0.039 per page sent and $0.019 per page received in the United States and Canada, as of 2026-10-28. It is built for software that acts on its own. Every mutating call takes an `Idempotency-Key`. Every send is quoted before it is charged. Every job ends with evidence a person can check. A fax API is a web service that sends and receives faxes through HTTP calls. Software that uses one needs no fax machine, no phone line and no online fax inbox. Fax for Agents is a cloud fax API for programs and agents, not an online fax service for people. ## What a job looks like A send has five steps and each one is a call you can make with curl. 1. Upload a PDF or an image, or post text, Markdown or HTML and let the API render it. The document gets a sha256 and a page count. 2. Quote the document to one destination. The quote fixes the price, the pages and the sender for ten minutes. 3. Send the quote. The wallet holds the amount. A test key is sent at once. A live API key needs an approval or a policy that allows it. 4. Watch the job. It moves through `queued` and `active` and ends `delivered`, `partial`, `failed` or `unknown`. Webhooks carry each change with a signature. 5. Read the receipt. Provider reference, document hash, pages, amount captured, timestamp. ## Receiving A number costs $2 a month as of 2026-10-28 and receives at $0.019 per page. Every received fax lands in a private inbox as a PDF behind a short-lived signed URL, and a signed `fax.received` event goes to your webhook. The text of a received fax is data. It is listed as untrusted on every MCP result and it never reaches a policy decision. ## What is not included Fax for Agents is not an online fax service for people, and it is not a HIPAA service. It sends PDF, PNG, JPG and TIFF uploads and documents rendered from text, Markdown and HTML. Office formats are not accepted. Inbound numbers are available in the United States, Canada, Puerto Rico and the US Virgin Islands at launch, elsewhere on request. ## Prices - $0.039 per page sent - $0.019 per page received - $2 per number per month - Optional Agent workspace, $19 per month Prepaid wallet, $10 minimum top-up. United States and Canada prices as of 2026-10-28. International rates are in the [rate table](https://faxforagents.com/pricing/). The sandbox is free. ## Questions ### Can an AI agent send a fax? Yes, through this API or its MCP server. The agent uploads or renders a document, asks for a quote, and sends the quoted job. A person sets the limits first. Either an approval is given for that exact document and recipient, or a delegated policy allows the send on the server. The job ends in one of six states and a receipt records the provider reference, the document hash, the amount and the time. ### What does fax delivered actually mean here? It means the provider reported every page as received by the remote machine, and that report is stored on the attempt. The receipt names the provider reference and the completion time. It does not mean a person read the document. A job without that report stays active or ends unknown, and the site never writes delivered without the evidence behind it. ### Is the fax API a HIPAA service? No. Fax for Agents is not a HIPAA service and does not sign business associate agreements. Protected health information must not be sent through it. The acceptable use policy says so plainly. Documents are processed in the EU, kept 30 days by default and deletable by the owner at any time. None of that makes the service suitable for PHI. ### How do I test a fax integration without contacting real numbers? Use a test key. It is tied to the sandbox, where each scripted destination ends in a chosen state, from delivered to unknown. The sandbox fires the same signed webhooks as a live send and costs nothing. Every test organization starts with a seeded test wallet, so the hold, capture and release path runs exactly as it will with real money. --- # The hosted fax MCP server > Fifteen tools to prepare, quote, approve, send and read faxes. Remote, at mcp.faxforagents.com. API key auth, a free sandbox, set up in minutes. Page: https://faxforagents.com/mcp/ Last changed: 2026-10-30 Updated Oct 30, 2026 - This covers: the 15 tools an agent calls at mcp.faxforagents.com with an API key - This is not: the REST API itself, which the tools wrap, and not a protocol every fax provider speaks - Measured by: tool calls that end with a receipt or a named refusal Related pages [Fax API](https://faxforagents.com/fax-api/) · [For AI agents](https://faxforagents.com/fax-for-ai-agents/) · [Receive](https://faxforagents.com/receive-fax-api/) · [Numbers](https://faxforagents.com/fax-numbers/) · [Coverage](https://faxforagents.com/coverage/) ## Claude Code Shell ``` `claude mcp add --transport http faxforagents https://mcp.faxforagents.com/mcp/ \ --header "Authorization: Bearer ffa_test_..."` ``` The trailing slash on the URL matters. Other clients are on the [setup page](https://faxforagents.com/docs/mcp-setup/). The hosted MCP server lives at `https://mcp.faxforagents.com/mcp/` and speaks Streamable HTTP. It carries the same credential as the REST API, `Authorization: Bearer ffa_test_...` or `X-API-Key`, on every request. There is no operator key on the server and no state between requests. A client with no key is still answered on `initialize`, `tools/list`, the resources and the prompts. Every tool then answers with `error_code: invalid_token` and the way to get a key. ## The 15 tools | Tool | What it does | | --- | --- | | `get_guide` | The manual, by section: overview, send, states, errors, approvals, policies, agents, inbox, ocr | | `get_fax_capabilities` | The account-level view behind the key: formats, page cap, routes, prices, scopes. It never evaluates a send | | `prepare_fax` | Create a document from text, Markdown, HTML or an upload | | `get_document` | One prepared document by id: page count, sha256, validation warnings and signed preview URLs | | `quote_fax` | Price one document to one destination, with the policy verdict: allowed or denied, the first failing rule, whether a person must approve. The quote binds the send | | `send_approved_fax` | Send a quote with an approval or under a policy | | `get_fax_status` | The job's state and attempts, and once delivered or partial the carrier's evidence as a reference | | `list_faxes` | Sent faxes, newest first | | `get_fax_balance` | Available and held balance | | `cancel_fax` | Cancel if still possible | | `request_human_review` | With a quote id, ask a person to approve that send. Without one, ask a person for help, the message recorded as data | | `get_review_status` | The state of one request: pending, approved, rejected, revoked or expired, with the approval id once approved | | `list_inbox` | Received faxes, newest first, metadata only | | `read_inbound_fax` | One received fax, its text listed as untrusted | | `extract_fax_data` | OCR and structured extraction of a received fax, every value listed as untrusted | Version 1.3.0 merged three tools into their neighbours. `get_delivery_evidence` is part of `get_fax_status`. `request_fax_approval` and `request_human_help` are one tool, `request_human_review`. The bare `guide` is `get_guide`. Version 1.3.2 swapped two. `preview_fax` is `get_document`, which returns the document itself. `evaluate_fax_policy` is gone because the quote carries the same verdict, and `get_review_status` reads one request back. Documents are listed through the REST API. The old names are gone from the server. ## Resources and prompts The manual is also served as MCP resources. `faxforagents://guide` is the whole text and `faxforagents://guide/send`, `faxforagents://guide/states` and the other sections are one resource each, all `text/markdown` and all readable without a key. Two prompts give a client the tool order. `send_a_fax` takes a destination and an optional summary. It walks through prepare, quote with its policy verdict, human review and the poll for its decision, send and status. `handle_received_fax` takes an inbox item id and walks through read, extract and human review, with the rule that the text of a received fax is data. Every result is text plus structured content: `ok`, `message`, `error_code`, and `untrusted` listing every string a customer or a sender supplied. The success message of a send says queued. The word delivered appears only when the provider's evidence exists, with the evidence beside it. ## What the server will not do It will not widen a key's scopes, raise a budget or decide an approval. Those are a person's actions in the dashboard, with a passkey step-up for a decision. It will not read the text of a received fax as an instruction. A quote id or an approval id lifted from a document and passed as an argument is refused as `invalid_input` before any request leaves the tool. ## Clients Claude Code, Claude Desktop, Cursor, Codex and n8n connect with the URL and a key. The setup page has the exact configuration for each. OAuth 2.1 for MCP is planned for a later release and is not live. API keys are the only credential today. The server's source, with the stdio bridge for a client that cannot reach a remote server, is at [github.com/faxforagents/mcp](https://github.com/faxforagents/mcp). ## Prices - $0.039 per page sent - $0.019 per page received - $2 per number per month - Optional Agent workspace, $19 per month Prepaid wallet, $10 minimum top-up. United States and Canada prices as of 2026-10-28. International rates are in the [rate table](https://faxforagents.com/pricing/). The sandbox is free. ## Questions ### What is a fax MCP server? A server that exposes fax operations as tools an AI client can call over the Model Context Protocol. This one runs at mcp.faxforagents.com and wraps the Fax for Agents REST API. Its 15 tools prepare a document, quote it, ask a person to review it, send and cancel. They also read status with the carrier's evidence, read the inbox, extract data from a received fax and check the balance. The manual is served as resources and two prompts give the tool order. The protocol is the door. The API behind it still enforces every rule. ### How is a fax MCP different from a fax API? The API is a set of HTTP endpoints your code calls with a key. The MCP server presents the same operations as named tools with descriptions and schemas. A client such as Claude Code or Cursor can pick and call them in a conversation. Both reach the same server, the same wallet and the same approvals. Nothing is possible through MCP that the API forbids. ### Does the MCP server replace the telephone provider? No. The MCP server is an interface. Transmission still happens through a fax provider the service connects to, with its own line, retries and delivery reports. The tools return what that provider reported, which is why a send answers queued and the delivered word only appears with the provider's evidence attached. ### Do I need a subscription to use the fax MCP? No. The MCP server is part of the service at no extra charge. Pages are prepaid at $0.039 sent and $0.019 received in the US and Canada as of 2026-10-28. The sandbox is free with a test key. The optional Agent workspace at $19 a month is a separate subscription and the MCP tools work without it. --- # Fax for AI agents: approvals, budgets and evidence > How an AI agent sends a fax safely. Approvals bound to one document and recipient, policies evaluated on the server, budgets, and an inbox whose text is data. Page: https://faxforagents.com/fax-for-ai-agents/ Last changed: 2026-10-30 Updated Oct 30, 2026 - This covers: the authorization model an agent works inside: approvals, delegated policies, budgets and an inbox whose text is data - This is not: an agent framework, and not a chatbot that faxes whatever a prompt says - Measured by: sends that stayed inside the limits a person set Related pages [Fax API](https://faxforagents.com/fax-api/) · [MCP server](https://faxforagents.com/mcp/) · [Receive](https://faxforagents.com/receive-fax-api/) · [Numbers](https://faxforagents.com/fax-numbers/) · [Coverage](https://faxforagents.com/coverage/) ## What an approval binds The five fields an approval covers. A change to any one invalidates it. | Field | Checked at send time | Refusal when it differs | | --- | --- | --- | | Document sha256 | against the quote and the document row | `document_hash_mismatch` | | Destination | E.164, exact | `approval_mismatch` | | Sender number | exact, or the pool | `approval_mismatch` | | Amount and pages | micro-USD and billable pages | `approval_mismatch` | | Expiry | the quote's ten minutes | `approval_expired` | An agent that can send a fax can also send the wrong one. Fax for Agents is built so that cannot happen quietly. Every live send is authorized in one of two ways, both enforced on the server. A person approves that exact send, or a delegated policy written in advance allows it. The agent can ask. It cannot decide, and it cannot change the limits. ## Three workflows **Human in the loop.** The agent prepares and quotes the fax, then calls `request_human_review` with the quote id. The owner gets a mail with the destination, the pages and the amount, opens the request and approves it with a passkey. The agent sends with the approval id. One approval, one send. **Bounded autonomy.** An administrator writes a delegated policy. These destinations or country prefixes. At most this many pages and this amount per job. This much per day and per month, until this date. The agent's key is bound to the policy. Every send is evaluated against the current version with the counters locked. A prompt that says "ignore the budget" changes nothing, because the evaluation never reads prompt text. **Receive and read.** A fax arrives on the agent's number. The agent lists the inbox and reads the item. The text comes back under `untrusted`. It can summarise it for a person. It cannot turn an instruction printed on that fax into a send. ## What the agent gets Its own identity, with a name, a purpose that is shown and never evaluated, a key with bounded scopes and a budget. Disabling the agent revokes its keys in the same transaction. Its activity is an audit trail: every row with its actor, metadata only, never a document body. ## What this is not It is not an agent framework. Use it from Claude Code, Cursor, LangChain, your own loop or a cron job. It is not a chatbot that faxes from a prompt. Nothing leaves the service without a quote, a hold on the wallet and an authorization the server can name. ## Prices - $0.039 per page sent - $0.019 per page received - $2 per number per month - Optional Agent workspace, $19 per month Prepaid wallet, $10 minimum top-up. United States and Canada prices as of 2026-10-28. International rates are in the [rate table](https://faxforagents.com/pricing/). The sandbox is free. ## Questions ### How do I stop an agent from faxing the wrong document? Bind the send. An approval in Fax for Agents covers one document hash, one destination, one sender number, one amount and an expiry. If the document changes after the approval, its hash changes and the send is refused with document_hash_mismatch. A delegated policy limits destinations by number or country prefix, so a wrong recipient is refused before the provider is called. ### Can a fax agent increase its own spending limit? No. An agent key is never issued the scopes that write budgets, policies or approvals. The server refuses those routes to any key with an agent id, even one minted with every scope. Budgets and caps are set by a person in the dashboard. A prompt that says ignore the budget changes nothing, because the policy engine never reads prompt text. ### How do I prevent prompt injection from incoming fax documents? The service treats everything in a received fax as data. Its text is listed under untrusted on every MCP result and is rendered as plain text, never as a heading, link or button. The policy engine and every tool decision take their inputs from the quote and the document row, not from that text. An id lifted from a fax and passed as an argument is refused as invalid input. ### Which fax operations should require human approval? Any live send that no delegated policy allows. In practice that means the first sends to a new recipient, anything over the per-job amount cap, and anything outside the allowed country prefixes. Reads, quotes, previews and sandbox sends need no approval. A person decides in the dashboard with a passkey step-up, and the decision covers exactly one send. --- # Receive faxes by API > Inbound fax API. A number for $2 a month, each received fax as a private PDF, a signed fax.received webhook. $0.019 per page as of 2026-10-28. Page: https://faxforagents.com/receive-fax-api/ Last changed: 2026-10-28 Updated Oct 28, 2026 - This covers: inbound numbers, the private inbox, the fax.received event and retention - This is not: outbound sending, which the fax API page covers, and not a shared email inbox - Measured by: received faxes that reached the inbox with a verified signed webhook Related pages [Fax API](https://faxforagents.com/fax-api/) · [MCP server](https://faxforagents.com/mcp/) · [For AI agents](https://faxforagents.com/fax-for-ai-agents/) · [Numbers](https://faxforagents.com/fax-numbers/) · [Coverage](https://faxforagents.com/coverage/) ## The event your webhook gets HTTP ``` `POST https://example.com/hooks/fax webhook-id: evt_019a2f3b7c1d7a2e9f00c4b2d7e8a1f9 webhook-timestamp: 1793196125 webhook-signature: v1,K2m9... { "id": "evt_019a2f3b7c1d7a2e9f00c4b2d7e8a1f9", "type": "fax.received", "data": { "inbox_item_id": "inb_019a2f3b7c1d7a2e9f00c4b2d7e8a1f7", "to": "+14155550100", "from": "+14155550177", "pages": 2, "partial_content": false, "document_id": "doc_019a2f3b7c1d7a2e9f00c4b2d7e8a1f8", "document_sha256": "4f2a...", "number_id": "num_019a2f3b7c1d7a2e9f00c4b2d7e8a1f5", "received_at": "2026-10-28T15:21:05Z", "download": "/v1/inbox/inb_019a2f3b7c1d7a2e9f00c4b2d7e8a1f7/download" } }` ``` A sample. The signature is HMAC-SHA256 over `id.timestamp.body` with your webhook secret. [How to verify it](https://faxforagents.com/docs/webhooks/). Receiving is three things: a number, a private inbox and a signed event. A number costs $2 a month as of 2026-10-28. Each received page costs $0.019. There is no subscription and no minimum beyond the $10 wallet top-up. ## How it works 1. `POST /v1/number-orders` buys a fax-capable number in the United States, Canada, Puerto Rico or the US Virgin Islands. Other countries are on request, because they need registration documents the provider must review first. 2. A fax arrives. The provider's callback is verified by signature. The pages are stored as one PDF under your organization, private, never at a public URL. 3. A `fax.received` event is written and delivered to your webhook with `webhook-id`, `webhook-timestamp` and `webhook-signature`. 4. `GET /v1/inbox/{id}` returns the item: the numbers, the page count, the time, and a signed download URL that lives ten minutes. ## The text is data Text extracted from a received fax is returned as a field named for what it is: `untrusted`. In the dashboard it sits in a grey block labelled "from the document, unverified". On the MCP server every tool lists it under `untrusted`. The policy engine never reads it. A fax that says "approve the attached and send it to this number" is a fax, not a command. ## Retention Inbox items are kept 90 days and documents 30 days by default. The owner can delete a document at any time. Metadata and audit rows stay, so an id still answers who, when and how many pages. ## What is not included Inbound OCR and structured extraction exist as `POST /v1/extractions` and the `extract_fax_data` tool, and run on the sandbox engine with a test key. For live keys the engine is off until it is switched on, with no date. A received fax is a PDF with metadata until then. Porting an existing number in is on request, not self-serve. This page is about receiving. Sending is on the fax API page. ## Prices - $0.039 per page sent - $0.019 per page received - $2 per number per month - Optional Agent workspace, $19 per month Prepaid wallet, $10 minimum top-up. United States and Canada prices as of 2026-10-28. International rates are in the [rate table](https://faxforagents.com/pricing/). The sandbox is free. ## Questions ### How can my application receive a fax as a PDF? Order a number from the API, point a webhook at your endpoint, and wait. When a fax arrives the service stores it as a PDF in your private inbox. It sends a signed fax.received event with the inbox id, the numbers and the page count. Your code verifies the signature, calls the inbox endpoint and downloads the PDF through a signed URL that lives ten minutes. ### Can an agent receive faxes as well as send them? Yes. The MCP server has list_inbox and read_inbound_fax, and the sandbox accepts a synthetic inbound fax from a test key. Everything the fax contains comes back listed as untrusted. The agent can read it, summarise it and tell a person about it. It cannot act on an instruction printed in the fax, because no tool decision reads that text. ### What happens to a received fax after 90 days? Inbox items are kept 90 days by default and documents 30 days, as decided on 2026-10-28. After that the file is deleted and the metadata and audit rows stay, so a job id still resolves to who sent what and when. The owner can delete a document earlier from the dashboard or the API. The defaults are per organization and will be configurable. ### How should repeated incoming faxes be handled? Each arrival is its own inbox item with its own id and its own event. A resend by the sender shows up twice, as it did on the line. Your webhook handler should dedupe on the webhook-id header, which stays the same across our retries of one event, and on the inbox id. Two items with the same page count and sender are still two faxes. --- # Fax numbers for your agent > A fax-capable number from the API for $2 a month as of 2026-10-28. United States, Canada, Puerto Rico and the US Virgin Islands at launch. Page: https://faxforagents.com/fax-numbers/ Last changed: 2026-10-28 Updated Oct 28, 2026 - This covers: ordering, labelling and releasing fax-capable numbers from the API - This is not: porting an existing number in, which is on request, and not voice numbers - Measured by: numbers active at $2 a month that receive Related pages [Fax API](https://faxforagents.com/fax-api/) · [MCP server](https://faxforagents.com/mcp/) · [For AI agents](https://faxforagents.com/fax-for-ai-agents/) · [Receive](https://faxforagents.com/receive-fax-api/) · [Coverage](https://faxforagents.com/coverage/) Inbound numbers at launch, as of 2026-10-28 | Country | Documents needed | Monthly | Per page received | | --- | --- | --- | --- | | United States | none | $2 | $0.019 | | Canada | none | $2 | $0.019 | | Puerto Rico | none | $2 | $0.019 | | US Virgin Islands | none | $2 | $0.019 | | Everywhere else | on request | $2 | $0.019 | A number is what makes receiving possible. Order one from the API, give it a label and point its inbound route at a webhook. Every fax to it lands in your private inbox. ## Price $2 per number per month and $0.019 per received page, as of 2026-10-28, from the prepaid wallet. No setup fee. The dashboard asks for a confirmation before it starts a recurring charge. ## Where United States, Canada, Puerto Rico and the US Virgin Islands at launch. These are the countries where the provider needs no registration documents, read on 2026-10-28. Every other country is on request. Write to [hello@faxforagents.com](mailto:hello@faxforagents.com) with the country and the use. We tell you what documents the provider asks for and how long it takes. ## What you can do from the API - Search the inventory by country and type. - Order a number. The order answers with the number once the provider assigns it. - Label it and set its inbound route. - Release it. The monthly charge stops and the number goes into quarantine. - List your numbers and read one. The exact endpoints are in the API reference under numbers once the release that carries them is live. The reference is built from the server's own OpenAPI document, so it never lists a route that does not answer. ## What is not included Porting in and porting out are on request. Voice is not offered on these numbers. Toll-free numbers are not in the launch inventory. ## Prices - $0.039 per page sent - $0.019 per page received - $2 per number per month - Optional Agent workspace, $19 per month Prepaid wallet, $10 minimum top-up. United States and Canada prices as of 2026-10-28. International rates are in the [rate table](https://faxforagents.com/pricing/). The sandbox is free. ## Questions ### How much does a dedicated fax number cost? $2 per number per month as of 2026-10-28, charged to the prepaid wallet, plus $0.019 per page received. There is no setup fee and no minimum term. Releasing a number stops the charge from the next month. The price is the same in every country where a number can be ordered today. At launch that means the United States, Canada, Puerto Rico and the US Virgin Islands. ### Can I port an existing business fax number? Porting is on request and not self-serve at launch. Write to hello@faxforagents.com with the number and the current carrier. A port needs a letter of authorization and a recent bill. It takes days to weeks depending on the losing carrier. The number keeps working on the old side until the port completes. Port-out works the same way in reverse. ### Who controls a rented fax number? The number is leased to your organization for as long as it is active and paid. You decide its label, its inbound route and when it is released. The provider owns the number itself, as with any rented number. A released number is quarantined before it is offered to anyone else. A fax sent to it after release does not land in a stranger's inbox. ### Which countries require extra number registration documents? Most of them. Outside the United States, Canada, Puerto Rico and the US Virgin Islands a number needs documents. A company registration, a local address or an identity document, which the provider reviews before the number is assigned. That is why those countries are on request. The coverage page lists what we read from the provider on 2026-10-28. --- # Where Fax for Agents sends and hosts numbers > Outbound and inbound coverage from the versioned coverage file. Numbers in US, CA, PR and VI at launch. A country is verified only after a real fax reached it. Page: https://faxforagents.com/coverage/ Last changed: 2026-10-28 Updated Oct 28, 2026 - This covers: the countries a fax can be sent to and the countries a number can be ordered in, from the versioned coverage file - This is not: a promise that every published route answers a fax tone, and not a sanctions screening service - Measured by: countries marked verified by a real fax Related pages [Fax API](https://faxforagents.com/fax-api/) · [MCP server](https://faxforagents.com/mcp/) · [For AI agents](https://faxforagents.com/fax-for-ai-agents/) · [Receive](https://faxforagents.com/receive-fax-api/) · [Numbers](https://faxforagents.com/fax-numbers/) This page is rendered from `/coverage.json`, the same versioned file the API serves, so it cannot say more than the server knows. Each row carries the version and the date it was generated. ## Inbound numbers Numbers can be ordered in 4 countries without registration documents, as read from the provider on 2026-10-28. Every other country is on request. Countries where a number can be ordered today | Code | Country | Monthly | | --- | --- | --- | | `CA` | Canada | $2.000 | | `PR` | Puerto Rico | $2.000 | | `US` | United States | $2.000 | | `VI` | US Virgin Islands | $2.000 | ## Outbound The United States and Canada are the launch route at $0.039 per page sent, rate table launch-v1, effective 2026-10-28. 215 countries have a published route and a price in rate table `telnyx-2026-10-28`, generated 2026-10-28. 0 of them are marked verified, which means one real fax reached the country through this service. A reachable row is a price, not a promise that a fax tone answers. Outbound routes from coverage.json version telnyx-2026-10-28 | Code | Country | Per page sent | Status | | --- | --- | --- | --- | | `AF` | Afghanistan | $0.690 | reachable, not yet verified | | `AL` | Albania | $0.819 | reachable, not yet verified | | `DZ` | Algeria | $0.367 | reachable, not yet verified | | `AS` | American Samoa | $0.668 | reachable, not yet verified | | `AD` | Andorra | $0.176 | reachable, not yet verified | | `AO` | Angola | $0.971 | reachable, not yet verified | | `AI` | Anguilla | $0.848 | reachable, not yet verified | | `AG` | Antigua and Barbuda | $0.895 | reachable, not yet verified | | `AR` | Argentina | $0.456 | reachable, not yet verified | | `AM` | Armenia | $0.730 | reachable, not yet verified | | `AW` | Aruba | $0.367 | reachable, not yet verified | | `AU` | Australia | $0.103 | reachable, not yet verified | | `AT` | Austria | $0.114 | reachable, not yet verified | | `AZ` | Azerbaijan | $1.273 | reachable, not yet verified | | `BS` | Bahamas | $0.957 | reachable, not yet verified | | `BH` | Bahrain | $0.501 | reachable, not yet verified | | `BD` | Bangladesh | $0.124 | reachable, not yet verified | | `BB` | Barbados | $0.767 | reachable, not yet verified | | `BY` | Belarus | $2.156 | reachable, not yet verified | | `BE` | Belgium | $0.165 | reachable, not yet verified | | `BZ` | Belize | $1.137 | reachable, not yet verified | | `BJ` | Benin | $1.773 | reachable, not yet verified | | `BM` | Bermuda | $0.184 | reachable, not yet verified | | `BT` | Bhutan | $0.350 | reachable, not yet verified | | `BO` | Bolivia | $0.698 | reachable, not yet verified | | `BA` | Bosnia and Herzegovina | $0.937 | reachable, not yet verified | | `BW` | Botswana | $0.766 | reachable, not yet verified | | `BR` | Brazil | $0.090 | reachable, not yet verified | | `VG` | British Virgin Islands | $0.821 | reachable, not yet verified | | `BN` | Brunei Darussalam | $0.290 | reachable, not yet verified | | `BG` | Bulgaria | $0.426 | reachable, not yet verified | | `BF` | Burkina Faso | $1.346 | reachable, not yet verified | | `BI` | Burundi | $2.034 | reachable, not yet verified | | `KH` | Cambodia | $0.366 | reachable, not yet verified | | `CM` | Cameroon | $1.085 | reachable, not yet verified | | `CA` | Canada | $0.039 | reachable, not yet verified | | `CV` | Cape Verde | $0.971 | reachable, not yet verified | | `KY` | Cayman Islands | $0.546 | reachable, not yet verified | | `CF` | Central African Republic | $2.180 | reachable, not yet verified | | `TD` | Chad | $2.331 | reachable, not yet verified | | `CL` | Chile | $0.090 | reachable, not yet verified | | `CN` | China | $2.120 | reachable, not yet verified | | `CO` | Colombia | $0.087 | reachable, not yet verified | | `KM` | Comoros | $2.786 | reachable, not yet verified | | `CG` | Congo (Republic of the) | $2.038 | reachable, not yet verified | | `CK` | Cook Islands | $5.659 | reachable, not yet verified | | `CR` | Costa Rica | $0.113 | reachable, not yet verified | | `HR` | Croatia | $0.730 | reachable, not yet verified | | `AN` | Curacao and the Caribbean Netherlands (former Netherlands Antilles) | $0.138 | reachable, not yet verified | | `CY` | Cyprus | $0.671 | reachable, not yet verified | | `CZ` | Czech Republic | $0.111 | reachable, not yet verified | | `CD` | Democratic Republic of the Congo | $1.730 | reachable, not yet verified | | `DK` | Denmark | $1.424 | reachable, not yet verified | | `DJ` | Djibouti | $1.857 | reachable, not yet verified | | `DM` | Dominica | $0.930 | reachable, not yet verified | | `DO` | Dominican Republic | $0.208 | reachable, not yet verified | | `EC` | Ecuador | $0.712 | reachable, not yet verified | | `EG` | Egypt | $0.424 | reachable, not yet verified | | `SV` | El Salvador | $0.789 | reachable, not yet verified | | `GQ` | Equatorial Guinea | $2.044 | reachable, not yet verified | | `ER` | Eritrea | $0.863 | reachable, not yet verified | | `EE` | Estonia | $0.099 | reachable, not yet verified | | `ET` | Ethiopia | $0.949 | reachable, not yet verified | | `FK` | Falkland Islands (Malvinas) | $8.524 | reachable, not yet verified | | `FO` | Faroe Islands | $0.153 | reachable, not yet verified | | `FJ` | Fiji | $1.081 | reachable, not yet verified | | `FI` | Finland | $1.273 | reachable, not yet verified | | `FR` | France | $0.092 | reachable, not yet verified | | `RE` | French Departments and Territories in the Indian Ocean | $0.129 | reachable, not yet verified | | `GF` | French Guiana | $0.139 | reachable, not yet verified | | `PF` | French Polynesia | $1.152 | reachable, not yet verified | | `GA` | Gabon | $2.340 | reachable, not yet verified | | `GM` | Gambia | $2.438 | reachable, not yet verified | | `GE` | Georgia | $1.515 | reachable, not yet verified | | `DE` | Germany | $0.133 | reachable, not yet verified | | `GH` | Ghana | $1.285 | reachable, not yet verified | | `GI` | Gibraltar | $0.188 | reachable, not yet verified | | `GR` | Greece | $0.335 | reachable, not yet verified | | `GL` | Greenland | $0.366 | reachable, not yet verified | | `GD` | Grenada | $0.802 | reachable, not yet verified | | `GP` | Guadeloupe, Saint Barthelemy and Saint Martin | $0.133 | reachable, not yet verified | | `GU` | Guam | $0.140 | reachable, not yet verified | | `GT` | Guatemala | $0.929 | reachable, not yet verified | | `GN` | Guinea | $2.756 | reachable, not yet verified | | `GW` | Guinea-Bissau | $2.193 | reachable, not yet verified | | `GY` | Guyana | $0.902 | reachable, not yet verified | | `HT` | Haiti | $1.333 | reachable, not yet verified | | `HN` | Honduras | $1.010 | reachable, not yet verified | | `HK` | Hong Kong (China) | $0.217 | reachable, not yet verified | | `HU` | Hungary | $0.274 | reachable, not yet verified | | `IS` | Iceland | $0.090 | reachable, not yet verified | | `IN` | India | $0.154 | reachable, not yet verified | | `ID` | Indonesia | $0.271 | reachable, not yet verified | | `IQ` | Iraq | $0.707 | reachable, not yet verified | | `IE` | Ireland | $0.102 | reachable, not yet verified | | `IL` | Israel | $0.151 | reachable, not yet verified | | `IT` | Italy | $0.093 | reachable, not yet verified | | `CI` | Ivory Coast | $1.903 | reachable, not yet verified | | `JM` | Jamaica | $0.925 | reachable, not yet verified | | `JP` | Japan | $0.114 | reachable, not yet verified | | `JO` | Jordan | $0.635 | reachable, not yet verified | | `KZ` | Kazakhstan | $0.586 | reachable, not yet verified | | `KE` | Kenya | $0.789 | reachable, not yet verified | | `XK` | Kosovo | $2.130 | reachable, not yet verified | | `KW` | Kuwait | $0.206 | reachable, not yet verified | | `KG` | Kyrgyzstan | $1.191 | reachable, not yet verified | | `LA` | Lao P.D.R. | $0.335 | reachable, not yet verified | | `LV` | Latvia | $1.010 | reachable, not yet verified | | `LB` | Lebanon | $0.365 | reachable, not yet verified | | `LS` | Lesotho | $1.700 | reachable, not yet verified | | `LR` | Liberia | $1.908 | reachable, not yet verified | | `LY` | Libya | $1.652 | reachable, not yet verified | | `LI` | Liechtenstein | $0.142 | reachable, not yet verified | | `LT` | Lithuania | $0.738 | reachable, not yet verified | | `LU` | Luxembourg | $0.154 | reachable, not yet verified | | `MO` | Macao (China) | $0.607 | reachable, not yet verified | | `MG` | Madagascar | $2.846 | reachable, not yet verified | | `MW` | Malawi | $1.707 | reachable, not yet verified | | `MY` | Malaysia | $0.090 | reachable, not yet verified | | `MV` | Maldives | $3.693 | reachable, not yet verified | | `ML` | Mali | $2.599 | reachable, not yet verified | | `MT` | Malta | $0.750 | reachable, not yet verified | | `MH` | Marshall Islands | $0.891 | reachable, not yet verified | | `MQ` | Martinique | $0.151 | reachable, not yet verified | | `MR` | Mauritania | $2.559 | reachable, not yet verified | | `MU` | Mauritius | $0.560 | reachable, not yet verified | | `MX` | Mexico | $0.084 | reachable, not yet verified | | `FM` | Micronesia | $2.354 | reachable, not yet verified | | `MD` | Moldova | $1.314 | reachable, not yet verified | | `MC` | Monaco | $0.310 | reachable, not yet verified | | `MN` | Mongolia | $0.107 | reachable, not yet verified | | `ME` | Montenegro | $1.092 | reachable, not yet verified | | `MS` | Montserrat | $1.035 | reachable, not yet verified | | `MA` | Morocco | $0.873 | reachable, not yet verified | | `MZ` | Mozambique | $0.798 | reachable, not yet verified | | `MM` | Myanmar | $0.421 | reachable, not yet verified | | `NA` | Namibia | $0.547 | reachable, not yet verified | | `NP` | Nepal | $0.860 | reachable, not yet verified | | `NL` | Netherlands | $0.190 | reachable, not yet verified | | `NC` | New Caledonia | $1.122 | reachable, not yet verified | | `NZ` | New Zealand | $0.117 | reachable, not yet verified | | `NI` | Nicaragua | $3.076 | reachable, not yet verified | | `NE` | Niger | $1.542 | reachable, not yet verified | | `NG` | Nigeria | $0.562 | reachable, not yet verified | | `MP` | Northern Mariana Islands | $0.199 | reachable, not yet verified | | `NO` | Norway | $0.135 | reachable, not yet verified | | `OM` | Oman | $0.447 | reachable, not yet verified | | `PK` | Pakistan | $0.224 | reachable, not yet verified | | `PW` | Palau | $1.908 | reachable, not yet verified | | `PA` | Panama | $0.236 | reachable, not yet verified | | `PG` | Papua New Guinea | $4.104 | reachable, not yet verified | | `PY` | Paraguay | $0.208 | reachable, not yet verified | | `PE` | Peru | $0.078 | reachable, not yet verified | | `PH` | Philippines | $0.551 | reachable, not yet verified | | `PL` | Poland | $0.375 | reachable, not yet verified | | `PT` | Portugal | $1.654 | reachable, not yet verified | | `PR` | Puerto Rico | $0.101 | reachable, not yet verified | | `QA` | Qatar | $0.789 | reachable, not yet verified | | `RO` | Romania | $0.081 | reachable, not yet verified | | `RU` | Russian Federation | $0.517 | reachable, not yet verified | | `RW` | Rwanda | $1.364 | reachable, not yet verified | | `KN` | Saint Kitts and Nevis | $0.707 | reachable, not yet verified | | `LC` | Saint Lucia | $0.721 | reachable, not yet verified | | `PM` | Saint Pierre and Miquelon | $1.023 | reachable, not yet verified | | `VC` | Saint Vincent and the Grenadines | $0.753 | reachable, not yet verified | | `WS` | Samoa | $1.474 | reachable, not yet verified | | `SM` | San Marino | $0.134 | reachable, not yet verified | | `SA` | Saudi Arabia | $0.493 | reachable, not yet verified | | `SN` | Senegal | $1.363 | reachable, not yet verified | | `RS` | Serbia | $0.611 | reachable, not yet verified | | `SC` | Seychelles | $2.332 | reachable, not yet verified | | `SL` | Sierra Leone | $2.041 | reachable, not yet verified | | `SG` | Singapore | $0.139 | reachable, not yet verified | | `SX` | Sint Maarten Dutch part | $0.542 | reachable, not yet verified | | `SK` | Slovakia | $0.090 | reachable, not yet verified | | `SI` | Slovenia | $0.653 | reachable, not yet verified | | `SB` | Solomon Islands | $4.691 | reachable, not yet verified | | `SO` | Somalia | $2.604 | reachable, not yet verified | | `ZA` | South Africa | $0.547 | reachable, not yet verified | | `KR` | South Korea | $0.143 | reachable, not yet verified | | `ES` | Spain | $1.273 | reachable, not yet verified | | `LK` | Sri Lanka | $0.668 | reachable, not yet verified | | `SR` | Suriname | $1.549 | reachable, not yet verified | | `SZ` | Swaziland | $0.744 | reachable, not yet verified | | `SE` | Sweden | $0.105 | reachable, not yet verified | | `CH` | Switzerland | $0.184 | reachable, not yet verified | | `TW` | Taiwan | $0.148 | reachable, not yet verified | | `TJ` | Tajikistan | $0.789 | reachable, not yet verified | | `TZ` | Tanzania | $1.110 | reachable, not yet verified | | `TH` | Thailand | $0.320 | reachable, not yet verified | | `MK` | The Former Yugoslav Republic of Macedonia | $0.689 | reachable, not yet verified | | `TL` | Timor-Leste | $0.812 | reachable, not yet verified | | `TG` | Togo | $1.161 | reachable, not yet verified | | `TO` | Tonga | $4.298 | reachable, not yet verified | | `TT` | Trinidad and Tobago | $0.435 | reachable, not yet verified | | `TN` | Tunisia | $2.816 | reachable, not yet verified | | `TR` | Turkey | $0.184 | reachable, not yet verified | | `TM` | Turkmenistan | $0.814 | reachable, not yet verified | | `TC` | Turks and Caicos | $1.213 | reachable, not yet verified | | `UG` | Uganda | $1.553 | reachable, not yet verified | | `UA` | Ukraine | $0.698 | reachable, not yet verified | | `AE` | United Arab Emirates | $0.562 | reachable, not yet verified | | `GB` | United Kingdom | $0.076 | reachable, not yet verified | | `US` | United States | $0.039 | reachable, not yet verified | | `VI` | United States Virgin Islands | $0.199 | reachable, not yet verified | | `UY` | Uruguay | $0.252 | reachable, not yet verified | | `UZ` | Uzbekistan | $0.456 | reachable, not yet verified | | `VU` | Vanuatu | $5.145 | reachable, not yet verified | | `VE` | Venezuela | $0.220 | reachable, not yet verified | | `VN` | Vietnam | $0.340 | reachable, not yet verified | | `WF` | Wallis and Futuna | $1.435 | reachable, not yet verified | | `PS` | West Bank | $0.707 | reachable, not yet verified | | `YE` | Yemen | $0.562 | reachable, not yet verified | | `ZM` | Zambia | $1.615 | reachable, not yet verified | | `ZW` | Zimbabwe | $1.142 | reachable, not yet verified | ## Excluded by policy These destinations are refused before any money is held, whatever the provider publishes. Excluded destinations, coverage version telnyx-2026-10-28 | Code | Reason | | --- | --- | | `CU` | sanctioned destination, excluded by policy | | `IR` | sanctioned destination, excluded by policy | | `KP` | sanctioned destination, excluded by policy | | `SD` | high-risk destination, excluded by policy | | `SS` | high-risk destination, excluded by policy | | `SY` | sanctioned destination, excluded by policy | The machine-readable file is at [/coverage.json](https://faxforagents.com/coverage.json) and the rate table at [/pricing.json](https://faxforagents.com/pricing.json). ## Questions ### What does reachable mean on this page? Reachable means the provider publishes a termination rate for that country. The service can place the call and a price exists in the rate table. It does not mean a fax tone will answer. A country is marked verified only after one real fax reached it through this service. Until then the row is a price, not a promise, and the page says which is which. ### Why are some countries excluded? Sanctioned and high-risk destinations are refused by policy whatever the provider publishes. The send is refused with unsupported_destination before any money is held or any call is placed. The list is in the coverage file with the reason for each entry and it changes only with a dated release. Screening a recipient against a sanctions list is your responsibility, not a feature of the service. ### Where can I get a fax number? In the United States, Canada, Puerto Rico and the US Virgin Islands at launch. Those are the countries where the provider asks for no registration documents. Every other country is on request. Write to hello@faxforagents.com with the country and we tell you what documents are needed. A number costs $2 a month wherever it is hosted, as of 2026-10-28. --- # Pricing > Prepaid, per page. $0.039 sent and $0.019 received in the US and Canada, $2 per number a month, $10 minimum top-up, sandbox free. Prices as of 2026-10-28. Page: https://faxforagents.com/pricing/ Last changed: 2026-10-28 Prepaid and per page. No subscription needed, no minimum beyond the first top-up. Every price on this page is as of 2026-10-28, from the same rate table the quote engine reads. ## United States and Canada Launch prices from rate table `launch-v1`, effective 2026-10-28 | What | Price | Billed | | --- | --- | --- | | Page sent | $0.039 | per page the provider delivered, cover page included | | Page received | $0.019 | per page that arrived on your number | | Fax number | $2.00 | per number per month | | Minimum top-up | $10.00 | once, into the prepaid wallet, by card | | Agent workspace | $19.00 | per month, optional subscription | | Sandbox | $0.00 | every page, every outcome, with a test key. | Failed sends are not charged. A partial delivery charges the confirmed pages only. An unknown outcome holds the amount until a review settles it. The rule is written out on the [refunds page](https://faxforagents.com/refunds/). ## Calculator ## International 213 routes from rate table `telnyx-2026-10-28`, generated 2026-10-28. Receiving and numbers outside the launch countries are on request. Per page sent, by destination | Destination | Prefix | Per page sent | | --- | --- | --- | | Afghanistan | +93 | $0.690 | | Albania | +355 | $0.819 | | Algeria | +213 | $0.367 | | American Samoa | +1684 | $0.668 | | Andorra | +376 | $0.176 | | Angola | +244 | $0.971 | | Anguilla | +1264 | $0.848 | | Antigua and Barbuda | +1268 | $0.895 | | Argentina | +54 | $0.456 | | Armenia | +374 | $0.730 | | Aruba | +297 | $0.367 | | Australia | +61 | $0.103 | | Austria | +43 | $0.114 | | Azerbaijan | +994 | $1.273 | | Bahamas | +1242 | $0.957 | | Bahrain | +973 | $0.501 | | Bangladesh | +880 | $0.124 | | Barbados | +1246 | $0.767 | | Belarus | +375 | $2.156 | | Belgium | +32 | $0.165 | | Belize | +501 | $1.137 | | Benin | +229 | $1.773 | | Bermuda | +1441 | $0.184 | | Bhutan | +975 | $0.350 | | Bolivia | +591 | $0.698 | | Bosnia and Herzegovina | +387 | $0.937 | | Botswana | +267 | $0.766 | | Brazil | +55 | $0.090 | | British Virgin Islands | +1284 | $0.821 | | Brunei Darussalam | +673 | $0.290 | | Bulgaria | +359 | $0.426 | | Burkina Faso | +226 | $1.346 | | Burundi | +257 | $2.034 | | Cambodia | +855 | $0.366 | | Cameroon | +237 | $1.085 | | Cape Verde | +238 | $0.971 | | Cayman Islands | +1345 | $0.546 | | Central African Republic | +236 | $2.180 | | Chad | +235 | $2.331 | | Chile | +56 | $0.090 | | China | +86 | $2.120 | | Colombia | +57 | $0.087 | | Comoros | +269 | $2.786 | | Congo (Republic of the) | +242 | $2.038 | | Cook Islands | +682 | $5.659 | | Costa Rica | +506 | $0.113 | | Croatia | +385 | $0.730 | | Curacao and the Caribbean Netherlands (former Netherlands Antilles) | +599 | $0.138 | | Cyprus | +357 | $0.671 | | Czech Republic | +420 | $0.111 | | Democratic Republic of the Congo | +243 | $1.730 | | Denmark | +45 | $1.424 | | Djibouti | +253 | $1.857 | | Dominica | +1767 | $0.930 | | Dominican Republic | +1809, +1829, +1849 | $0.208 | | Ecuador | +593 | $0.712 | | Egypt | +20 | $0.424 | | El Salvador | +503 | $0.789 | | Equatorial Guinea | +240 | $2.044 | | Eritrea | +291 | $0.863 | | Estonia | +372 | $0.099 | | Ethiopia | +251 | $0.949 | | Falkland Islands (Malvinas) | +500 | $8.524 | | Faroe Islands | +298 | $0.153 | | Fiji | +679 | $1.081 | | Finland | +358 | $1.273 | | France | +33 | $0.092 | | French Departments and Territories in the Indian Ocean | +262 | $0.129 | | French Guiana | +594 | $0.139 | | French Polynesia | +689 | $1.152 | | Gabon | +241 | $2.340 | | Gambia | +220 | $2.438 | | Georgia | +995 | $1.515 | | Germany | +49 | $0.133 | | Ghana | +233 | $1.285 | | Gibraltar | +350 | $0.188 | | Greece | +30 | $0.335 | | Greenland | +299 | $0.366 | | Grenada | +1473 | $0.802 | | Guadeloupe, Saint Barthelemy and Saint Martin | +590 | $0.133 | | Guam | +1671 | $0.140 | | Guatemala | +502 | $0.929 | | Guinea | +224 | $2.756 | | Guinea-Bissau | +245 | $2.193 | | Guyana | +592 | $0.902 | | Haiti | +509 | $1.333 | | Honduras | +504 | $1.010 | | Hong Kong (China) | +852 | $0.217 | | Hungary | +36 | $0.274 | | Iceland | +354 | $0.090 | | India | +91 | $0.154 | | Indonesia | +62 | $0.271 | | Iraq | +964 | $0.707 | | Ireland | +353 | $0.102 | | Israel | +972 | $0.151 | | Italy | +39 | $0.093 | | Ivory Coast | +225 | $1.903 | | Jamaica | +1876, +1658 | $0.925 | | Japan | +81 | $0.114 | | Jordan | +962 | $0.635 | | Kazakhstan | +76, +77 | $0.586 | | Kenya | +254 | $0.789 | | Kosovo | +383 | $2.130 | | Kuwait | +965 | $0.206 | | Kyrgyzstan | +996 | $1.191 | | Lao P.D.R. | +856 | $0.335 | | Latvia | +371 | $1.010 | | Lebanon | +961 | $0.365 | | Lesotho | +266 | $1.700 | | Liberia | +231 | $1.908 | | Libya | +218 | $1.652 | | Liechtenstein | +423 | $0.142 | | Lithuania | +370 | $0.738 | | Luxembourg | +352 | $0.154 | | Macao (China) | +853 | $0.607 | | Madagascar | +261 | $2.846 | | Malawi | +265 | $1.707 | | Malaysia | +60 | $0.090 | | Maldives | +960 | $3.693 | | Mali | +223 | $2.599 | | Malta | +356 | $0.750 | | Marshall Islands | +692 | $0.891 | | Martinique | +596 | $0.151 | | Mauritania | +222 | $2.559 | | Mauritius | +230 | $0.560 | | Mexico | +52 | $0.084 | | Micronesia | +691 | $2.354 | | Moldova | +373 | $1.314 | | Monaco | +377 | $0.310 | | Mongolia | +976 | $0.107 | | Montenegro | +382 | $1.092 | | Montserrat | +1664 | $1.035 | | Morocco | +212 | $0.873 | | Mozambique | +258 | $0.798 | | Myanmar | +95 | $0.421 | | Namibia | +264 | $0.547 | | Nepal | +977 | $0.860 | | Netherlands | +31 | $0.190 | | New Caledonia | +687 | $1.122 | | New Zealand | +64 | $0.117 | | Nicaragua | +505 | $3.076 | | Niger | +227 | $1.542 | | Nigeria | +234 | $0.562 | | Northern Mariana Islands | +1670 | $0.199 | | Norway | +47 | $0.135 | | Oman | +968 | $0.447 | | Pakistan | +92 | $0.224 | | Palau | +680 | $1.908 | | Panama | +507 | $0.236 | | Papua New Guinea | +675 | $4.104 | | Paraguay | +595 | $0.208 | | Peru | +51 | $0.078 | | Philippines | +63 | $0.551 | | Poland | +48 | $0.375 | | Portugal | +351 | $1.654 | | Puerto Rico | +1787, +1939 | $0.101 | | Qatar | +974 | $0.789 | | Romania | +40 | $0.081 | | Russian Federation | +7 | $0.517 | | Rwanda | +250 | $1.364 | | Saint Kitts and Nevis | +1869 | $0.707 | | Saint Lucia | +1758 | $0.721 | | Saint Pierre and Miquelon | +508 | $1.023 | | Saint Vincent and the Grenadines | +1784 | $0.753 | | Samoa | +685 | $1.474 | | San Marino | +378 | $0.134 | | Saudi Arabia | +966 | $0.493 | | Senegal | +221 | $1.363 | | Serbia | +381 | $0.611 | | Seychelles | +248 | $2.332 | | Sierra Leone | +232 | $2.041 | | Singapore | +65 | $0.139 | | Sint Maarten Dutch part | +1721 | $0.542 | | Slovakia | +421 | $0.090 | | Slovenia | +386 | $0.653 | | Solomon Islands | +677 | $4.691 | | Somalia | +252 | $2.604 | | South Africa | +27 | $0.547 | | South Korea | +82 | $0.143 | | Spain | +34 | $1.273 | | Sri Lanka | +94 | $0.668 | | Suriname | +597 | $1.549 | | Swaziland | +268 | $0.744 | | Sweden | +46 | $0.105 | | Switzerland | +41 | $0.184 | | Taiwan | +886 | $0.148 | | Tajikistan | +992 | $0.789 | | Tanzania | +255 | $1.110 | | Thailand | +66 | $0.320 | | The Former Yugoslav Republic of Macedonia | +389 | $0.689 | | Timor-Leste | +670 | $0.812 | | Togo | +228 | $1.161 | | Tonga | +676 | $4.298 | | Trinidad and Tobago | +1868 | $0.435 | | Tunisia | +216 | $2.816 | | Turkey | +90 | $0.184 | | Turkmenistan | +993 | $0.814 | | Turks and Caicos | +1649 | $1.213 | | Uganda | +256 | $1.553 | | Ukraine | +380 | $0.698 | | United Arab Emirates | +971 | $0.562 | | United Kingdom | +44 | $0.076 | | United States Virgin Islands | +1340 | $0.199 | | Uruguay | +598 | $0.252 | | Uzbekistan | +998 | $0.456 | | Vanuatu | +678 | $5.145 | | Venezuela | +58 | $0.220 | | Vietnam | +84 | $0.340 | | Wallis and Futuna | +681 | $1.435 | | West Bank | +970 | $0.707 | | Yemen | +967 | $0.562 | | Zambia | +260 | $1.615 | | Zimbabwe | +263 | $1.142 | ## How the wallet works You top up by card through a hosted checkout, $10 to $1,000 at a time. A send holds its quoted amount. When the job ends the hold is captured for the delivered pages and released for the rest, in the same transaction as the outcome. The ledger is double-entry and every transaction sums to zero. You can set a low-balance alert, an automatic top-up with a daily cap, and spending caps per organization, project or agent. Invoices and receipts come from the card processor and are listed in the dashboard. Prepaid balances are yours until spent. A refund of unspent balance is handled by hand through [hello@faxforagents.com](https://faxforagents.com/contact/). ## Questions about price ### Will I pay for failed fax attempts? No. A send that ends failed releases its hold and nothing is captured. Busy and no-answer attempts are retried up to three times and none of them is charged unless the fax is delivered. A send refused before the provider is called, for an invalid number or an empty wallet, costs nothing. The refunds page states the rule in full with the partial delivery case. ### What happens when only some pages are delivered? The job ends partial. Only the pages the provider confirmed are charged, at the per-page rate, and the hold for the rest is released in the same transaction. Nothing is retransmitted on its own, because a second attempt could send pages the recipient already has. You decide whether to send the missing pages as a new fax. ### Is the advertised fax page price all-inclusive? For the United States and Canada, yes. $0.039 per page sent and $0.019 per page received as of 2026-10-28. No connection fee, no per-minute charge and no monthly minimum beyond the $10 top-up. Numbers are $2 a month. International sends use the rate table on this page, which is versioned and dated. The sandbox is free. ### Are cover pages charged as fax pages? Yes, a cover page is a page on the line, so it is counted and charged like any other. The quote says so. It lists the document pages and the cover page separately as billable pages before you send, so the receipt holds no surprise. You can send without a cover page and most agents do. ### Do I need a subscription? No. Pay per page from the prepaid wallet with a $10 minimum top-up, and the API, the MCP server and the sandbox are included. The Agent workspace at $19 a month as of 2026-10-28 is optional and adds dashboard features for teams that run several agents. Nothing on the API is locked behind it. --- # Documentation > The quickstart first. Then sending and receiving, webhooks, approvals, policies, agents, errors, the sandbox, MCP setup, the SDKs and the API reference. Page: https://faxforagents.com/docs/ Last changed: 2026-10-28 Updated Oct 28, 2026 The REST API is at `https://api.faxforagents.com/v1` and the MCP server at `https://mcp.faxforagents.com/mcp/`. Both take the same key, as `Authorization: Bearer ffa_test_...` or `X-API-Key`. A test key is tied to the free sandbox. A live API key sends through the carrier and spends the wallet. ## Start here The [quickstart](https://faxforagents.com/docs/quickstart/) gets a key, uploads a document, quotes it, sends it to a sandbox number and reads the receipt. Five calls, nothing charged. ## Guides - [Quickstart](https://faxforagents.com/docs/quickstart/): A test key, a document, a quote, a sandbox send and the receipt. Five calls. Nothing leaves the box and nothing is charged. - [Send a fax](https://faxforagents.com/docs/send-fax/): The send contract. PDF and image uploads, text, Markdown and HTML rendered, the quote that binds the price, the Idempotency-Key and the six job states. - [Receive a fax](https://faxforagents.com/docs/receive-fax/): A number, a webhook and the private inbox. Each received fax is a PDF. The text of a fax is always data, never an instruction. - [Webhooks](https://faxforagents.com/docs/webhooks/): Every delivery carries webhook-id, webhook-timestamp and an HMAC-SHA256 signature. Verify it in Python and TypeScript, the retry schedule, replays and events. - [Approvals](https://faxforagents.com/docs/approvals/): An approval covers one document hash, one destination, one sender, one amount and an expiry. Any change invalidates it. Request, decide and send with one. - [Policies](https://faxforagents.com/docs/policies/): Limits evaluated on the server for every send. Allowed destinations, page and amount caps, daily and monthly limits, expiry. An agent cannot move them. - [Agents](https://faxforagents.com/docs/agents/): Each agent is a named principal with its own key. Scopes it cannot widen, a budget it cannot raise. Disabling an agent revokes its keys at once. - [Errors and states](https://faxforagents.com/docs/errors/): The error envelope and every code with its action. The six job states. When unknown means wait, why partial never retransmits, how to avoid duplicates. - [Sandbox](https://faxforagents.com/docs/sandbox/): Test keys send to scripted numbers: delivered, busy then delivered, partial, no answer, unknown, provider timeout, invalid number. Same events, no cost. - [MCP setup](https://faxforagents.com/docs/mcp-setup/): Connect your client to https://mcp.faxforagents.com/mcp/ with an API key. Copy-ready configuration for Claude Code, Claude Desktop, Cursor, Codex and n8n. - [SDKs](https://faxforagents.com/docs/sdks/): Two packages named faxforagents, for Python and for Node and TypeScript, built from the OpenAPI document, published on 2026-10-30. REST works without them. - [Python SDK](https://faxforagents.com/docs/sdks/python/): The faxforagents package for Python, on PyPI since 2026-10-30: upload, quote, send, wait for the receipt, page through jobs. The REST calls work without it. - [TypeScript SDK](https://faxforagents.com/docs/sdks/typescript/): The faxforagents package for Node and TypeScript, on npm since 2026-10-30: upload, quote, send, wait for the receipt, list jobs. The REST calls work without it. ## Reference - [API reference](https://faxforagents.com/docs/api/): From the OpenAPI 3.1 snapshot of 2026-10-30. 146 operations in 18 groups. Read it as plain HTML or open the interactive explorer. - [Full reference (no JS)](https://faxforagents.com/docs/api/reference/): Static HTML from the OpenAPI snapshot of 2026-10-30. Every operation, parameter, request body, response and schema. No JavaScript needed. - [openapi.json](https://faxforagents.com/openapi.json) and [openapi.yaml](https://faxforagents.com/openapi.yaml): the OpenAPI 3.1 document, snapshot of 2026-10-30. - [pricing.json](https://faxforagents.com/pricing.json) and [coverage.json](https://faxforagents.com/coverage.json): the rate table and the coverage file the server reads. - [llms.txt](https://faxforagents.com/llms.txt) and [llms-full.txt](https://faxforagents.com/llms-full.txt): this site for a tool that reads Markdown. Every page also has a Markdown twin at `index.md`. ## Limits in force From GET /v1/capabilities and /facts.json, as of 2026-10-28 | Limit | Value | | --- | --- | | Pages per document | 200 | | Upload size | 25 MiB | | HTML document size | 2 MiB | | HTML render time | 45 s | | Quote validity | 10 min | | Signed download URL validity | 10 min | | Requests per minute per API key | 600 | | Webhook timestamp tolerance | 300 s | Formats: PDF, PNG, JPG and TIFF uploads, and text, Markdown and HTML rendered to PDF by the service. Office formats are not accepted. The [send page](https://faxforagents.com/docs/send-fax/) has the rules for each. --- # Quickstart > A test key, a document, a quote, a sandbox send and the receipt. Five calls. Nothing leaves the box and nothing is charged. Page: https://faxforagents.com/docs/quickstart/ Last changed: 2026-10-28 Updated Oct 28, 2026 Five calls from a terminal. Everything below runs on the sandbox with a test key, so nothing leaves the box and nothing is charged. ## 1. Get a test API key Sign in at [app.faxforagents.com](https://app.faxforagents.com/sign-in) with a magic link, a passkey, GitHub or Google. Your organization comes with a sandbox wallet of $100.00 test credit. Open API keys and create one. The secret starts with `ffa_test_` and is shown once. Shell ``` `export FFA_KEY=ffa_test_... export FFA=https://api.faxforagents.com/v1` ``` ## 2. Create a document The fastest way is to let the service render text. A PDF goes through an upload session instead, shown on the [send page](https://faxforagents.com/docs/send-fax/). Shell ``` `curl -s $FFA/documents \ -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"markdown": "# Purchase order 4411\n\nPlease confirm by return fax.", "filename": "po-4411"}'` ``` The answer carries the document id (`doc_...`), its sha256 and its page count. An encrypted or unreadable file is refused with a named code and nothing is stored. ## 3. Quote it Shell ``` `curl -s $FFA/fax-quotes \ -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"document_id": "doc_...", "to": "+15550000001"}'` ``` The quote binds the document hash, the destination, the sender, the pages and the amount for ten minutes. On the sandbox the amount is $0.000. `+15550000001` is the sandbox number that always delivers. The [sandbox page](https://faxforagents.com/docs/sandbox/) lists the others. ## 4. Send it Shell ``` `curl -s $FFA/faxes \ -H "Authorization: Bearer $FFA_KEY" \ -H "Idempotency-Key: quickstart-1" \ -H "Content-Type: application/json" \ -d '{"quote_id": "quote_..."}'` ``` The answer is the job, state `queued`. Run the same call again with the same `Idempotency-Key` and you get the same job back with `Idempotent-Replayed: true`, not a second fax. ## 5. Read the receipt Shell ``` `curl -s $FFA/faxes/fax_.../receipt -H "Authorization: Bearer $FFA_KEY"` ``` Within a worker pass the sandbox job moves from `queued` through `active` to `delivered`. The receipt then lists the provider reference, the pages, the document hash, the amount captured and the time. Before that the receipt is empty and `GET /v1/faxes/{id}` shows the state. The word delivered never appears before the evidence does. ## Next - Register a webhook and [verify its signature](https://faxforagents.com/docs/webhooks/), so you do not have to poll. - Rehearse a busy line, a partial delivery and an unknown outcome on the [sandbox](https://faxforagents.com/docs/sandbox/). - Connect [Claude Code or Cursor](https://faxforagents.com/docs/mcp-setup/) to the MCP server and do the same five steps as tool calls. - Fund the live wallet when you are ready. A live send needs an [approval](https://faxforagents.com/docs/approvals/) or a [policy](https://faxforagents.com/docs/policies/). --- # Send a fax > The send contract. PDF and image uploads, text, Markdown and HTML rendered, the quote that binds the price, the Idempotency-Key and the six job states. Page: https://faxforagents.com/docs/send-fax/ Last changed: 2026-10-28 Updated Oct 28, 2026 A send is a document, a quote and a job. The document is immutable once created, the quote fixes the price, and the job records what the provider did. This page is the contract for all three. ## Documents A document is created from one of five sources and ends as one immutable PDF with a sha256 and a page count. The limit is 200 pages and 25 MiB. Document sources | Source | Body of POST /v1/documents | Notes | | --- | --- | --- | | PDF upload | `{"upload_id": "up_..."}` | validated with a PDF parser first. Encrypted or unreadable files are refused with `encrypted_pdf` or `unsupported_format` | | Image upload | `{"upload_id": "up_..."}` | PNG, JPG or TIFF, declared as `content_type` on the upload session. Converted to grayscale letter pages, see Images below | | Text | `{"text": "..."}` | rendered to pages by the service | | Markdown | `{"markdown": "..."}` | headings, lists, emphasis and tables | | HTML | `{"html": "..."}` | rendered in a headless browser with scripts off and no network, see HTML below | Office formats are not accepted. Convert a Word or Excel file to PDF first. ### Uploading a PDF Shell ``` `# 1. open a session (25 MiB cap, one hour) curl -s $FFA/uploads -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"filename": "contract.pdf", "size_bytes": 184233}' # answer: id (up_...) and a signed upload_url # 2. send the bytes to the signed upload_url (no key) curl -s -X PUT "$UPLOAD_URL" --data-binary @contract.pdf # 3. finish; the size and the optional sha256 are checked curl -s -X POST $FFA/uploads/up_.../complete -H "Authorization: Bearer $FFA_KEY" # 4. make the document curl -s $FFA/documents -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"upload_id": "up_..."}'` ``` `GET /v1/documents/{id}/pages` returns page previews behind signed URLs that live ten minutes. `DELETE` removes the bytes and the previews. The hash and the page count stay on any job that used it. ### HTML Shell ``` `# inline CSS and data: images print; an external stylesheet, image or font is refused curl -s $FFA/documents -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"html": "

Purchase order 4411

Please confirm by return fax.

", "filename": "po-4411"}' # answer: id (doc_...), source "html", page_count, sha256, and validation with resources_blocked when anything was refused` ``` The HTML is rendered in a headless browser with script execution off and no network. Inline CSS and `data:` images print. An external stylesheet, image or font is refused and counted, and the count comes back on the document's `validation` as the warning `resources_blocked`. Pages are US letter with a 0.4 inch margin unless the document's own `@page` rule says otherwise. The HTML is at most 2 MiB. A document that takes longer than 45 seconds to render is refused with 422 `conversion_failed`. A renderer that is down answers 503 `render_unavailable`, and that call is retryable. Fonts inside the renderer: Liberation, DejaVu, Noto Sans, Noto Serif and Noto CJK. Liberation is the metric twin of Arial, Times New Roman and Courier New. DejaVu, Noto Sans and Noto Serif cover Latin, Greek, Cyrillic, Hebrew, Arabic and most other scripts, and Noto CJK covers Chinese, Japanese and Korean. Name one of these in your CSS, or the font it stands in for. The MCP tool `prepare_fax` takes `html` next to `text` and `markdown`, and the CLI takes `ffa documents upload letter.html`. ### Images Shell ``` `# 1. the same upload session as a PDF, with the content type of the image curl -s $FFA/uploads -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"filename": "scan.tiff", "size_bytes": 912377, "content_type": "image/tiff"}' # 2. send the bytes, then complete the session curl -s -X PUT "$UPLOAD_URL" --data-binary @scan.tiff curl -s -X POST $FFA/uploads/up_.../complete -H "Authorization: Bearer $FFA_KEY" # 3. make the document: one grayscale letter page per TIFF frame curl -s $FFA/documents -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"upload_id": "up_..."}' # answer: source "image", page_count, and validation with image_rotated or image_downsampled when either applied` ``` The upload session takes `content_type` `image/png`, `image/jpeg` or `image/tiff` next to `application/pdf`, and the same `upload_id` makes the document. Every page becomes 8-bit grayscale, because fax is monochrome, on a US letter portrait page with a quarter inch margin, scaled to fit. An image wider than it is tall is rotated to fill the page, with the warning `image_rotated`. A frame larger than 2550 by 3300 pixels is scaled down to 300 dpi, with the warning `image_downsampled`. Each frame of a multi-page TIFF is one page. PNG and JPEG use the first frame only. A frame over 50 million pixels is refused with `image_too_large`, and bytes that do not match the declared type with `unsupported_format`. The document's `source` is `image`, and the limits stay 200 pages and 25 MiB. The dashboard's upload button takes these files too, and the CLI takes `.png`, `.jpg`, `.jpeg`, `.tif` and `.tiff`. The byte, time and pixel limits above are in [facts.json](https://faxforagents.com/facts.json) with the constant each one is read from, as of 2026-10-28. The page size, the margins and the 300 dpi ceiling are the converter's defaults in the same release, v1.1.0. ## Quotes Shell ``` `curl -s $FFA/fax-quotes -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"document_id": "doc_...", "to": "+14155550142", "from": "+16285550199", "cover": {"to_name": "Accounts", "subject": "PO 4411"}}'` ``` The quote carries the route, the document pages, the cover page when one is asked for and the billable pages. Also the amount in micro-USD, the rate table version and `expires_at`, 10 minutes out. It also says `authorization_required`: true for a live key with no policy that allows this send. A quote outside the published routes is refused with `unsupported_destination`. ## The send Shell ``` `curl -s $FFA/faxes -H "Authorization: Bearer $FFA_KEY" \ -H "Idempotency-Key: po-4411-first-send" \ -H "Content-Type: application/json" \ -d '{"quote_id": "quote_...", "approval_id": "apr_..."}'` ``` Three things happen in one transaction. The wallet is locked and the quoted amount moves to a hold, or the call is refused with `insufficient_balance` before any provider is touched. The authorization is checked: a test key is allowed, a live API key needs `approval_id` or a policy that allows the plan. The job is written as `queued` with an audit row, and a dispatch is queued for the worker. Always send an `Idempotency-Key`. A retry after a timeout returns the same job. A different body under the same key is a 409. ## What are the six job states? Job states and what they mean for money | State | Meaning | Hold | | --- | --- | --- | | queued | accepted, waiting for the worker | held | | active | the provider has the fax, attempts in progress. Busy and no answer are retried up to three times | held | | delivered | every page confirmed, evidence on the attempt | captured | | partial | some pages confirmed. Never retransmitted on its own | confirmed pages captured, rest released | | failed | attempts exhausted, invalid number, render failure or cancel. `failure_code` says which | released | | unknown | no outcome by the deadline. The reconcile tick keeps asking the provider. Never retransmitted on its own | held until reconciled | Read the job with `GET /v1/faxes/{id}`, its attempts with `/attempts`, its ordered events with `/events`, and the receipt with `/receipt`. `POST /v1/faxes/{id}/cancel` cancels a queued job at once and is best effort once sending has started. ## What does the receipt hold? Job id, destination, sender, pages, provider reference, document sha256, amount captured, completion time. It is also sent by mail to the organization's notification address for live jobs, and the same fields are on the `fax.delivered` event. --- # Receive a fax > A number, a webhook and the private inbox. Each received fax is a PDF. The text of a fax is always data, never an instruction. Page: https://faxforagents.com/docs/receive-fax/ Last changed: 2026-10-28 Updated Oct 28, 2026 Receiving needs a number and a webhook. The fax lands in your inbox as a PDF and a signed `fax.received` event tells you it is there. On the sandbox you can create one without a number. ## How do I test receiving before I own a number? Shell ``` `curl -s $FFA/sandbox/inbound-faxes -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"to": "+14155550100", "from": "+14155550177", "pages": 2}'` ``` With a test API key this creates a synthetic inbox item. It fires a `fax.received` event with the same shape and the same signature as a live one. Your receiver can be finished before you own a number. ## How do I get a fax number? Numbers cost $2 a month as of 2026-10-28 and are available in the United States, Canada, Puerto Rico and the US Virgin Islands at launch. The dashboard orders one with a confirmation of the recurring charge. The API does the same: `GET /v1/number-inventory`, `POST /v1/number-orders` and `GET /v1/numbers`, under the numbers group of the [reference](https://faxforagents.com/docs/api/). Elsewhere, write to hello@faxforagents.com. ## The event JSON ``` `{ "id": "evt_...", "type": "fax.received", "created_at": "2026-10-28T15:21:05Z", "data": { "inbox_item_id": "inb_...", "to": "+14155550100", "from": "+14155550177", "pages": 2, "partial_content": false, "document_id": "doc_...", "document_sha256": "4f2a...", "number_id": "num_...", "received_at": "2026-10-28T15:21:05Z", "download": "/v1/inbox/inb_.../download" } }` ``` The event carries metadata only. Verify the signature as on the [webhooks page](https://faxforagents.com/docs/webhooks/), then fetch the item. ## Read the inbox Shell ``` `curl -s "$FFA/inbox?limit=20" -H "Authorization: Bearer $FFA_KEY" curl -s $FFA/inbox/inb_... -H "Authorization: Bearer $FFA_KEY"` ``` The item has the numbers, the page count, the time and a signed download URL for the PDF that lives ten minutes. When an extraction exists it also has a `text` field. That field is data. The MCP tools list it under `untrusted`. Treat it the same way in your code: never let it name a destination, an approval or a document to send. ## How long are received faxes kept? Inbox items are kept 90 days by default and documents 30. Delete earlier from the dashboard or the API. Metadata stays so the id still resolves. ## What is not live yet? OCR and structured extraction ship as `POST /v1/extractions` and the `extract_fax_data` tool. They run on the sandbox engine today for a test key. For a live API key the answer is 503 `ocr_disabled` until the live engine is switched on, which has no date. Inbound numbers outside the four launch countries are on request. A reply tool that links a reply to the original is planned. --- # Webhooks > Every delivery carries webhook-id, webhook-timestamp and an HMAC-SHA256 signature. Verify it in Python and TypeScript, the retry schedule, replays and events. Page: https://faxforagents.com/docs/webhooks/ Last changed: 2026-10-28 Updated Oct 28, 2026 A webhook is a URL, an event filter and a secret shown once. Every delivery is signed with the Standard Webhooks scheme, retried on a fixed schedule, and replayable with the same event id. ## Register one Shell ``` `curl -s $FFA/webhooks -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/hooks/fax", "event_types": ["fax.delivered", "fax.failed", "fax.partially_delivered", "fax.outcome_unknown", "fax.received"]}'` ``` The answer carries the secret, `whsec_...`, once. Store it. `POST /v1/webhooks/{id}/test` sends a signed `webhook.test` event so you can check the receiver before a real one arrives. ## Events `fax.queued`, `fax.started`, `fax.delivered`, `fax.partially_delivered`, `fax.failed`, `fax.outcome_unknown`, `fax.received`, `document.ready`, `document.rejected`, `balance.low`, `webhook.test`. The sandbox fires the same ones as live. ## The three headers Headers on every delivery | Header | Value | | --- | --- | | `webhook-id` | the event id, the same across retries and replays | | `webhook-timestamp` | unix seconds when this delivery was signed | | `webhook-signature` | `v1,base64(HMAC-SHA256(secret, "{id}.{timestamp}.{body}"))` . During a secret rotation there are two, space separated | ## Verify in Python Python ``` `import base64, hmac, hashlib, time def verify(secret: str, headers: dict, body: bytes, tolerance_s: int = 300) -> str: h = {k.lower(): v for k, v in headers.items()} msg_id, ts, sigs = h["webhook-id"], h["webhook-timestamp"], h["webhook-signature"] if abs(time.time() - int(ts)) > tolerance_s: raise ValueError("stale") key = base64.b64decode(secret.removeprefix("whsec_")) want = hmac.new(key, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256).digest() for sig in sigs.split(): if sig.startswith("v1,") and hmac.compare_digest(base64.b64decode(sig[3:]), want): return msg_id raise ValueError("no signature matched")` ``` ## Verify in TypeScript TypeScript ``` `import { createHmac, timingSafeEqual } from 'node:crypto' export function verify(secret: string, headers: Record, body: Buffer, toleranceS = 300): string { const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v])) const id = h['webhook-id'], ts = h['webhook-timestamp'], sigs = h['webhook-signature'] if (Math.abs(Date.now() / 1000 - Number(ts)) > toleranceS) throw new Error('stale') const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64') const want = createHmac('sha256', key).update(`${id}.${ts}.`).update(body).digest() for (const sig of sigs.split(' ')) { if (!sig.startsWith('v1,')) continue const got = Buffer.from(sig.slice(3), 'base64') if (got.length === want.length && timingSafeEqual(got, want)) return id } throw new Error('no signature matched') }` ``` Verify over the raw body bytes, before any JSON parsing. Reject a timestamp older than 300 seconds. Dedupe your handler on `webhook-id` so a retry or a replay is a no-op. ## How are deliveries retried? A non-2xx answer schedules the next attempt from the row's own schedule: at once, 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours. Every attempt is on `GET /v1/webhook-deliveries` with its status and the answer's first bytes, never the secret. `POST /v1/webhook-deliveries/{id}/replay` sends the event again with the same `webhook-id`. ## What happens after an outage? `GET /v1/events?cursor=` serves the same events in sequence order, oldest first, with `next_cursor` on each page and an optional `type` filter. Walk it from the last cursor you stored and you have everything you missed, whether or not the webhook delivered. ## How do I rotate a webhook secret? `POST /v1/webhooks/{id}/rotate-secret` returns a new secret. The old one keeps signing for 24 hours, so both signatures are on each delivery and you can switch without a gap. --- # Approvals > An approval covers one document hash, one destination, one sender, one amount and an expiry. Any change invalidates it. Request, decide and send with one. Page: https://faxforagents.com/docs/approvals/ Last changed: 2026-10-30 Updated Oct 30, 2026 An approval is a person's yes to one exact send. The server copies the quote into the request, a person decides in the dashboard, and the agent sends with the approval id. Requesting grants nothing. One approval, one send. ## What does an approval bind? The request copies `quote_id`, `document_id`, `document_sha256`, `to`, `from`, `amount_micro`, `currency`, `billable_pages` and `expires_at`. It expires with the quote, ten minutes after the quote was made. If the agent needs longer it quotes again and asks again. ## Request Shell ``` `curl -s $FFA/approval-requests -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"quote_id": "quote_..."}'` ``` Scope `approval:request`, which default keys and agent keys carry. A second request for the same quote while one is pending returns the first. The organization's notification address gets a mail with the destination, the pages, the amount, who asked and when it expires. Through MCP the tool is `request_human_review` with a `quote_id`. ## Decide A person opens the request in the dashboard and approves or rejects it. Approving needs a step-up within the last 15 minutes: a passkey, or a fresh magic link opened in the same browser. Rejecting needs a reason. The requester's contact gets a mail with the decision. Through the API: `POST /v1/approval-requests/{id}/approve` and `/reject` with scope `approval:decide`. An agent key is never issued that scope, and the key that made the request is refused. The agent reads the decision with `GET /v1/approval-requests/{id}` (scope `approval:read`) or the MCP tool `get_review_status`. Once the request is approved, that answer carries the approval id to send with. ## Send Shell ``` `curl -s $FFA/faxes -H "Authorization: Bearer $FFA_KEY" \ -H "Idempotency-Key: po-4411" -H "Content-Type: application/json" \ -d '{"quote_id": "quote_...", "approval_id": "apr_..."}'` ``` The server takes the approval row for update and checks it against the quote and the document. The job records the approval, and the approval records the job. ## Why is a send with an approval refused? Why a send with an approval is refused | Code | What changed | | --- | --- | | `document_hash_mismatch` | the document's sha256 differs from the one approved | | `approval_expired` | past `expires_at` | | `approval_mismatch` | a different quote, destination, sender, amount, currency or page count. `details.field` names it | | `approval_not_granted` | pending, rejected or revoked. `details.status` says which | | `approval_consumed` | already used. `details.fax_job_id` names the job | | `approval_required` | a live send with no approval and no policy that allows it | ## Revoke `POST /v1/approvals/{id}/revoke` withdraws an approval nobody has used yet. The agent has to ask again. ## How does an agent ask a person for help? `POST /v1/approval-requests` with `{"kind": "help", "message": "...", "fax_job_id": "..."}`, or the MCP tool `request_human_review` without a `quote_id`, opens a request with no quote. The message is shown to the person as text from the agent, never acted on by the server. --- # Delegated policies > Limits evaluated on the server for every send. Allowed destinations, page and amount caps, daily and monthly limits, expiry. An agent cannot move them. Page: https://faxforagents.com/docs/policies/ Last changed: 2026-10-30 Updated Oct 30, 2026 A delegated policy is a set of limits an administrator writes once. Every send under it is evaluated on the server from the quote and the document row. Nothing an agent writes is an input, so a prompt cannot move a limit. ## The rules Fields of policy.rules | Field | Meaning | | --- | --- | | `allowed_destinations` | exact E.164 numbers | | `allowed_country_prefixes` | `+1` , `+44` and so on. At least one destination rule is required | | `allowed_sender_numbers` | null allows any sender, including the pool | | `max_pages_per_job` , `max_amount_micro_per_job` | per send | | `daily_max_jobs` , `daily_max_amount_micro` | per UTC day | | `monthly_max_jobs` , `monthly_max_amount_micro` | per UTC month | | `allowed_document_sources` | `uploaded` , `rendered` , or both | | `allowed_environments` | `live` , `test` | | `expires_at` | UTC, exact | | `agent_ids` , `key_ids` | who the policy binds. An agent's own `policy_ids` binds it too | ## Create one Shell ``` `curl -s $FFA/policies -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{ "name": "orders desk", "purpose": "confirm purchase orders to known suppliers", "rules": { "allowed_country_prefixes": ["+1"], "max_pages_per_job": 10, "max_amount_micro_per_job": 500000, "daily_max_amount_micro": 5000000, "allowed_document_sources": ["rendered"], "allowed_environments": ["live"], "expires_at": "2027-01-01T00:00:00Z", "agent_ids": ["agent_..."] } }'` ``` Scope `policy:admin`, never on an agent key. `PATCH` makes version n+1. Every version is immutable and a job records the version that allowed it. ## How is a send evaluated? In this order, and the first failing rule decides. Enabled, applies to this key or agent, expiry, environment, destination, sender, pages, amount, document source, daily jobs, daily amount, monthly jobs, monthly amount. Caps count the planned send, so a daily cap of 3 allows the third and refuses the fourth. The counters are locked before the wallet, so two concurrent sends serialize on the cap. ## Dry run Shell ``` `curl -s $FFA/policy-evaluations -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"quote_id": "quote_..."}'` ``` Allow or deny, with the first failing rule and every denial listed, `executed: false`. Through MCP the verdict rides on the quote: `quote_fax` answers with a `policy` object, the same evaluation the send runs. `GET /v1/policies/effective` tells an API key what it may do on its own and whether a person is needed for the rest. ## Budgets Separate from policies, a budget is a daily or monthly spending cap per organization, project or agent, per environment. The hold refuses `budget_exceeded` or `agent_budget_exceeded` before any money moves. Writing a budget needs `billing:write`, which an agent key is never issued. --- # Agent identities > Each agent is a named principal with its own key. Scopes it cannot widen, a budget it cannot raise. Disabling an agent revokes its keys at once. Page: https://faxforagents.com/docs/agents/ Last changed: 2026-10-28 Updated Oct 28, 2026 An agent identity is a named principal with its own key, its own scopes and its own budget. Its actions are audited as `agent:`. Disabling it revokes its keys in the same transaction. ## How do I register an agent? Shell ``` `curl -s $FFA/agents -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "orders desk", "purpose": "confirm purchase orders", "contact_email": "ops@example.com", "policy_ids": ["pol_..."]}' curl -s $FFA/agents/agent_.../keys -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"label": "orders desk key"}'` ``` Scope `agent:admin` for the agent and `key:admin` for the key. The purpose is shown to people and never evaluated. The key is shown once. ## Which scopes can an agent have? An API key carries a set of scopes. The default set reads and sends. - Faxes: `fax:send`, `fax:quote`, `fax:read`, `fax:cancel` - Documents: `document:read`, `document:write` - Reads: `inbox:read`, `billing:read`, `approval:read`, `policy:read`, `account:read` - Requests: `approval:request`, `sandbox:write` A read-only key is the default set minus the write and send scopes. An agent key is never issued `approval:decide`, `key:admin`, `policy:admin`, `agent:admin`, `billing:write` or `webhook:admin`. Asking for one is refused, not trimmed. Every route that does those things also refuses any principal with an agent id. An API key minted by hand with every scope still cannot. ## How are keys created, rotated and revoked? `POST /v1/api-keys` creates a key. `POST /v1/api-keys/{id}/rotate` returns a new secret and keeps the old one working for 24 hours. `DELETE` revokes at once. Keys are stored as a peppered HMAC, so a database dump does not leak them. A test key is tied to the sandbox and the test wallet. ## What does the activity trail show? `GET /v1/agents/{id}/activity` is the agent's audit trail: every row it caused, every row about it, metadata only. Pair it with a budget on the agent and a policy bound to it, and you have the three controls the [agents page](https://faxforagents.com/fax-for-ai-agents/) describes. --- # Errors and job states > The error envelope and every code with its action. The six job states. When unknown means wait, why partial never retransmits, how to avoid duplicates. Page: https://faxforagents.com/docs/errors/ Last changed: 2026-10-28 Updated Oct 28, 2026 Every non-2xx answer is one envelope. The code says what happened. `retryable` says whether the same call can be tried again. `correlation_id` is what to quote when you write to us. JSON ``` `{"error": {"code": "insufficient_balance", "message": "the wallet holds $0.80 and the quote is $1.17", "retryable": false, "correlation_id": "req_..."}}` ``` ## Codes Error codes and the action for each | Code | Status | What to do | | --- | --- | --- | | `invalid_token` | 401 | the key is missing, revoked or malformed. Get a key from the dashboard | | `scope_missing` | 403 | the API key lacks the scope. The message names it | | `agent_forbidden` | 403 | an agent key is not allowed on this route. A person does this | | `not_found` | 404 | no such resource in your organization. A foreign id looks the same | | `invalid_request` | 400 or 422 | the body failed validation. `details` lists the fields | | `conflict` | 409 | a state that forbids the call, or a different body under the same Idempotency-Key. | | `encrypted_pdf` , `unsupported_format` , `page_limit_exceeded` | 422 | fix the document and create it again. Nothing was stored or charged | | `conversion_failed` , `image_too_large` | 422 | the HTML took longer than 45 seconds to render, or an image frame is over 50 million pixels. Make it smaller and create it again | | `render_unavailable` | 503 | the HTML renderer is down. Retryable | | `live_provider_unavailable` | 503 | live sending is not enabled on this server yet, so live quotes, sends and number orders are refused. Test keys keep working. Not retryable | | `unsupported_destination` | 422 | no published route, or excluded by policy. Not retryable | | `quote_expired` | 409 | quote again. Quotes last ten minutes | | `insufficient_balance` | 402 | top up the wallet. The provider was not called | | `budget_exceeded` , `agent_budget_exceeded` | 403 | the cap for the period is reached. A person raises it or waits | | `approval_required` | 403 | a live send with no approval and no allowing policy. Ask a person | | `approval_*` | 409 | see the [approvals page](https://faxforagents.com/docs/approvals/) . Quote and ask again | | `sandbox_only` , `environment_not_billable` | 403 | the route is for the other kind of key. | | `payload_too_large` | 413 | 25 MiB upload cap | | `rate_limited` | 429 | 600 requests a minute per key. Retryable after `RateLimit-Reset` | | `billing_unavailable` , `temporarily_unavailable` , `provider_timeout` | 502 or 503 | retryable with the same Idempotency-Key. | | `internal_error` | 500 | retryable. Quote the correlation id if it persists | ## Why is a failed fax not an HTTP error? A send answers 201 with a job in `queued`. What happens after is a state on the job, carried by events and webhooks, not an HTTP error. What your code should do per state | State | Do | | --- | --- | | queued , active | wait for the webhook or poll `GET /v1/faxes/{id}` . A fax takes a minute or more on the line. Busy and no answer are retried up to three times by the service | | delivered | read the receipt and store the provider reference | | partial | read `pages_sent` . Decide whether to send the missing pages as a new fax. The service never retransmits on its own | | failed | read `failure_code` : `busy` , `no_answer` , `invalid_number` , `render_failed` , `provider_error` , `canceled` . Nothing was charged | | unknown | wait. The hold stays and the reconcile tick keeps asking the provider. Do not send again from your side: a duplicate fax is the one outcome worse than a late one. If it stays unknown, write to us with the job id | ## How do I avoid sending a fax twice? Send every mutating call with an `Idempotency-Key`, derived from your own record such as the order id. A timeout on your side then becomes a retry that returns the same job. Never create a new quote and send after a timeout without first reading the job list for your key. --- # Sandbox > Test keys send to scripted numbers: delivered, busy then delivered, partial, no answer, unknown, provider timeout, invalid number. Same events, no cost. Page: https://faxforagents.com/docs/sandbox/ Last changed: 2026-10-28 Updated Oct 28, 2026 A test key (`ffa_test_...`) sends only to the sandbox. No carrier, no cost, nothing leaves the box. The outcome is decided by the destination number or by a scenario you select. Every state a real fax can end in can be rehearsed, including the ones you hope never to see. ## Destinations Scripted sandbox destinations | Destination | Scenario | What happens | | --- | --- | --- | | `+15550000001` | `delivered` | every page on the first attempt, evidence on the attempt | | `+15550000002` | `busy_then_delivered` | busy on attempt 1, delivered on attempt 2 | | `+15550000003` | `partial` | the call drops after 3 pages (N minus 1 on a short fax). Those pages charged, the rest released | | `+15550000004` | `no_answer` | nobody answers on any of the 3 bounded attempts. failed, failure_code no_answer | | `+15550000005` | `unknown` | the provider accepts the fax and never reports. unknown after the deadline, failure_code callback_missing, hold kept | | `+15550000006` | `provider_timeout` | the provider does not answer the send call. unknown, failure_code provider_timeout | | `+15550000007` | `invalid_number` | rejected at once. failed, no retry | | `any other +1 number` | `delivered` | the default | A number outside `+1` on one of the [published routes](https://faxforagents.com/pricing/) is quoted and delivered the same way, at $0 on the sandbox. A number on no route, or on an excluded one, is refused with `unsupported_destination`. ## How do I select a scenario? Shell ``` `curl -s $FFA/sandbox/scenarios -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"scenario": "partial", "destination": "+12025550100"}'` ``` That destination behaves that way for 24 hours. Without `destination` it applies to every send of the organization that has no more specific fixture. `GET /v1/sandbox/scenarios` lists the catalog, the table above and the active fixtures. With the CLI: `ffa sandbox scenario partial --to +12025550100`. A live key is refused on these routes with `sandbox_only`. ## How fast is the sandbox, and what does it cost? The sandbox answers the send call at once, so one worker pass takes a job from `queued` to its outcome. Retries are scheduled after 60 seconds and the unknown deadline is 600 seconds, as in production. Sandbox pages cost $0.000. Every test organization starts with a seeded wallet of $100.00 test credit. The hold, capture and release path runs exactly as it will with real money. `GET /v1/balance` with a test API key shows that wallet. ## Inbound and reset `POST /v1/sandbox/inbound-faxes` creates a synthetic inbox item and a `fax.received` event with the live shape. `DELETE /v1/sandbox/data` removes the organization's test faxes, attempts, events, deliveries, quotes, documents, uploads, synthetic inbox items and fixtures. It brings the test wallet back to the seed with one balancing ledger entry. ## Webhooks Sandbox sends fire the same events with the same signatures as live ones: `fax.queued`, `fax.started`, `fax.delivered`, `fax.partially_delivered`, `fax.failed`, `fax.outcome_unknown`. A receiver can be finished end to end before the first live page. --- # MCP setup > Connect your client to https://mcp.faxforagents.com/mcp/ with an API key. Copy-ready configuration for Claude Code, Claude Desktop, Cursor, Codex and n8n. Page: https://faxforagents.com/docs/mcp-setup/ Last changed: 2026-10-30 Updated Oct 30, 2026 The server is at `https://mcp.faxforagents.com/mcp/`, with the trailing slash. It speaks Streamable HTTP and takes your API key as a bearer token on every request. A test key is the free sandbox. There is no OAuth yet. ## Claude Code Shell ``` `claude mcp add --transport http faxforagents https://mcp.faxforagents.com/mcp/ \ --header "Authorization: Bearer ffa_test_..."` ``` Then in a session: "quote a two page fax to +15550000001 and send it". Claude calls `prepare_fax`, `quote_fax` and `send_approved_fax` and reads the state back with `get_fax_status`. ## Claude Desktop Settings, Connectors, Add custom connector. Name `faxforagents`, URL `https://mcp.faxforagents.com/mcp/`. Claude Desktop asks for OAuth on a remote server and this server has none yet. Use the stdio bridge below until the OAuth release lands. JSON ``` `{ "mcpServers": { "faxforagents": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.faxforagents.com/mcp/", "--header", "Authorization: Bearer ${FFA_KEY}"], "env": { "FFA_KEY": "ffa_test_..." } } } }` ``` The file is `claude_desktop_config.json` under the Claude application support folder. ## Cursor `.cursor/mcp.json` in the project, or the global one in `~/.cursor/`: JSON ``` `{ "mcpServers": { "faxforagents": { "url": "https://mcp.faxforagents.com/mcp/", "headers": { "Authorization": "Bearer ffa_test_..." } } } }` ``` ## Codex Shell ``` `export FFA_KEY=ffa_test_... codex mcp add faxforagents --url https://mcp.faxforagents.com/mcp/ --bearer-token-env-var FFA_KEY` ``` Or in `~/.codex/config.toml`: TOML ``` `[mcp_servers.faxforagents] url = "https://mcp.faxforagents.com/mcp/" bearer_token_env_var = "FFA_KEY"` ``` ## n8n Add an MCP Client Tool node to an agent workflow. Endpoint `https://mcp.faxforagents.com/mcp/`, transport HTTP Streamable, authentication Header Auth with name `Authorization` and value `Bearer ffa_test_...`. Keep the key in an n8n credential, not in the node. ## How do I check it works? Ask the client to call `get_guide` with no arguments. The answer is the manual's overview and proves the key and the URL. Then `get_fax_capabilities` shows the limits, the formats and the prices in force for that key. ## What should I tell the agent? Say what to fax and to whom. The tools do the rest in order: prepare, quote, send. A live API key needs an approval or a policy. The quote says so under `policy`, and `get_review_status` tells the agent when a person has decided. Do not paste an API key into a prompt. Put it in the client's configuration as above. --- # SDKs > Two packages named faxforagents, for Python and for Node and TypeScript, built from the OpenAPI document, published on 2026-10-30. REST works without them. Page: https://faxforagents.com/docs/sdks/ Last changed: 2026-10-30 Updated Oct 30, 2026 Two packages, both named `faxforagents`, both generated from the same OpenAPI document as this site. Both have been published since 2026-10-30. Each page shows the install line, the package's own quickstart and the REST calls that work without it. - [Python](https://faxforagents.com/docs/sdks/python/): `pip install faxforagents`, on [PyPI](https://pypi.org/project/faxforagents/). The source is at [github.com/faxforagents/sdk-python](https://github.com/faxforagents/sdk-python). - [TypeScript](https://faxforagents.com/docs/sdks/typescript/): `npm install faxforagents`, on [npm](https://www.npmjs.com/package/faxforagents). The source is at [github.com/faxforagents/sdk-typescript](https://github.com/faxforagents/sdk-typescript). Other languages use the [REST API](https://faxforagents.com/docs/api/) directly. Every call is one HTTP request with a bearer token, and the webhook verifier is twelve lines in any language with HMAC-SHA256. --- # Python SDK > The faxforagents package for Python, on PyPI since 2026-10-30: upload, quote, send, wait for the receipt, page through jobs. The REST calls work without it. Page: https://faxforagents.com/docs/sdks/python/ Last changed: 2026-10-30 Updated Oct 30, 2026 The Python package is named `faxforagents` and has been on PyPI since 2026-10-30. Install it with `pip install faxforagents`. Python 3.10 or later, two dependencies, `httpx` and `pydantic`. The REST calls further down work with `httpx` or `requests` as well, so nothing here depends on the package. ## Status Published. The package is built from the same OpenAPI document as this site and released through PyPI trusted publishing. The quickstart below is the one from the package's README. The source is at [github.com/faxforagents/sdk-python](https://github.com/faxforagents/sdk-python) and the package page is [pypi.org/project/faxforagents](https://pypi.org/project/faxforagents/). ## Install and send with the package Shell ``` `pip install faxforagents # the client pip install "faxforagents[cli]" # plus the faxforagents command` ``` Python ``` `import os from faxforagents import Client os.environ["FAXFORAGENTS_API_KEY"] = "ffa_test_..." # or export it in the shell with Client() as ffa: doc = ffa.documents.create(text="Hello from an agent.", filename="hello") print(doc.id, doc.page_count, doc.sha256[:12]) quote = ffa.quotes.create(document_id=doc.id, to="+15550000001") print(quote.amount_micro, "micro, expires", quote.expires_at) fax = ffa.faxes.create(quote_id=quote.id) # Idempotency-Key minted for you print(fax.id, fax.state) # queued, never delivered yet fax = ffa.faxes.wait(fax.id) # polls until a terminal state print(fax.state, fax.pages_sent, fax.evidence) # delivered, 1, {...} for job in ffa.faxes.iter(state="failed"): # follows next_cursor print(job.id, job.failure_code)` ``` A test key is tied to the sandbox, where the destination number picks the outcome. The same calls exist in async form, with `AsyncClient` and `await` on every call. ## Send a fax with httpx Python ``` `import httpx API = "https://api.faxforagents.com/v1" KEY = "ffa_test_..." # from the dashboard, never in source control h = {"Authorization": f"Bearer {KEY}"} with httpx.Client(base_url=API, headers=h, timeout=30) as c: doc = c.post("/documents", json={"markdown": "# PO 4411\n\nPlease confirm."}).raise_for_status().json() quote = c.post("/fax-quotes", json={"document_id": doc["id"], "to": "+15550000001"}).raise_for_status().json() job = c.post("/faxes", json={"quote_id": quote["id"]}, headers={"Idempotency-Key": "po-4411"}).raise_for_status().json() print(job["id"], job["state"]) # fax_..., queued` ``` ## Upload a PDF Python ``` `size = path.stat().st_size up = c.post("/uploads", json={"filename": path.name, "size_bytes": size}).raise_for_status().json() httpx.put(up["upload_url"], content=path.read_bytes(), timeout=120).raise_for_status() c.post(f"/uploads/{up['id']}/complete").raise_for_status() doc = c.post("/documents", json={"upload_id": up["id"]}).raise_for_status().json()` ``` ## How do I page through jobs? Python ``` `cursor = None while True: page = c.get("/faxes", params={"limit": 50, "cursor": cursor}).raise_for_status().json() for job in page["items"]: print(job["id"], job["state"]) cursor = page.get("next_cursor") if not cursor: break` ``` ## How do I verify a webhook? The reference verifier is on the [webhooks page](https://faxforagents.com/docs/webhooks/). The package ships the same function as `faxforagents.webhooks.verify`. ## Errors Every non-2xx body is the error envelope. Read `error.code` and `error.retryable`. Retry a retryable call with the same `Idempotency-Key`. The [errors page](https://faxforagents.com/docs/errors/) lists every code. --- # TypeScript SDK > The faxforagents package for Node and TypeScript, on npm since 2026-10-30: upload, quote, send, wait for the receipt, list jobs. The REST calls work without it. Page: https://faxforagents.com/docs/sdks/typescript/ Last changed: 2026-10-30 Updated Oct 30, 2026 The npm package is named `faxforagents` and has been on npm since 2026-10-30. Install it with `npm install faxforagents`. Node 18 or later, Deno, Bun, browsers and edge runtimes, no runtime dependencies. The REST calls further down work with `fetch` as well, so nothing here depends on the package. ## Status Published. The package is generated from the same OpenAPI document as this site, built with tsup and published with npm provenance. The quickstart below is the one from the package's README. The package page is [npmjs.com/package/faxforagents](https://www.npmjs.com/package/faxforagents). The MCP bridge for stdio clients will be `faxforagents-mcp`. The sources are at [github.com/faxforagents/sdk-typescript](https://github.com/faxforagents/sdk-typescript) and [github.com/faxforagents/mcp](https://github.com/faxforagents/mcp). ## Install and send with the package Shell ``` `npm install faxforagents` ``` TypeScript ``` `import { FaxForAgents } from 'faxforagents'; const ffa = new FaxForAgents(); // reads FAXFORAGENTS_API_KEY const doc = await ffa.documents.create({ text: 'Hello from an agent.', filename: 'hello' }); console.log(doc.id, doc.page_count, doc.sha256.slice(0, 12)); const quote = await ffa.quotes.create({ document_id: doc.id, to: '+15550000001' }); console.log(quote.amount_micro, 'micro, expires', quote.expires_at); let fax = await ffa.faxes.create({ quote_id: quote.id }); // Idempotency-Key minted for you console.log(fax.id, fax.state); // queued, never delivered yet fax = await ffa.faxes.wait(fax.id); // polls until a terminal state console.log(fax.state, fax.pages_sent, fax.evidence); // delivered, 1, {...} for await (const job of ffa.faxes.iter({ state: 'failed' })) { // follows next_cursor console.log(job.id, job.failure_code); }` ``` A test key is tied to the sandbox, where the destination number picks the outcome. ESM and CommonJS builds, types included. ## Send a fax with fetch TypeScript ``` `const API = 'https://api.faxforagents.com/v1' const KEY = process.env.FFA_KEY! // ffa_test_..., never in source control async function call(path: string, init: RequestInit = {}): Promise { const r = await fetch(API + path, { ...init, headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json', ...init.headers } }) if (!r.ok) throw new Error(`${r.status} ${await r.text()}`) return r.json() as Promise } const doc = await call<{ id: string }>('/documents', { method: 'POST', body: JSON.stringify({ markdown: '# PO 4411\n\nPlease confirm.' }) }) const quote = await call<{ id: string }>('/fax-quotes', { method: 'POST', body: JSON.stringify({ document_id: doc.id, to: '+15550000001' }) }) const job = await call<{ id: string; state: string }>('/faxes', { method: 'POST', headers: { 'Idempotency-Key': 'po-4411' }, body: JSON.stringify({ quote_id: quote.id }) }) console.log(job.id, job.state) // fax_..., queued` ``` ## Upload a PDF TypeScript ``` `import { readFile, stat } from 'node:fs/promises' const size = (await stat(file)).size const up = await call<{ id: string; upload_url: string }>('/uploads', { method: 'POST', body: JSON.stringify({ filename: 'contract.pdf', size_bytes: size }) }) await fetch(up.upload_url, { method: 'PUT', body: await readFile(file) }) await call(`/uploads/${up.id}/complete`, { method: 'POST' }) const doc = await call<{ id: string }>('/documents', { method: 'POST', body: JSON.stringify({ upload_id: up.id }) })` ``` ## How do I page through jobs? TypeScript ``` `let cursor: string | undefined do { const page = await call<{ items: { id: string; state: string }[]; next_cursor?: string }>(`/faxes?limit=50${cursor ? `&cursor=${cursor}` : ''}`) for (const job of page.items) console.log(job.id, job.state) cursor = page.next_cursor } while (cursor)` ``` ## How do I verify a webhook? The reference verifier is on the [webhooks page](https://faxforagents.com/docs/webhooks/). The package ships the same function as `verifyWebhook`. ## Errors Every non-2xx body is the error envelope. Read `error.code` and `error.retryable`. Retry a retryable call with the same `Idempotency-Key`. The [errors page](https://faxforagents.com/docs/errors/) lists every code. --- # API reference > From the OpenAPI 3.1 snapshot of 2026-10-30. 146 operations in 18 groups. Read it as plain HTML or open the interactive explorer. Page: https://faxforagents.com/docs/api/ Last changed: 2026-10-28 Updated Oct 28, 2026 The REST API is at `https://api.faxforagents.com/v1`. The reference is built from the server's own OpenAPI 3.1 document, snapshot of 2026-10-30, version 1.3.3. 146 operations in 18 groups and 147 schemas. ## How do I read the reference? - [The full reference as plain HTML](https://faxforagents.com/docs/api/reference/): every operation, parameter, body, response and schema. Loads with no JavaScript and reads well on a phone. - [openapi.json](https://faxforagents.com/openapi.json) and [openapi.yaml](https://faxforagents.com/openapi.yaml): the document itself, for a generator or a client. - The interactive explorer below, which loads a third-party viewer on demand. - [github.com/faxforagents/openapi](https://github.com/faxforagents/openapi): the same document in a public repository, with a changelog per version. ## How do I authenticate? `Authorization: Bearer ffa_test_...` or `X-API-Key: ffa_test_...` on every call. Test keys reach the sandbox, live keys reach the carrier. Signed upload and download URLs take no key. Every mutating call accepts an `Idempotency-Key`. ## Groups - [agents](https://faxforagents.com/docs/api/reference/#tag-agents) - [identity](https://faxforagents.com/docs/api/reference/#tag-identity) - [approvals](https://faxforagents.com/docs/api/reference/#tag-approvals) - [auth](https://faxforagents.com/docs/api/reference/#tag-auth) - [billing](https://faxforagents.com/docs/api/reference/#tag-billing) - [platform](https://faxforagents.com/docs/api/reference/#tag-platform) - [extractions](https://faxforagents.com/docs/api/reference/#tag-extractions) - [numbers](https://faxforagents.com/docs/api/reference/#tag-numbers) - [documents](https://faxforagents.com/docs/api/reference/#tag-documents) - [events](https://faxforagents.com/docs/api/reference/#tag-events) - [quotes](https://faxforagents.com/docs/api/reference/#tag-quotes) - [faxes](https://faxforagents.com/docs/api/reference/#tag-faxes) - [inbox](https://faxforagents.com/docs/api/reference/#tag-inbox) - [members](https://faxforagents.com/docs/api/reference/#tag-members) - [organizations](https://faxforagents.com/docs/api/reference/#tag-organizations) - [policies](https://faxforagents.com/docs/api/reference/#tag-policies) - [sandbox](https://faxforagents.com/docs/api/reference/#tag-sandbox) - [webhooks](https://faxforagents.com/docs/api/reference/#tag-webhooks) ## Interactive explorer The explorer is a 3 MB third-party viewer, loaded only when you ask for it, pinned to one version and checked against its published hash. It reads `/openapi.json` from this site and lets you try calls with your own key. Your API key goes to api.faxforagents.com and nowhere else. --- # Full API reference > Static HTML from the OpenAPI snapshot of 2026-10-30. Every operation, parameter, request body, response and schema. No JavaScript needed. Page: https://faxforagents.com/docs/api/reference/ Last changed: 2026-10-28 Updated Oct 28, 2026 Every operation of the Fax for Agents API as static HTML. 146 operations and 147 schemas from the OpenAPI document version 1.3.3, snapshot of 2026-10-30. The base URL is `https://api.faxforagents.com/v1`. The same document is at [/openapi.json](https://faxforagents.com/openapi.json). ## agents - `GET /v1/agents` List agent identities - `POST /v1/agents` Register an agent identity - `GET /v1/agents/{agent_id}` Read an agent with its keys and budgets - `PATCH /v1/agents/{agent_id}` Change an agent; disabling revokes its keys - `GET /v1/agents/{agent_id}/activity` The agent's audit trail - `POST /v1/agents/{agent_id}/keys` Mint a key for the agent (bounded scopes, secret shown once) ### GET `/v1/agents` List agent identities #### Responses GET /v1/agents responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/agents response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Agent | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/agents \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/agents` Register an agent identity #### Request body application/json, required POST /v1/agents request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `contact_email` | string or null | no | Where decided approvals are mailed. | | `name` | string | yes | | | `policy_ids` | array of string | no | | | `purpose` | string or null | no | Shown to humans, never evaluated. | #### Responses POST /v1/agents responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/agents response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `budget_id` | string or null | yes | | | `budgets` | array of object | yes | | | `contact_email` | string or null | yes | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `disabled_at` | string or null | yes | | | `enabled` | boolean | yes | | | `id` | string | yes | | | `keys` | array of object ApiKey | yes | | | `name` | string | yes | | | `policy_ids` | array of string | yes | | | `project_id` | string or null | yes | | | `purpose` | string or null | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/agents \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/agents/{agent_id}` Read an agent with its keys and budgets #### Parameters GET /v1/agents/{agent_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `agent_id` | path | string | yes | | #### Responses GET /v1/agents/{agent_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/agents/{agent_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `budget_id` | string or null | yes | | | `budgets` | array of object | yes | | | `contact_email` | string or null | yes | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `disabled_at` | string or null | yes | | | `enabled` | boolean | yes | | | `id` | string | yes | | | `keys` | array of object ApiKey | yes | | | `name` | string | yes | | | `policy_ids` | array of string | yes | | | `project_id` | string or null | yes | | | `purpose` | string or null | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/agents/AGENT_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### PATCH `/v1/agents/{agent_id}` Change an agent; disabling revokes its keys #### Parameters PATCH /v1/agents/{agent_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `agent_id` | path | string | yes | | #### Request body application/json, required PATCH /v1/agents/{agent_id} request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `contact_email` | string or null | no | | | `enabled` | boolean or null | no | false revokes every key of the agent. | | `name` | string or null | no | | | `policy_ids` | array of string or null | no | | | `purpose` | string or null | no | | #### Responses PATCH /v1/agents/{agent_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PATCH /v1/agents/{agent_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `budget_id` | string or null | yes | | | `budgets` | array of object | yes | | | `contact_email` | string or null | yes | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `disabled_at` | string or null | yes | | | `enabled` | boolean | yes | | | `id` | string | yes | | | `keys` | array of object ApiKey | yes | | | `name` | string | yes | | | `policy_ids` | array of string | yes | | | `project_id` | string or null | yes | | | `purpose` | string or null | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/agents/AGENT_ID \ -X PATCH \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/agents/{agent_id}/activity` The agent's audit trail #### Parameters GET /v1/agents/{agent_id}/activity parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `agent_id` | path | string | yes | | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/agents/{agent_id}/activity responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/agents/{agent_id}/activity response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object AgentActivity | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/agents/AGENT_ID/activity \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/agents/{agent_id}/keys` Mint a key for the agent (bounded scopes, secret shown once) #### Parameters POST /v1/agents/{agent_id}/keys parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `agent_id` | path | string | yes | | #### Request body application/json, required POST /v1/agents/{agent_id}/keys request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `environment` | string (live, test) | no | | | `expires_in_days` | integer or null | no | | | `label` | string | no | | | `scopes` | array of string or null | no | Omit for the default agent set. Never approval:decide, key:admin, policy:admin, agent:admin. | #### Responses POST /v1/agents/{agent_id}/keys responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/agents/{agent_id}/keys response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `label` | string | yes | | | `last_used_at` | string or null | yes | | | `prefix` | string | yes | | | `project_id` | string or null | yes | | | `revoked_at` | string or null | yes | | | `rotated_from_id` | string or null | yes | | | `scopes` | array of string | yes | | | `secret` | string | yes | Shown once. Store it now; it cannot be read back. | #### Example ``` `curl https://api.faxforagents.com/v1/agents/AGENT_ID/keys \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ## identity - `GET /v1/api-keys` List API keys (metadata only) - `POST /v1/api-keys` Create an API key (the secret is shown once) - `DELETE /v1/api-keys/{key_id}` Revoke a key - `POST /v1/api-keys/{key_id}/rotate` Rotate a key: a new secret, the old one works for 24 more hours - `GET /v1/me` Who am I ### GET `/v1/api-keys` List API keys (metadata only) #### Responses GET /v1/api-keys responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/api-keys response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object ApiKey | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/api-keys \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/api-keys` Create an API key (the secret is shown once) #### Request body application/json, required POST /v1/api-keys request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `environment` | string (live, test) | no | | | `expires_in_days` | integer or null | no | | | `label` | string | yes | | | `scopes` | array of string or null | no | Omit for the default agent set; '*' for all. | #### Responses POST /v1/api-keys responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/api-keys response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `label` | string | yes | | | `last_used_at` | string or null | yes | | | `prefix` | string | yes | | | `project_id` | string or null | yes | | | `revoked_at` | string or null | yes | | | `rotated_from_id` | string or null | yes | | | `scopes` | array of string | yes | | | `secret` | string | yes | Shown once. Store it now; it cannot be read back. | #### Example ``` `curl https://api.faxforagents.com/v1/api-keys \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/api-keys/{key_id}` Revoke a key #### Parameters DELETE /v1/api-keys/{key_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `key_id` | path | string | yes | | #### Responses DELETE /v1/api-keys/{key_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields DELETE /v1/api-keys/{key_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `deleted` | boolean | no | | | `id` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/api-keys/KEY_ID \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/api-keys/{key_id}/rotate` Rotate a key: a new secret, the old one works for 24 more hours #### Parameters POST /v1/api-keys/{key_id}/rotate parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `key_id` | path | string | yes | | #### Responses POST /v1/api-keys/{key_id}/rotate responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/api-keys/{key_id}/rotate response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `label` | string | yes | | | `last_used_at` | string or null | yes | | | `prefix` | string | yes | | | `project_id` | string or null | yes | | | `revoked_at` | string or null | yes | | | `rotated_from_id` | string or null | yes | | | `scopes` | array of string | yes | | | `secret` | string | yes | Shown once. Store it now; it cannot be read back. | #### Example ``` `curl https://api.faxforagents.com/v1/api-keys/KEY_ID/rotate \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/me` Who am I #### Responses GET /v1/me responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/me response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `actor` | string | yes | | | `environment` | string (live, test) | yes | | | `key_id` | string | yes | | | `key_prefix` | string | yes | | | `org_id` | string | yes | | | `project_id` | string | yes | | | `scopes` | array of string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/me \ -H "Authorization: Bearer ffa_test_..."` ``` ## approvals - `GET /v1/approval-requests` Approval requests, pending first - `POST /v1/approval-requests` Ask a human to approve a quoted send, or ask for help (grants nothing) - `GET /v1/approval-requests/{approval_id}` Read one request - `POST /v1/approval-requests/{approval_id}/approve` Approve the bound quote (a human with approval:decide, never the requester) - `POST /v1/approval-requests/{approval_id}/reject` Reject with a reason - `POST /v1/approvals/{approval_id}/revoke` Revoke an approval nobody has used yet ### GET `/v1/approval-requests` Approval requests, pending first #### Parameters GET /v1/approval-requests parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `status` | query | string or null | no | | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/approval-requests responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/approval-requests response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object ApprovalRequest | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/approval-requests \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/approval-requests` Ask a human to approve a quoted send, or ask for help (grants nothing) #### Request body application/json, required POST /v1/approval-requests request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `fax_job_id` | string or null | no | | | `kind` | string (send, help) | no | | | `message` | string or null | no | What the person should know (kind help). Shown as data. | | `quote_id` | string or null | no | The quote to bind (kind send). | #### Responses POST /v1/approval-requests responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/approval-requests response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `bound` | object | yes | | | `consumed_by_job_id` | string or null | yes | | | `created_at` | string | yes | | | `decided_at` | string or null | yes | | | `decided_by` | string or null | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `kind` | string (send, help) | yes | | | `message` | string or null | yes | | | `notified` | boolean or null | no | On creation: whether the organization was mailed. false when the request repeats a pending one or the organization's daily cap of request mails is reached; the request is still shown in the dashboard. | | `quote_id` | string or null | yes | | | `reason` | string or null | yes | | | `requested_by` | string | yes | | | `status` | string (pending, approved, rejected, revoked, expired) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/approval-requests \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/approval-requests/{approval_id}` Read one request #### Parameters GET /v1/approval-requests/{approval_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `approval_id` | path | string | yes | | #### Responses GET /v1/approval-requests/{approval_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/approval-requests/{approval_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `bound` | object | yes | | | `consumed_by_job_id` | string or null | yes | | | `created_at` | string | yes | | | `decided_at` | string or null | yes | | | `decided_by` | string or null | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `kind` | string (send, help) | yes | | | `message` | string or null | yes | | | `notified` | boolean or null | no | On creation: whether the organization was mailed. false when the request repeats a pending one or the organization's daily cap of request mails is reached; the request is still shown in the dashboard. | | `quote_id` | string or null | yes | | | `reason` | string or null | yes | | | `requested_by` | string | yes | | | `status` | string (pending, approved, rejected, revoked, expired) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/approval-requests/APPROVAL_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/approval-requests/{approval_id}/approve` Approve the bound quote (a human with approval:decide, never the requester) #### Parameters POST /v1/approval-requests/{approval_id}/approve parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `approval_id` | path | string | yes | | #### Request body application/json, required POST /v1/approval-requests/{approval_id}/approve request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `reason` | string or null | no | | #### Responses POST /v1/approval-requests/{approval_id}/approve responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/approval-requests/{approval_id}/approve response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `bound` | object | yes | | | `consumed_by_job_id` | string or null | yes | | | `created_at` | string | yes | | | `decided_at` | string or null | yes | | | `decided_by` | string or null | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `kind` | string (send, help) | yes | | | `message` | string or null | yes | | | `notified` | boolean or null | no | On creation: whether the organization was mailed. false when the request repeats a pending one or the organization's daily cap of request mails is reached; the request is still shown in the dashboard. | | `quote_id` | string or null | yes | | | `reason` | string or null | yes | | | `requested_by` | string | yes | | | `status` | string (pending, approved, rejected, revoked, expired) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/approval-requests/APPROVAL_ID/approve \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/approval-requests/{approval_id}/reject` Reject with a reason #### Parameters POST /v1/approval-requests/{approval_id}/reject parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `approval_id` | path | string | yes | | #### Request body application/json, required POST /v1/approval-requests/{approval_id}/reject request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `reason` | string or null | no | | #### Responses POST /v1/approval-requests/{approval_id}/reject responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/approval-requests/{approval_id}/reject response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `bound` | object | yes | | | `consumed_by_job_id` | string or null | yes | | | `created_at` | string | yes | | | `decided_at` | string or null | yes | | | `decided_by` | string or null | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `kind` | string (send, help) | yes | | | `message` | string or null | yes | | | `notified` | boolean or null | no | On creation: whether the organization was mailed. false when the request repeats a pending one or the organization's daily cap of request mails is reached; the request is still shown in the dashboard. | | `quote_id` | string or null | yes | | | `reason` | string or null | yes | | | `requested_by` | string | yes | | | `status` | string (pending, approved, rejected, revoked, expired) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/approval-requests/APPROVAL_ID/reject \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/approvals/{approval_id}/revoke` Revoke an approval nobody has used yet #### Parameters POST /v1/approvals/{approval_id}/revoke parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `approval_id` | path | string | yes | | #### Request body application/json, required POST /v1/approvals/{approval_id}/revoke request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `reason` | string or null | no | | #### Responses POST /v1/approvals/{approval_id}/revoke responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/approvals/{approval_id}/revoke response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `bound` | object | yes | | | `consumed_by_job_id` | string or null | yes | | | `created_at` | string | yes | | | `decided_at` | string or null | yes | | | `decided_by` | string or null | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `kind` | string (send, help) | yes | | | `message` | string or null | yes | | | `notified` | boolean or null | no | On creation: whether the organization was mailed. false when the request repeats a pending one or the organization's daily cap of request mails is reached; the request is still shown in the dashboard. | | `quote_id` | string or null | yes | | | `reason` | string or null | yes | | | `requested_by` | string | yes | | | `status` | string (pending, approved, rejected, revoked, expired) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/approvals/APPROVAL_ID/revoke \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ## auth - `POST /v1/auth/invitations/{token}/accept` Join the organization an invitation link names (the signed-in email must match) - `POST /v1/auth/logout` End this session - `POST /v1/auth/magic-links` Send a sign-in link to an email address (always 202) - `GET /v1/auth/magic-links/{token}` Open a sign-in link: sets the session cookie and redirects to the app - `GET /v1/auth/me` The signed-in person, their organizations and the CSRF token - `GET /v1/auth/oauth/{provider}/callback` Finish GitHub or Google sign-in (the app forwards its /auth/{provider}/callback here) - `GET /v1/auth/oauth/{provider}/start` Begin GitHub or Google sign-in (redirects to the provider) - `GET /v1/auth/passkeys` Your passkeys - `POST /v1/auth/passkeys/login/options` Options for navigator.credentials.get() (discoverable, no email needed) - `POST /v1/auth/passkeys/login/verify` Sign in with a passkey assertion - `POST /v1/auth/passkeys/register/options` Options for navigator.credentials.create() (fresh session required) - `POST /v1/auth/passkeys/register/verify` Store a new passkey after the browser created it - `DELETE /v1/auth/passkeys/{passkey_id}` Remove one of your passkeys - `GET /v1/auth/providers` Which sign-in methods are available - `POST /v1/auth/reauth` Prove it is you again (passkey now, or a fresh link by mail) before a sensitive action - `DELETE /v1/auth/sessions` Sign out everywhere else - `GET /v1/auth/sessions` Your live sessions - `DELETE /v1/auth/sessions/{session_id}` Revoke one of your sessions - `POST /v1/auth/switch-organization` Look at another organization you belong to ### POST `/v1/auth/invitations/{token}/accept` Join the organization an invitation link names (the signed-in email must match) #### Parameters POST /v1/auth/invitations/{token}/accept parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `token` | path | string | yes | | #### Responses POST /v1/auth/invitations/{token}/accept responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/auth/invitations/{token}/accept response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `organization` | object OrganizationSummary | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/invitations/TOKEN/accept \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/auth/logout` End this session #### Responses POST /v1/auth/logout responses | Status | Meaning | | --- | --- | | `204` | Successful Response | #### Example ``` `curl https://api.faxforagents.com/v1/auth/logout \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/auth/magic-links` Send a sign-in link to an email address (always 202) #### Request body application/json, required POST /v1/auth/magic-links request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `email` | string (email) | yes | | | `next` | string or null | no | An in-app path to open after sign-in. | #### Responses POST /v1/auth/magic-links responses | Status | Meaning | | --- | --- | | `202` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/auth/magic-links response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `message` | string | yes | | | `sent` | boolean | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/magic-links \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/auth/magic-links/{token}` Open a sign-in link: sets the session cookie and redirects to the app #### Parameters GET /v1/auth/magic-links/{token} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `token` | path | string | yes | | #### Responses GET /v1/auth/magic-links/{token} responses | Status | Meaning | | --- | --- | | `303` | Redirect to the app, signed in or with an error code in the query. | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/auth/magic-links/TOKEN \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/auth/me` The signed-in person, their organizations and the CSRF token #### Responses GET /v1/auth/me responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/auth/me response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `csrf_token` | string | yes | | | `organization` | object OrganizationSummary or null | yes | | | `organizations` | array of object OrganizationSummary | yes | | | `passkeys` | integer | yes | | | `providers` | object Providers | yes | | | `session` | object SessionInfo | yes | | | `user` | object UserOut | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/me \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/auth/oauth/{provider}/callback` Finish GitHub or Google sign-in (the app forwards its /auth/{provider}/callback here) #### Parameters GET /v1/auth/oauth/{provider}/callback parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `provider` | path | string | yes | | | `code` | query | string | no | Default `""` . | | `state` | query | string | no | Default `""` . | | `error` | query | string or null | no | | #### Responses GET /v1/auth/oauth/{provider}/callback responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/auth/oauth/{provider}/callback response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `csrf_token` | string | yes | | | `organization` | object OrganizationSummary or null | yes | | | `organizations` | array of object OrganizationSummary | yes | | | `passkeys` | integer | yes | | | `providers` | object Providers | yes | | | `session` | object SessionInfo | yes | | | `user` | object UserOut | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/oauth/PROVIDER/callback \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/auth/oauth/{provider}/start` Begin GitHub or Google sign-in (redirects to the provider) #### Parameters GET /v1/auth/oauth/{provider}/start parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `provider` | path | string | yes | | | `next` | query | string or null | no | | #### Responses GET /v1/auth/oauth/{provider}/start responses | Status | Meaning | | --- | --- | | `303` | Successful Response | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/auth/oauth/PROVIDER/start \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/auth/passkeys` Your passkeys #### Responses GET /v1/auth/passkeys responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/auth/passkeys response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Passkey | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/passkeys \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/auth/passkeys/login/options` Options for navigator.credentials.get() (discoverable, no email needed) #### Responses POST /v1/auth/passkeys/login/options responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Example ``` `curl https://api.faxforagents.com/v1/auth/passkeys/login/options \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/auth/passkeys/login/verify` Sign in with a passkey assertion #### Request body application/json, required POST /v1/auth/passkeys/login/verify request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `credential` | object | yes | | #### Responses POST /v1/auth/passkeys/login/verify responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/auth/passkeys/login/verify response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `csrf_token` | string | yes | | | `organization` | object OrganizationSummary or null | yes | | | `organizations` | array of object OrganizationSummary | yes | | | `passkeys` | integer | yes | | | `providers` | object Providers | yes | | | `session` | object SessionInfo | yes | | | `user` | object UserOut | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/passkeys/login/verify \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/auth/passkeys/register/options` Options for navigator.credentials.create() (fresh session required) #### Responses POST /v1/auth/passkeys/register/options responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Example ``` `curl https://api.faxforagents.com/v1/auth/passkeys/register/options \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/auth/passkeys/register/verify` Store a new passkey after the browser created it #### Request body application/json, required POST /v1/auth/passkeys/register/verify request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `credential` | object | yes | | | `label` | string or null | no | | #### Responses POST /v1/auth/passkeys/register/verify responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/auth/passkeys/register/verify response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `aaguid` | string or null | yes | | | `backup_eligible` | boolean | yes | | | `created_at` | string | yes | | | `id` | string | yes | | | `label` | string or null | yes | | | `last_used_at` | string or null | yes | | | `transports` | array of string or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/passkeys/register/verify \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/auth/passkeys/{passkey_id}` Remove one of your passkeys #### Parameters DELETE /v1/auth/passkeys/{passkey_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `passkey_id` | path | string | yes | | #### Responses DELETE /v1/auth/passkeys/{passkey_id} responses | Status | Meaning | | --- | --- | | `204` | Successful Response | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/auth/passkeys/PASSKEY_ID \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/auth/providers` Which sign-in methods are available #### Responses GET /v1/auth/providers responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/auth/providers response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `github` | boolean | yes | | | `google` | boolean | yes | | | `magic_link` | boolean | no | | | `passkeys` | boolean | no | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/providers \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/auth/reauth` Prove it is you again (passkey now, or a fresh link by mail) before a sensitive action #### Request body application/json, required POST /v1/auth/reauth request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `credential` | object or null | no | | | `method` | string (passkey, magic_link) | yes | | #### Responses POST /v1/auth/reauth responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `202` | A sign-in link was sent; opening it in this browser refreshes the session. | | `422` | Validation Error | #### Response fields POST /v1/auth/reauth response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `csrf_token` | string | yes | | | `organization` | object OrganizationSummary or null | yes | | | `organizations` | array of object OrganizationSummary | yes | | | `passkeys` | integer | yes | | | `providers` | object Providers | yes | | | `session` | object SessionInfo | yes | | | `user` | object UserOut | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/reauth \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/auth/sessions` Sign out everywhere else #### Responses DELETE /v1/auth/sessions responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields DELETE /v1/auth/sessions response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `revoked` | integer | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/sessions \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/auth/sessions` Your live sessions #### Responses GET /v1/auth/sessions responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/auth/sessions response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object SessionOut | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/sessions \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/auth/sessions/{session_id}` Revoke one of your sessions #### Parameters DELETE /v1/auth/sessions/{session_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `session_id` | path | string | yes | | #### Responses DELETE /v1/auth/sessions/{session_id} responses | Status | Meaning | | --- | --- | | `204` | Successful Response | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/auth/sessions/SESSION_ID \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/auth/switch-organization` Look at another organization you belong to #### Request body application/json, required POST /v1/auth/switch-organization request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `org_id` | string | yes | | #### Responses POST /v1/auth/switch-organization responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/auth/switch-organization response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `csrf_token` | string | yes | | | `organization` | object OrganizationSummary or null | yes | | | `organizations` | array of object OrganizationSummary | yes | | | `passkeys` | integer | yes | | | `providers` | object Providers | yes | | | `session` | object SessionInfo | yes | | | `user` | object UserOut | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auth/switch-organization \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ## billing - `GET /v1/auto-topups` Auto top-up settings - `POST /v1/auto-topups` Opt in to auto top-up (threshold, amount, daily cap); the card is saved at the first payment - `DELETE /v1/auto-topups/{auto_topup_id}` Turn auto top-up off - `GET /v1/balance` Available and held balance - `POST /v1/billing-portal-sessions` Open the Stripe customer portal (cards, invoices, the subscription) - `GET /v1/billing-settings` Billing settings of the organization - `PATCH /v1/billing-settings` Change the billing email or the low-balance threshold - `GET /v1/budgets` Spending caps of this environment - `POST /v1/budgets` Create a daily or monthly cap for the organization, a project or an agent - `PATCH /v1/budgets/{budget_id}` Change a cap's limit or status - `GET /v1/invoices` Stripe invoices and receipts (hosted links) - `GET /v1/invoices/{invoice_id}` One Stripe invoice - `GET /v1/ledger` Wallet transactions, newest first - `GET /v1/subscriptions` Subscriptions and the workspace entitlement - `POST /v1/subscriptions/checkout` Open a hosted Checkout for the Agent workspace subscription - `GET /v1/topup-sessions` Top-up sessions, newest first - `POST /v1/topup-sessions` Open a hosted Checkout to fund the live wallet (10 to 1000 USD) - `GET /v1/topup-sessions/{topup_session_id}` Read a top-up session - `GET /v1/usage` Usage rollups by day, direction, destination and project - `POST /v1/usage-exports` Export usage as CSV behind a signed download - `GET /v1/usage-exports/{export_id}` Read a usage export (fresh link) - `GET /v1/usage-exports/{export_id}/content` The CSV behind a signed URL (no API key) ### GET `/v1/auto-topups` Auto top-up settings #### Responses GET /v1/auto-topups responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/auto-topups response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object AutoTopup | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auto-topups \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/auto-topups` Opt in to auto top-up (threshold, amount, daily cap); the card is saved at the first payment #### Request body application/json, required POST /v1/auto-topups request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | Whole dollars, 10 to 1000 USD. | | `daily_cap_micro` | integer | yes | Never more than this per UTC day. | | `threshold_micro` | integer | yes | Top up when the available balance drops below this. | #### Responses POST /v1/auto-topups responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/auto-topups response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `created_at` | string | yes | | | `daily_cap_micro` | integer | yes | | | `disabled_at` | string or null | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `last_attempt_at` | string or null | yes | | | `payment_method_saved` | boolean | yes | | | `setup_session_id` | string or null | yes | | | `setup_url` | string or null | no | Where to save the card while status is pending_setup. | | `status` | string (pending_setup, active, disabled) | yes | | | `threshold_micro` | integer | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auto-topups \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/auto-topups/{auto_topup_id}` Turn auto top-up off #### Parameters DELETE /v1/auto-topups/{auto_topup_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `auto_topup_id` | path | string | yes | | #### Responses DELETE /v1/auto-topups/{auto_topup_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields DELETE /v1/auto-topups/{auto_topup_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `deleted` | boolean | no | | | `id` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/auto-topups/AUTO_TOPUP_ID \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/balance` Available and held balance #### Responses GET /v1/balance responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/balance response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `available_micro` | integer | yes | | | `currency` | string | yes | | | `environment` | string (live, test) | yes | | | `held_micro` | integer | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/balance \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/billing-portal-sessions` Open the Stripe customer portal (cards, invoices, the subscription) #### Responses POST /v1/billing-portal-sessions responses | Status | Meaning | | --- | --- | | `201` | Successful Response | #### Response fields POST /v1/billing-portal-sessions response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `expires_at` | string | yes | | | `url` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/billing-portal-sessions \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/billing-settings` Billing settings of the organization #### Responses GET /v1/billing-settings responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/billing-settings response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `billing_email` | string or null | yes | | | `disputed` | boolean | yes | | | `low_balance_active` | boolean | yes | | | `low_balance_threshold_micro` | integer | yes | | | `org_id` | string | yes | | | `stripe_customer` | boolean | yes | | | `workspace` | boolean | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/billing-settings \ -H "Authorization: Bearer ffa_test_..."` ``` ### PATCH `/v1/billing-settings` Change the billing email or the low-balance threshold #### Request body application/json, required PATCH /v1/billing-settings request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `billing_email` | string or null | no | | | `low_balance_threshold_micro` | integer or null | no | | #### Responses PATCH /v1/billing-settings responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PATCH /v1/billing-settings response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `billing_email` | string or null | yes | | | `disputed` | boolean | yes | | | `low_balance_active` | boolean | yes | | | `low_balance_threshold_micro` | integer | yes | | | `org_id` | string | yes | | | `stripe_customer` | boolean | yes | | | `workspace` | boolean | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/billing-settings \ -X PATCH \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/budgets` Spending caps of this environment #### Responses GET /v1/budgets responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/budgets response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Budget | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/budgets \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/budgets` Create a daily or monthly cap for the organization, a project or an agent #### Request body application/json, required POST /v1/budgets request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | no | | | `limit_micro` | integer | yes | | | `period` | string (daily, monthly) | yes | | | `project_id` | string or null | no | | | `scope` | string (organization, project, agent) | no | | #### Responses POST /v1/budgets responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/budgets response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `limit_micro` | integer | yes | | | `period` | string (daily, monthly) | yes | | | `project_id` | string or null | yes | | | `remaining_micro` | integer | yes | | | `resets_at` | string | yes | | | `scope` | string (organization, project, agent) | yes | | | `spent_micro` | integer | yes | | | `status` | string (active, disabled) | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/budgets \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### PATCH `/v1/budgets/{budget_id}` Change a cap's limit or status #### Parameters PATCH /v1/budgets/{budget_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `budget_id` | path | string | yes | | #### Request body application/json, required PATCH /v1/budgets/{budget_id} request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `limit_micro` | integer or null | no | | | `status` | string (active, disabled) or null | no | | #### Responses PATCH /v1/budgets/{budget_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PATCH /v1/budgets/{budget_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `limit_micro` | integer | yes | | | `period` | string (daily, monthly) | yes | | | `project_id` | string or null | yes | | | `remaining_micro` | integer | yes | | | `resets_at` | string | yes | | | `scope` | string (organization, project, agent) | yes | | | `spent_micro` | integer | yes | | | `status` | string (active, disabled) | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/budgets/BUDGET_ID \ -X PATCH \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/invoices` Stripe invoices and receipts (hosted links) #### Parameters GET /v1/invoices parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `20` . | #### Responses GET /v1/invoices responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/invoices response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Invoice | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/invoices \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/invoices/{invoice_id}` One Stripe invoice #### Parameters GET /v1/invoices/{invoice_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `invoice_id` | path | string | yes | | #### Responses GET /v1/invoices/{invoice_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/invoices/{invoice_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_due_micro` | integer | yes | | | `amount_paid_micro` | integer | yes | | | `created_at` | string or null | yes | | | `currency` | string | yes | | | `hosted_invoice_url` | string or null | yes | | | `id` | string | yes | | | `invoice_pdf` | string or null | yes | | | `number` | string or null | yes | | | `status` | string or null | yes | | | `subscription_id` | string or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/invoices/INVOICE_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/ledger` Wallet transactions, newest first #### Parameters GET /v1/ledger parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/ledger responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/ledger response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object LedgerEntry | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/ledger \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/subscriptions` Subscriptions and the workspace entitlement #### Responses GET /v1/subscriptions responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/subscriptions response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Subscription | yes | | | `next_cursor` | string or null | no | | | `workspace` | boolean | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/subscriptions \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/subscriptions/checkout` Open a hosted Checkout for the Agent workspace subscription #### Responses POST /v1/subscriptions/checkout responses | Status | Meaning | | --- | --- | | `201` | Successful Response | #### Response fields POST /v1/subscriptions/checkout response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `product` | string | yes | | | `stripe_session_id` | string | yes | | | `url` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/subscriptions/checkout \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/topup-sessions` Top-up sessions, newest first #### Parameters GET /v1/topup-sessions parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/topup-sessions responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/topup-sessions response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object TopupSession | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/topup-sessions \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/topup-sessions` Open a hosted Checkout to fund the live wallet (10 to 1000 USD) #### Request body application/json, required POST /v1/topup-sessions request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_usd` | integer | yes | Whole US dollars, 10 to 1000 per session. | #### Responses POST /v1/topup-sessions responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/topup-sessions response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `auto_topup_id` | string or null | yes | | | `completed_at` | string or null | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `environment` | string (live, test) | yes | | | `error` | string or null | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `kind` | string (manual, auto, setup) | yes | | | `status` | string (open, completed, expired, canceled, failed) | yes | | | `stripe_payment_intent_id` | string or null | yes | | | `stripe_session_id` | string or null | yes | | | `url` | string or null | no | The hosted Checkout page while the session is open. | #### Example ``` `curl https://api.faxforagents.com/v1/topup-sessions \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/topup-sessions/{topup_session_id}` Read a top-up session #### Parameters GET /v1/topup-sessions/{topup_session_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `topup_session_id` | path | string | yes | | #### Responses GET /v1/topup-sessions/{topup_session_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/topup-sessions/{topup_session_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `auto_topup_id` | string or null | yes | | | `completed_at` | string or null | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `environment` | string (live, test) | yes | | | `error` | string or null | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `kind` | string (manual, auto, setup) | yes | | | `status` | string (open, completed, expired, canceled, failed) | yes | | | `stripe_payment_intent_id` | string or null | yes | | | `stripe_session_id` | string or null | yes | | | `url` | string or null | no | The hosted Checkout page while the session is open. | #### Example ``` `curl https://api.faxforagents.com/v1/topup-sessions/TOPUP_SESSION_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/usage` Usage rollups by day, direction, destination and project #### Parameters GET /v1/usage parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `from` | query | string or null | no | | | `to` | query | string or null | no | | | `group_by` | query | string | no | Comma-separated subset. Default `"day,direction,route,project"` . | | `project_id` | query | string or null | no | | #### Responses GET /v1/usage responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/usage response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `currency` | string | yes | | | `environment` | string (live, test) | yes | | | `from` | string | yes | | | `group_by` | array of string | yes | | | `items` | array of object UsageItem | yes | | | `to` | string | yes | | | `totals` | object | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/usage \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/usage-exports` Export usage as CSV behind a signed download #### Request body application/json, required POST /v1/usage-exports request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `from` | string or null | no | ISO date; default 30 days ago. | | `to` | string or null | no | ISO date; default today. | #### Responses POST /v1/usage-exports responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/usage-exports response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string | yes | | | `id` | string | yes | | | `params` | object | yes | | | `row_count` | integer | yes | | | `status` | string (ready, expired) | yes | | | `url` | string or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/usage-exports \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/usage-exports/{export_id}` Read a usage export (fresh link) #### Parameters GET /v1/usage-exports/{export_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `export_id` | path | string | yes | | #### Responses GET /v1/usage-exports/{export_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/usage-exports/{export_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string | yes | | | `id` | string | yes | | | `params` | object | yes | | | `row_count` | integer | yes | | | `status` | string (ready, expired) | yes | | | `url` | string or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/usage-exports/EXPORT_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/usage-exports/{export_id}/content` The CSV behind a signed URL (no API key) #### Parameters GET /v1/usage-exports/{export_id}/content parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `export_id` | path | string | yes | | | `exp` | query | string or null | no | | | `sig` | query | string or null | no | | #### Responses GET /v1/usage-exports/{export_id}/content responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/usage-exports/EXPORT_ID/content \ -H "Authorization: Bearer ffa_test_..."` ``` ## platform - `GET /v1/capabilities` Limits and features in force - `GET /v1/service-status` Customer-safe availability ### GET `/v1/capabilities` Limits and features in force #### Responses GET /v1/capabilities responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/capabilities response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `approvals` | boolean | yes | | | `environment` | string (live, test) | yes | | | `formats` | array of string | yes | | | `limits` | object | yes | | | `max_pages` | integer | yes | | | `max_upload_bytes` | integer | yes | | | `numbers` | boolean | yes | | | `price_version` | string | yes | | | `provider` | string | yes | | | `rates` | object | yes | | | `receive` | boolean | yes | | | `routes` | array of string | yes | | | `scopes` | array of string | yes | | | `send` | boolean | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/capabilities \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/service-status` Customer-safe availability #### Responses GET /v1/service-status responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/service-status response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `database` | boolean | yes | | | `environment` | string (live, test) | yes | | | `provider` | string | yes | | | `status` | string (ok, degraded) | yes | | | `status_page` | string or null | no | | | `version` | string | yes | | | `worker_beat_age_s` | integer or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/service-status \ -H "Authorization: Bearer ffa_test_..."` ``` ## extractions - `GET /v1/classifications` Classifications, newest first - `POST /v1/classifications` Classify a received fax or a document into one of your document types, or abstain - `GET /v1/classifications/{classification_id}` One classification - `GET /v1/document-summaries` Summaries, newest first - `POST /v1/document-summaries` Summarise a received fax or a document; every sentence cites its pages - `GET /v1/document-summaries/{summary_id}` One summary - `GET /v1/extraction-schemas` Your extraction schemas - `POST /v1/extraction-schemas` Save a versioned extraction schema (the same name again is the next version) - `GET /v1/extraction-schemas/{schema_id}` One schema version - `GET /v1/extractions` Extractions, newest first - `POST /v1/extractions` Read a received fax or a document: OCR per page, then fields against a schema - `GET /v1/extractions/{extraction_id}` One extraction with its corrections - `POST /v1/extractions/{extraction_id}/corrections` Record a reviewer's correction of one field (stored beside the extraction, never merged) ### GET `/v1/classifications` Classifications, newest first #### Parameters GET /v1/classifications parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `inbox_item_id` | query | string or null | no | | | `document_id` | query | string or null | no | | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/classifications responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/classifications response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Extraction | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/classifications \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/classifications` Classify a received fax or a document into one of your document types, or abstain #### Request body application/json, required POST /v1/classifications request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `abstain_below` | number | no | | | `document_id` | string or null | no | | | `document_types` | array of string | yes | | | `inbox_item_id` | string or null | no | | | `max_pages` | integer or null | no | | | `strict` | boolean | no | Refuse with page_limit_exceeded when the document has more pages than would be read, instead of reading the first ones and warning. | | `strong_model_if_below` | number | no | | #### Responses POST /v1/classifications responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/classifications response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `confidence` | number or null | yes | | | `corrections` | array of object or null | no | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | The document's page count. Above pages_processed on a completed row means the cap left pages unread; output.truncated and a warning say so. | | `environment` | string (live, test) | yes | | | `error_code` | string or null | yes | | | `id` | string | yes | | | `inbox_item_id` | string or null | yes | | | `kind` | string (extract, classify, summarize) | yes | | | `models` | object | yes | | | `options` | object | yes | | | `output` | object or null | yes | | | `pages` | array of object ExtractionPage | yes | | | `pages_escalated` | integer | yes | | | `pages_processed` | integer | yes | | | `pages_requested` | integer | yes | | | `price_micro` | integer | yes | | | `provider_cost_micro` | integer | yes | | | `schema` | object or null | no | | | `schema_id` | string or null | yes | | | `started_at` | string or null | yes | | | `status` | string (pending, queued, running, completed, failed) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/classifications \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/classifications/{classification_id}` One classification #### Parameters GET /v1/classifications/{classification_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `classification_id` | path | string | yes | | #### Responses GET /v1/classifications/{classification_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/classifications/{classification_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `confidence` | number or null | yes | | | `corrections` | array of object or null | no | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | The document's page count. Above pages_processed on a completed row means the cap left pages unread; output.truncated and a warning say so. | | `environment` | string (live, test) | yes | | | `error_code` | string or null | yes | | | `id` | string | yes | | | `inbox_item_id` | string or null | yes | | | `kind` | string (extract, classify, summarize) | yes | | | `models` | object | yes | | | `options` | object | yes | | | `output` | object or null | yes | | | `pages` | array of object ExtractionPage | yes | | | `pages_escalated` | integer | yes | | | `pages_processed` | integer | yes | | | `pages_requested` | integer | yes | | | `price_micro` | integer | yes | | | `provider_cost_micro` | integer | yes | | | `schema` | object or null | no | | | `schema_id` | string or null | yes | | | `started_at` | string or null | yes | | | `status` | string (pending, queued, running, completed, failed) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/classifications/CLASSIFICATION_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/document-summaries` Summaries, newest first #### Parameters GET /v1/document-summaries parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `inbox_item_id` | query | string or null | no | | | `document_id` | query | string or null | no | | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/document-summaries responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/document-summaries response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Extraction | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/document-summaries \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/document-summaries` Summarise a received fax or a document; every sentence cites its pages #### Request body application/json, required POST /v1/document-summaries request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `document_id` | string or null | no | | | `inbox_item_id` | string or null | no | | | `max_pages` | integer or null | no | | | `strict` | boolean | no | Refuse with page_limit_exceeded when the document has more pages than would be read, instead of reading the first ones and warning. | | `strong_model_if_below` | number | no | | #### Responses POST /v1/document-summaries responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/document-summaries response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `confidence` | number or null | yes | | | `corrections` | array of object or null | no | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | The document's page count. Above pages_processed on a completed row means the cap left pages unread; output.truncated and a warning say so. | | `environment` | string (live, test) | yes | | | `error_code` | string or null | yes | | | `id` | string | yes | | | `inbox_item_id` | string or null | yes | | | `kind` | string (extract, classify, summarize) | yes | | | `models` | object | yes | | | `options` | object | yes | | | `output` | object or null | yes | | | `pages` | array of object ExtractionPage | yes | | | `pages_escalated` | integer | yes | | | `pages_processed` | integer | yes | | | `pages_requested` | integer | yes | | | `price_micro` | integer | yes | | | `provider_cost_micro` | integer | yes | | | `schema` | object or null | no | | | `schema_id` | string or null | yes | | | `started_at` | string or null | yes | | | `status` | string (pending, queued, running, completed, failed) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/document-summaries \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/document-summaries/{summary_id}` One summary #### Parameters GET /v1/document-summaries/{summary_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `summary_id` | path | string | yes | | #### Responses GET /v1/document-summaries/{summary_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/document-summaries/{summary_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `confidence` | number or null | yes | | | `corrections` | array of object or null | no | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | The document's page count. Above pages_processed on a completed row means the cap left pages unread; output.truncated and a warning say so. | | `environment` | string (live, test) | yes | | | `error_code` | string or null | yes | | | `id` | string | yes | | | `inbox_item_id` | string or null | yes | | | `kind` | string (extract, classify, summarize) | yes | | | `models` | object | yes | | | `options` | object | yes | | | `output` | object or null | yes | | | `pages` | array of object ExtractionPage | yes | | | `pages_escalated` | integer | yes | | | `pages_processed` | integer | yes | | | `pages_requested` | integer | yes | | | `price_micro` | integer | yes | | | `provider_cost_micro` | integer | yes | | | `schema` | object or null | no | | | `schema_id` | string or null | yes | | | `started_at` | string or null | yes | | | `status` | string (pending, queued, running, completed, failed) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/document-summaries/SUMMARY_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/extraction-schemas` Your extraction schemas #### Responses GET /v1/extraction-schemas responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/extraction-schemas response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object ExtractionSchema | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/extraction-schemas \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/extraction-schemas` Save a versioned extraction schema (the same name again is the next version) #### Request body application/json, required POST /v1/extraction-schemas request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `description` | string or null | no | | | `name` | string | yes | | | `schema` | object | yes | | #### Responses POST /v1/extraction-schemas responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/extraction-schemas response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `description` | string or null | yes | | | `id` | string | yes | | | `name` | string | yes | | | `schema` | object | yes | | | `status` | string | yes | | | `version` | integer | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/extraction-schemas \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/extraction-schemas/{schema_id}` One schema version #### Parameters GET /v1/extraction-schemas/{schema_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `schema_id` | path | string | yes | | #### Responses GET /v1/extraction-schemas/{schema_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/extraction-schemas/{schema_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `description` | string or null | yes | | | `id` | string | yes | | | `name` | string | yes | | | `schema` | object | yes | | | `status` | string | yes | | | `version` | integer | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/extraction-schemas/SCHEMA_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/extractions` Extractions, newest first #### Parameters GET /v1/extractions parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `inbox_item_id` | query | string or null | no | | | `document_id` | query | string or null | no | | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/extractions responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/extractions response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Extraction | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/extractions \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/extractions` Read a received fax or a document: OCR per page, then fields against a schema #### Request body application/json, required POST /v1/extractions request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `document_id` | string or null | no | | | `inbox_item_id` | string or null | no | | | `max_pages` | integer or null | no | | | `schema` | object or null | no | A JSON Schema object of string, number, boolean or date fields with descriptions. | | `schema_id` | string or null | no | | | `strict` | boolean | no | Refuse with page_limit_exceeded when the document has more pages than would be read, instead of reading the first ones and warning. | | `strong_model_if_below` | number | no | | #### Responses POST /v1/extractions responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/extractions response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `confidence` | number or null | yes | | | `corrections` | array of object or null | no | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | The document's page count. Above pages_processed on a completed row means the cap left pages unread; output.truncated and a warning say so. | | `environment` | string (live, test) | yes | | | `error_code` | string or null | yes | | | `id` | string | yes | | | `inbox_item_id` | string or null | yes | | | `kind` | string (extract, classify, summarize) | yes | | | `models` | object | yes | | | `options` | object | yes | | | `output` | object or null | yes | | | `pages` | array of object ExtractionPage | yes | | | `pages_escalated` | integer | yes | | | `pages_processed` | integer | yes | | | `pages_requested` | integer | yes | | | `price_micro` | integer | yes | | | `provider_cost_micro` | integer | yes | | | `schema` | object or null | no | | | `schema_id` | string or null | yes | | | `started_at` | string or null | yes | | | `status` | string (pending, queued, running, completed, failed) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/extractions \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/extractions/{extraction_id}` One extraction with its corrections #### Parameters GET /v1/extractions/{extraction_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `extraction_id` | path | string | yes | | #### Responses GET /v1/extractions/{extraction_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/extractions/{extraction_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `confidence` | number or null | yes | | | `corrections` | array of object or null | no | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | The document's page count. Above pages_processed on a completed row means the cap left pages unread; output.truncated and a warning say so. | | `environment` | string (live, test) | yes | | | `error_code` | string or null | yes | | | `id` | string | yes | | | `inbox_item_id` | string or null | yes | | | `kind` | string (extract, classify, summarize) | yes | | | `models` | object | yes | | | `options` | object | yes | | | `output` | object or null | yes | | | `pages` | array of object ExtractionPage | yes | | | `pages_escalated` | integer | yes | | | `pages_processed` | integer | yes | | | `pages_requested` | integer | yes | | | `price_micro` | integer | yes | | | `provider_cost_micro` | integer | yes | | | `schema` | object or null | no | | | `schema_id` | string or null | yes | | | `started_at` | string or null | yes | | | `status` | string (pending, queued, running, completed, failed) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/extractions/EXTRACTION_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/extractions/{extraction_id}/corrections` Record a reviewer's correction of one field (stored beside the extraction, never merged) #### Parameters POST /v1/extractions/{extraction_id}/corrections parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `extraction_id` | path | string | yes | | #### Request body application/json, required POST /v1/extractions/{extraction_id}/corrections request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `field` | string or null | no | | | `note` | string or null | no | | | `value` | any | no | | #### Responses POST /v1/extractions/{extraction_id}/corrections responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/extractions/{extraction_id}/corrections response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `extraction_id` | string | yes | | | `field` | string | yes | | | `id` | string | yes | | | `note` | string or null | yes | | | `value` | any | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/extractions/EXTRACTION_ID/corrections \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ## numbers - `GET /v1/coverage` Where we send and where we host numbers - `GET /v1/number-inventory` Fax-capable numbers for sale - `POST /v1/number-orders` Buy a number (the first month is held now, charged on activation) - `GET /v1/number-orders/{order_id}` Read a number order - `GET /v1/numbers` Your numbers - `GET /v1/numbers/{number_id}` One number - `PATCH /v1/numbers/{number_id}` Label and inbound route of a number - `POST /v1/numbers/{number_id}/release-requests` Release a number (confirm with the number itself; 30-day quarantine follows) - `GET /v1/port-orders` Port orders and port-out requests - `POST /v1/port-orders` Request a port-in (handled with you) - `GET /v1/port-orders/{port_id}` One port order - `POST /v1/port-out-requests` Note a port-out of one of your numbers - `POST /v1/portability-checks` Can these numbers port in - `POST /v1/sender-verifications` Verify a number of yours as a sender (only numbers you own) ### GET `/v1/coverage` Where we send and where we host numbers #### Responses GET /v1/coverage responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/coverage response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `coverage_version` | string or null | yes | | | `excluded` | object | yes | | | `generated_at` | string or null | yes | | | `inbound` | object | yes | | | `note` | string or null | yes | | | `orderable_now` | array of string | yes | | | `outbound` | object | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/coverage \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/number-inventory` Fax-capable numbers for sale #### Parameters GET /v1/number-inventory parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `country` | query | string | no | Default `"US"` . | | `locality` | query | string or null | no | | | `type` | query | string (local, toll_free) | no | Default `"local"` . | | `limit` | query | integer | no | Default `10` . | #### Responses GET /v1/number-inventory responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/number-inventory response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `country` | string | yes | | | `items` | array of object InventoryNumber | yes | | | `locality` | string or null | yes | | | `note` | string | yes | | | `number_type` | string | yes | | | `recurring_period` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/number-inventory \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/number-orders` Buy a number (the first month is held now, charged on activation) #### Request body application/json, required POST /v1/number-orders request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `e164` | string | yes | A number from GET /v1/number-inventory, E.164. | | `inbound_webhook_id` | string or null | no | The webhook that receives fax.received for this number (default: all). | | `label` | string or null | no | | | `project_id` | string or null | no | Inbound faxes are filed under this project. | #### Responses POST /v1/number-orders responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/number-orders response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `country` | string or null | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `e164` | string or null | yes | | | `environment` | string (live, test) | yes | | | `error` | string or null | yes | | | `first_month_hold_micro` | integer | yes | Held now, captured when the number activates. | | `id` | string | yes | | | `number` | object Number or null | yes | | | `number_id` | string or null | yes | | | `number_type` | string or null | yes | | | `provider` | string | yes | | | `recurring_charge_micro` | integer | yes | Charged every month on the activation day. | | `recurring_period` | string | yes | | | `status` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/number-orders \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/number-orders/{order_id}` Read a number order #### Parameters GET /v1/number-orders/{order_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `order_id` | path | string | yes | | #### Responses GET /v1/number-orders/{order_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/number-orders/{order_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `country` | string or null | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `e164` | string or null | yes | | | `environment` | string (live, test) | yes | | | `error` | string or null | yes | | | `first_month_hold_micro` | integer | yes | Held now, captured when the number activates. | | `id` | string | yes | | | `number` | object Number or null | yes | | | `number_id` | string or null | yes | | | `number_type` | string or null | yes | | | `provider` | string | yes | | | `recurring_charge_micro` | integer | yes | Charged every month on the activation day. | | `recurring_period` | string | yes | | | `status` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/number-orders/ORDER_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/numbers` Your numbers #### Parameters GET /v1/numbers parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `include_released` | query | boolean | no | Default `false` . | #### Responses GET /v1/numbers responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/numbers response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Number | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/numbers \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/numbers/{number_id}` One number #### Parameters GET /v1/numbers/{number_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `number_id` | path | string | yes | | #### Responses GET /v1/numbers/{number_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/numbers/{number_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `activated_at` | string or null | yes | | | `country` | string | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `e164` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `inbound_status` | string | yes | | | `inbound_webhook_id` | string or null | yes | | | `label` | string or null | yes | | | `locality` | string or null | yes | | | `monthly_micro` | integer | yes | | | `number_type` | string | yes | | | `order_id` | string or null | yes | | | `project_id` | string or null | yes | | | `provider` | string | yes | | | `released_at` | string or null | yes | | | `renews_at` | string or null | yes | | | `status` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/numbers/NUMBER_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### PATCH `/v1/numbers/{number_id}` Label and inbound route of a number #### Parameters PATCH /v1/numbers/{number_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `number_id` | path | string | yes | | #### Request body application/json, required PATCH /v1/numbers/{number_id} request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `inbound_webhook_id` | string or null | no | | | `label` | string or null | no | | | `project_id` | string or null | no | | #### Responses PATCH /v1/numbers/{number_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PATCH /v1/numbers/{number_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `activated_at` | string or null | yes | | | `country` | string | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `e164` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `inbound_status` | string | yes | | | `inbound_webhook_id` | string or null | yes | | | `label` | string or null | yes | | | `locality` | string or null | yes | | | `monthly_micro` | integer | yes | | | `number_type` | string | yes | | | `order_id` | string or null | yes | | | `project_id` | string or null | yes | | | `provider` | string | yes | | | `released_at` | string or null | yes | | | `renews_at` | string or null | yes | | | `status` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/numbers/NUMBER_ID \ -X PATCH \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/numbers/{number_id}/release-requests` Release a number (confirm with the number itself; 30-day quarantine follows) #### Parameters POST /v1/numbers/{number_id}/release-requests parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `number_id` | path | string | yes | | #### Request body application/json, required POST /v1/numbers/{number_id}/release-requests request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirm` | string or null | no | The number itself, typed out, to confirm. | #### Responses POST /v1/numbers/{number_id}/release-requests responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/numbers/{number_id}/release-requests response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `activated_at` | string or null | yes | | | `country` | string | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `e164` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `inbound_status` | string | yes | | | `inbound_webhook_id` | string or null | yes | | | `label` | string or null | yes | | | `locality` | string or null | yes | | | `monthly_micro` | integer | yes | | | `number_type` | string | yes | | | `order_id` | string or null | yes | | | `project_id` | string or null | yes | | | `provider` | string | yes | | | `released_at` | string or null | yes | | | `renews_at` | string or null | yes | | | `status` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/numbers/NUMBER_ID/release-requests \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/port-orders` Port orders and port-out requests #### Responses GET /v1/port-orders responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/port-orders response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object PortOrder | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/port-orders \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/port-orders` Request a port-in (handled with you) #### Request body application/json, required POST /v1/port-orders request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `note` | string or null | no | | | `numbers` | array of string | yes | | #### Responses POST /v1/port-orders responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/port-orders response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `direction` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `milestones` | array of object | yes | | | `numbers` | array of string | yes | | | `provider` | string | yes | | | `request` | object | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/port-orders \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/port-orders/{port_id}` One port order #### Parameters GET /v1/port-orders/{port_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `port_id` | path | string | yes | | #### Responses GET /v1/port-orders/{port_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/port-orders/{port_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `direction` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `milestones` | array of object | yes | | | `numbers` | array of string | yes | | | `provider` | string | yes | | | `request` | object | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/port-orders/PORT_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/port-out-requests` Note a port-out of one of your numbers #### Request body application/json, required POST /v1/port-out-requests request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `note` | string or null | no | | | `number_id` | string | yes | | #### Responses POST /v1/port-out-requests responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/port-out-requests response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `direction` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `milestones` | array of object | yes | | | `numbers` | array of string | yes | | | `provider` | string | yes | | | `request` | object | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/port-out-requests \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/portability-checks` Can these numbers port in #### Request body application/json, required POST /v1/portability-checks request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `numbers` | array of string | yes | | #### Responses POST /v1/portability-checks responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/portability-checks response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `checked_at` | string | yes | | | `items` | array of object | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/portability-checks \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/sender-verifications` Verify a number of yours as a sender (only numbers you own) #### Request body application/json, required POST /v1/sender-verifications request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `from` | string | yes | A number your organization owns, E.164. | #### Responses POST /v1/sender-verifications responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/sender-verifications response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `from` | string | yes | | | `id` | string | yes | | | `method` | string | yes | | | `number_id` | string or null | yes | | | `status` | string | yes | | | `verified_at` | string or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/sender-verifications \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ## documents - `GET /v1/documents` List documents, newest first - `POST /v1/documents` Create an immutable document from an upload or from text - `DELETE /v1/documents/{document_id}` Delete a document (its bytes and previews) - `GET /v1/documents/{document_id}` Read a document's hash, pages and validation state - `GET /v1/documents/{document_id}/content` The PDF bytes behind a signed URL (no API key) - `GET /v1/documents/{document_id}/download` A short-lived signed download URL - `GET /v1/documents/{document_id}/pages` Rendered pages with signed preview URLs - `GET /v1/documents/{document_id}/pages/{page_number}/content` A page PNG behind a signed URL (no API key) - `GET /v1/documents/{document_id}/validation` Validation warnings - `POST /v1/uploads` Create a bounded upload session - `DELETE /v1/uploads/{upload_id}` Cancel an incomplete upload - `POST /v1/uploads/{upload_id}/complete` Finalize an upload (size and hash checked) - `PUT /v1/uploads/{upload_id}/content` Send the bytes to a signed upload URL (no API key) ### GET `/v1/documents` List documents, newest first #### Parameters GET /v1/documents parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/documents responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/documents response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Document | yes | | | `next_cursor` | string or null | no | | #### 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 #### Request body application/json, required POST /v1/documents request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `filename` | string or null | no | | | `html` | string or null | no | HTML 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. | | `markdown` | string or null | no | Markdown (headings, lists, emphasis, code) rendered to PDF. | | `text` | string or null | no | Plain text rendered to a letter PDF. | | `upload_id` | string or null | no | | #### Responses POST /v1/documents responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/documents response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | yes | | | `created_at` | string | yes | | | `deleted_at` | string or null | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string or null | yes | | | `filename` | string | yes | | | `id` | string | yes | | | `page_count` | integer | yes | | | `render_reason` | string or null | yes | | | `render_status` | string | yes | | | `sha256` | string | yes | | | `size_bytes` | integer | yes | | | `source` | string | yes | | | `status` | string | yes | | | `upload_id` | string or null | yes | | | `validation` | object | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/documents \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/documents/{document_id}` Delete a document (its bytes and previews) #### Parameters DELETE /v1/documents/{document_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `document_id` | path | string | yes | | #### Responses DELETE /v1/documents/{document_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields DELETE /v1/documents/{document_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `deleted` | boolean | no | | | `id` | string | yes | | #### 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 #### Parameters GET /v1/documents/{document_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `document_id` | path | string | yes | | #### Responses GET /v1/documents/{document_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/documents/{document_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | yes | | | `created_at` | string | yes | | | `deleted_at` | string or null | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string or null | yes | | | `filename` | string | yes | | | `id` | string | yes | | | `page_count` | integer | yes | | | `render_reason` | string or null | yes | | | `render_status` | string | yes | | | `sha256` | string | yes | | | `size_bytes` | integer | yes | | | `source` | string | yes | | | `status` | string | yes | | | `upload_id` | string or null | yes | | | `validation` | object | yes | | #### 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) #### Parameters GET /v1/documents/{document_id}/content parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `document_id` | path | string | yes | | | `exp` | query | string or null | no | | | `sig` | query | string or null | no | | #### Responses GET /v1/documents/{document_id}/content responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID/content \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/documents/{document_id}/download` A short-lived signed download URL #### Parameters GET /v1/documents/{document_id}/download parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `document_id` | path | string | yes | | #### Responses GET /v1/documents/{document_id}/download responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/documents/{document_id}/download response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | yes | | | `expires_at` | string | yes | | | `url` | string | yes | | #### 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 #### Parameters GET /v1/documents/{document_id}/pages parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `document_id` | path | string | yes | | #### Responses GET /v1/documents/{document_id}/pages responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/documents/{document_id}/pages response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `document_id` | string | yes | | | `items` | array of object PagePreview | yes | | | `page_count` | integer | yes | | | `render_reason` | string or null | yes | | | `render_status` | string | yes | | #### 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) #### Parameters GET /v1/documents/{document_id}/pages/{page_number}/content parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `document_id` | path | string | yes | | | `page_number` | path | integer | yes | | | `exp` | query | string or null | no | | | `sig` | query | string or null | no | | #### Responses GET /v1/documents/{document_id}/pages/{page_number}/content responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID/pages/PAGE_NUMBER/content \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/documents/{document_id}/validation` Validation warnings #### Parameters GET /v1/documents/{document_id}/validation parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `document_id` | path | string | yes | | #### Responses GET /v1/documents/{document_id}/validation responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/documents/{document_id}/validation response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | | `page_count` | integer | yes | | | `render_reason` | string or null | yes | | | `render_status` | string | yes | | | `status` | string | yes | | | `warnings` | array of object | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/documents/DOCUMENT_ID/validation \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/uploads` Create a bounded upload session #### Request body application/json, required POST /v1/uploads request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | no | application/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. | | `filename` | string | yes | | | `size_bytes` | integer | yes | | #### Responses POST /v1/uploads responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/uploads response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `content_type` | string | yes | | | `created_at` | string | yes | | | `declared_size` | integer | yes | | | `expires_at` | string | yes | | | `filename` | string | yes | | | `id` | string | yes | | | `received_size` | integer | yes | | | `sha256` | string or null | yes | | | `status` | string | yes | | | `upload_method` | string or null | no | | | `upload_url` | string or null | no | | | `upload_url_expires_at` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/uploads \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/uploads/{upload_id}` Cancel an incomplete upload #### Parameters DELETE /v1/uploads/{upload_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `upload_id` | path | string | yes | | #### Responses DELETE /v1/uploads/{upload_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields DELETE /v1/uploads/{upload_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `deleted` | boolean | no | | | `id` | string | yes | | #### 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) #### Parameters POST /v1/uploads/{upload_id}/complete parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `upload_id` | path | string | yes | | #### Request body application/json, required POST /v1/uploads/{upload_id}/complete request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `sha256` | string or null | no | Optional: fail if the bytes differ. | #### Responses POST /v1/uploads/{upload_id}/complete responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/uploads/{upload_id}/complete response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `content_type` | string | yes | | | `created_at` | string | yes | | | `declared_size` | integer | yes | | | `expires_at` | string | yes | | | `filename` | string | yes | | | `id` | string | yes | | | `received_size` | integer | yes | | | `sha256` | string or null | yes | | | `status` | string | yes | | | `upload_method` | string or null | no | | | `upload_url` | string or null | no | | | `upload_url_expires_at` | string or null | no | | #### 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) #### Parameters PUT /v1/uploads/{upload_id}/content parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `upload_id` | path | string | yes | | | `exp` | query | string or null | no | | | `sig` | query | string or null | no | | #### Responses PUT /v1/uploads/{upload_id}/content responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PUT /v1/uploads/{upload_id}/content response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `content_type` | string | yes | | | `created_at` | string | yes | | | `declared_size` | integer | yes | | | `expires_at` | string | yes | | | `filename` | string | yes | | | `id` | string | yes | | | `received_size` | integer | yes | | | `sha256` | string or null | yes | | | `status` | string | yes | | | `upload_method` | string or null | no | | | `upload_url` | string or null | no | | | `upload_url_expires_at` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/uploads/UPLOAD_ID/content \ -X PUT \ -H "Authorization: Bearer ffa_test_..."` ``` ## events - `GET /v1/events` Events for reconciliation, oldest first from the cursor ### GET `/v1/events` Events for reconciliation, oldest first from the cursor #### Parameters GET /v1/events parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `type` | query | string or null | no | | | `cursor` | query | string or null | no | The cursor from the previous page (opaque). | | `limit` | query | integer | no | Default `100` . | #### Responses GET /v1/events responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/events response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Event | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/events \ -H "Authorization: Bearer ffa_test_..."` ``` ## quotes - `POST /v1/fax-quotes` Price an exact document to one destination - `GET /v1/fax-quotes/{quote_id}` Read a quote ### POST `/v1/fax-quotes` Price an exact document to one destination #### Request body application/json, required POST /v1/fax-quotes request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `cover` | object or null | no | Cover sheet fields: to_name, from_name, subject, message. Counts as a page. | | `document_id` | string | yes | | | `from` | string or null | no | A number you own; omit for the shared pool. | | `to` | string | yes | Destination in E.164, for example +12025550123. | #### Responses POST /v1/fax-quotes responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/fax-quotes response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `authorization_required` | boolean | yes | | | `billable_pages` | integer | yes | | | `consumed_by_job_id` | string or null | yes | | | `cover` | object or null | yes | | | `cover_pages` | integer | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | | | `document_sha256` | string | yes | | | `environment` | string (live, test) | yes | | | `expired` | boolean | yes | | | `expires_at` | string | yes | | | `from` | string or null | no | | | `id` | string | yes | | | `per_page_micro` | integer | no | | | `price_version` | string | yes | | | `route` | string | yes | | | `route_label` | string | no | | | `to` | string | yes | | | `warnings` | array of object | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/fax-quotes \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/fax-quotes/{quote_id}` Read a quote #### Parameters GET /v1/fax-quotes/{quote_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `quote_id` | path | string | yes | | #### Responses GET /v1/fax-quotes/{quote_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/fax-quotes/{quote_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `authorization_required` | boolean | yes | | | `billable_pages` | integer | yes | | | `consumed_by_job_id` | string or null | yes | | | `cover` | object or null | yes | | | `cover_pages` | integer | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | | | `document_sha256` | string | yes | | | `environment` | string (live, test) | yes | | | `expired` | boolean | yes | | | `expires_at` | string | yes | | | `from` | string or null | no | | | `id` | string | yes | | | `per_page_micro` | integer | no | | | `price_version` | string | yes | | | `route` | string | yes | | | `route_label` | string | no | | | `to` | string | yes | | | `warnings` | array of object | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/fax-quotes/QUOTE_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ## faxes - `GET /v1/faxes` List fax jobs, newest first - `POST /v1/faxes` Send a quoted fax (Idempotency-Key recommended) - `GET /v1/faxes/{fax_id}` Read a fax job (with evidence once delivered) - `GET /v1/faxes/{fax_id}/attempts` Attempt history - `POST /v1/faxes/{fax_id}/cancel` Cancel if still possible (best effort once sending) - `GET /v1/faxes/{fax_id}/events` Ordered events of one fax - `GET /v1/faxes/{fax_id}/receipt` Delivery evidence as a receipt (JSON) ### GET `/v1/faxes` List fax jobs, newest first #### Parameters GET /v1/faxes parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `state` | query | string or null | no | | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/faxes responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/faxes response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object FaxJob | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/faxes \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/faxes` Send a quoted fax (Idempotency-Key recommended) #### Request body application/json, required M9: an approved request authorizes the send; without one a quote that needs authorization is evaluated against the caller's delegated policies. POST /v1/faxes request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `approval_id` | string or null | no | An approved approval request bound to this quote. Consumed by this one send. | | `client_reference` | string or null | no | | | `quote_id` | string | yes | | #### Responses POST /v1/faxes responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/faxes response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `attempt_count` | integer | yes | | | `billable_pages` | integer | yes | | | `cancel_requested_at` | string or null | yes | | | `captured_micro` | integer | yes | | | `client_reference` | string or null | yes | | | `completed_at` | string or null | yes | | | `cover_pages` | integer | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `direction` | string | yes | | | `document_id` | string | yes | | | `document_sha256` | string | yes | | | `environment` | string (live, test) | yes | | | `evidence` | object or null | no | The carrier's evidence once delivered or partial; null before. | | `failure_code` | string or null | yes | | | `from` | string or null | no | | | `id` | string | yes | | | `pages_sent` | integer | yes | | | `provider` | string | yes | | | `provider_ref` | string or null | yes | | | `quote_id` | string or null | yes | | | `route` | string | yes | | | `state` | string (queued, active, partial, failed, unknown, delivered) | yes | | | `to` | string | yes | | | `transmitted_sha256` | string or null | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/faxes \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/faxes/{fax_id}` Read a fax job (with evidence once delivered) #### Parameters GET /v1/faxes/{fax_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `fax_id` | path | string | yes | | #### Responses GET /v1/faxes/{fax_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/faxes/{fax_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `attempt_count` | integer | yes | | | `billable_pages` | integer | yes | | | `cancel_requested_at` | string or null | yes | | | `captured_micro` | integer | yes | | | `client_reference` | string or null | yes | | | `completed_at` | string or null | yes | | | `cover_pages` | integer | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `direction` | string | yes | | | `document_id` | string | yes | | | `document_sha256` | string | yes | | | `environment` | string (live, test) | yes | | | `evidence` | object or null | no | The carrier's evidence once delivered or partial; null before. | | `failure_code` | string or null | yes | | | `from` | string or null | no | | | `id` | string | yes | | | `pages_sent` | integer | yes | | | `provider` | string | yes | | | `provider_ref` | string or null | yes | | | `quote_id` | string or null | yes | | | `route` | string | yes | | | `state` | string (queued, active, partial, failed, unknown, delivered) | yes | | | `to` | string | yes | | | `transmitted_sha256` | string or null | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/faxes/FAX_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/faxes/{fax_id}/attempts` Attempt history #### Parameters GET /v1/faxes/{fax_id}/attempts parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `fax_id` | path | string | yes | | #### Responses GET /v1/faxes/{fax_id}/attempts responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/faxes/{fax_id}/attempts response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Attempt | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/faxes/FAX_ID/attempts \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/faxes/{fax_id}/cancel` Cancel if still possible (best effort once sending) #### Parameters POST /v1/faxes/{fax_id}/cancel parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `fax_id` | path | string | yes | | #### Responses POST /v1/faxes/{fax_id}/cancel responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/faxes/{fax_id}/cancel response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `attempt_count` | integer | yes | | | `billable_pages` | integer | yes | | | `cancel_requested_at` | string or null | yes | | | `captured_micro` | integer | yes | | | `client_reference` | string or null | yes | | | `completed_at` | string or null | yes | | | `cover_pages` | integer | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `direction` | string | yes | | | `document_id` | string | yes | | | `document_sha256` | string | yes | | | `environment` | string (live, test) | yes | | | `evidence` | object or null | no | The carrier's evidence once delivered or partial; null before. | | `failure_code` | string or null | yes | | | `from` | string or null | no | | | `id` | string | yes | | | `pages_sent` | integer | yes | | | `provider` | string | yes | | | `provider_ref` | string or null | yes | | | `quote_id` | string or null | yes | | | `route` | string | yes | | | `state` | string (queued, active, partial, failed, unknown, delivered) | yes | | | `to` | string | yes | | | `transmitted_sha256` | string or null | yes | | | `updated_at` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/faxes/FAX_ID/cancel \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/faxes/{fax_id}/events` Ordered events of one fax #### Parameters GET /v1/faxes/{fax_id}/events parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `fax_id` | path | string | yes | | #### Responses GET /v1/faxes/{fax_id}/events responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/faxes/{fax_id}/events response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Event | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/faxes/FAX_ID/events \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/faxes/{fax_id}/receipt` Delivery evidence as a receipt (JSON) #### Parameters GET /v1/faxes/{fax_id}/receipt parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `fax_id` | path | string | yes | | #### Responses GET /v1/faxes/{fax_id}/receipt responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/faxes/{fax_id}/receipt response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `attempts` | array of object Attempt | yes | | | `billable_pages` | integer | yes | | | `captured_micro` | integer | yes | | | `completed_at` | string or null | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `document_sha256` | string | yes | | | `evidence` | object or null | yes | | | `failure_code` | string or null | yes | | | `fax_job_id` | string | yes | | | `from` | string or null | no | | | `generated_at` | string | yes | | | `note` | string | yes | | | `pages_sent` | integer | yes | | | `provider` | string | yes | | | `state` | string | yes | | | `to` | string | yes | | | `transmitted_sha256` | string or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/faxes/FAX_ID/receipt \ -H "Authorization: Bearer ffa_test_..."` ``` ## inbox - `GET /v1/inbox` Received faxes, newest first (metadata only) - `DELETE /v1/inbox/{inbox_id}` Delete a received fax - `GET /v1/inbox/{inbox_id}` One received fax with its text as data - `PATCH /v1/inbox/{inbox_id}` Mark a received fax read or archived - `GET /v1/inbox/{inbox_id}/content` The received PDF behind a signed URL (no API key) - `GET /v1/inbox/{inbox_id}/download` A short-lived signed link to the received PDF ### GET `/v1/inbox` Received faxes, newest first (metadata only) #### Parameters GET /v1/inbox parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | | `status` | query | string (received, read, archived) or null | no | | #### Responses GET /v1/inbox responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/inbox response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object faxforagents__api__routes__inbox__InboxItem | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/inbox \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/inbox/{inbox_id}` Delete a received fax #### Parameters DELETE /v1/inbox/{inbox_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `inbox_id` | path | string | yes | | #### Responses DELETE /v1/inbox/{inbox_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields DELETE /v1/inbox/{inbox_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `deleted` | boolean | no | | | `id` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/inbox/INBOX_ID \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/inbox/{inbox_id}` One received fax with its text as data #### Parameters GET /v1/inbox/{inbox_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `inbox_id` | path | string | yes | | #### Responses GET /v1/inbox/{inbox_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/inbox/{inbox_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | no | | | `document` | object or null | no | | | `document_id` | string or null | yes | | | `duration_secs` | integer or null | no | | | `environment` | string (live, test) | no | | | `expires_at` | string or null | no | | | `extraction_id` | string or null | no | | | `from` | string or null | no | | | `id` | string | yes | | | `media_status` | string | no | | | `note` | string or null | no | | | `number_id` | string or null | no | | | `pages` | integer | yes | | | `partial_content` | boolean | no | | | `project_id` | string or null | no | | | `provider` | string | no | | | `read_at` | string or null | no | | | `received_at` | string | yes | | | `status` | string | yes | | | `text` | string or null | no | | | `to` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/inbox/INBOX_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### PATCH `/v1/inbox/{inbox_id}` Mark a received fax read or archived #### Parameters PATCH /v1/inbox/{inbox_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `inbox_id` | path | string | yes | | #### Request body application/json, required PATCH /v1/inbox/{inbox_id} request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | string (received, read, archived) or null | no | | #### Responses PATCH /v1/inbox/{inbox_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PATCH /v1/inbox/{inbox_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | no | | | `document_id` | string or null | yes | | | `duration_secs` | integer or null | no | | | `environment` | string (live, test) | no | | | `expires_at` | string or null | no | | | `from` | string or null | no | | | `id` | string | yes | | | `media_status` | string | no | | | `note` | string or null | no | | | `number_id` | string or null | no | | | `pages` | integer | yes | | | `partial_content` | boolean | no | | | `project_id` | string or null | no | | | `provider` | string | no | | | `read_at` | string or null | no | | | `received_at` | string | yes | | | `status` | string | yes | | | `to` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/inbox/INBOX_ID \ -X PATCH \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/inbox/{inbox_id}/content` The received PDF behind a signed URL (no API key) #### Parameters GET /v1/inbox/{inbox_id}/content parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `inbox_id` | path | string | yes | | | `exp` | query | string or null | no | | | `sig` | query | string or null | no | | #### Responses GET /v1/inbox/{inbox_id}/content responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/inbox/INBOX_ID/content \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/inbox/{inbox_id}/download` A short-lived signed link to the received PDF #### Parameters GET /v1/inbox/{inbox_id}/download parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `inbox_id` | path | string | yes | | #### Responses GET /v1/inbox/{inbox_id}/download responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/inbox/{inbox_id}/download response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | yes | | | `expires_at` | string | yes | | | `url` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/inbox/INBOX_ID/download \ -H "Authorization: Bearer ffa_test_..."` ``` ## members - `GET /v1/invitations` Open invitations - `POST /v1/invitations` Invite someone by email (a link that works for 7 days) - `DELETE /v1/invitations/{invitation_id}` Withdraw an invitation - `GET /v1/members` The organization's members - `DELETE /v1/members/{member_id}` Remove a member (owner) - `PATCH /v1/members/{member_id}` Change a member's role ### GET `/v1/invitations` Open invitations #### Responses GET /v1/invitations responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/invitations response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Invitation | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/invitations \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/invitations` Invite someone by email (a link that works for 7 days) #### Request body application/json, required POST /v1/invitations request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `email` | string (email) | yes | | | `role` | string (owner, admin, member, viewer) | no | | #### Responses POST /v1/invitations responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/invitations response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `accepted_at` | string or null | yes | | | `created_at` | string | yes | | | `email` | string | yes | | | `expires_at` | string | yes | | | `id` | string | yes | | | `invited_by` | string | yes | | | `role` | string (owner, admin, member, viewer) | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/invitations \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/invitations/{invitation_id}` Withdraw an invitation #### Parameters DELETE /v1/invitations/{invitation_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `invitation_id` | path | string | yes | | #### Responses DELETE /v1/invitations/{invitation_id} responses | Status | Meaning | | --- | --- | | `204` | Successful Response | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/invitations/INVITATION_ID \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/members` The organization's members #### Responses GET /v1/members responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/members response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Member | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/members \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/members/{member_id}` Remove a member (owner) #### Parameters DELETE /v1/members/{member_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `member_id` | path | string | yes | | #### Responses DELETE /v1/members/{member_id} responses | Status | Meaning | | --- | --- | | `204` | Successful Response | | `422` | Validation Error | #### Example ``` `curl https://api.faxforagents.com/v1/members/MEMBER_ID \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### PATCH `/v1/members/{member_id}` Change a member's role #### Parameters PATCH /v1/members/{member_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `member_id` | path | string | yes | | #### Request body application/json, required PATCH /v1/members/{member_id} request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `role` | string (owner, admin, member, viewer) or null | no | Required. Optional in the schema so a foreign id is a 404 first. | #### Responses PATCH /v1/members/{member_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PATCH /v1/members/{member_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `email` | string | yes | | | `id` | string | yes | | | `name` | string or null | yes | | | `role` | string (owner, admin, member, viewer) | yes | | | `user_id` | string or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/members/MEMBER_ID \ -X PATCH \ -H "Authorization: Bearer ffa_test_..."` ``` ## organizations - `GET /v1/organizations` The organizations you belong to - `GET /v1/organizations/current` The organization you are looking at - `PATCH /v1/organizations/current` Rename the organization - `POST /v1/organizations/current/deletion-requests` Ask for the organization and its data to be deleted (owner) - `POST /v1/organizations/current/export-requests` Ask for an export of the organization's data (owner) ### GET `/v1/organizations` The organizations you belong to #### Responses GET /v1/organizations responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/organizations response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object OrganizationSummary | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/organizations \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/organizations/current` The organization you are looking at #### Responses GET /v1/organizations/current responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/organizations/current response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `id` | string | yes | | | `members` | integer | yes | | | `name` | string | yes | | | `role` | string (owner, admin, member, viewer) or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/organizations/current \ -H "Authorization: Bearer ffa_test_..."` ``` ### PATCH `/v1/organizations/current` Rename the organization #### Request body application/json, required PATCH /v1/organizations/current request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | #### Responses PATCH /v1/organizations/current responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PATCH /v1/organizations/current response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `id` | string | yes | | | `members` | integer | yes | | | `name` | string | yes | | | `role` | string (owner, admin, member, viewer) or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/organizations/current \ -X PATCH \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/organizations/current/deletion-requests` Ask for the organization and its data to be deleted (owner) #### Responses POST /v1/organizations/current/deletion-requests responses | Status | Meaning | | --- | --- | | `202` | Successful Response | #### Response fields POST /v1/organizations/current/deletion-requests response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string (export, deletion) | yes | | | `message` | string | yes | | | `requested` | boolean | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/organizations/current/deletion-requests \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/organizations/current/export-requests` Ask for an export of the organization's data (owner) #### Responses POST /v1/organizations/current/export-requests responses | Status | Meaning | | --- | --- | | `202` | Successful Response | #### Response fields POST /v1/organizations/current/export-requests response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string (export, deletion) | yes | | | `message` | string | yes | | | `requested` | boolean | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/organizations/current/export-requests \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ## policies - `GET /v1/policies` List the organization's policies - `POST /v1/policies` Create a delegated policy (version 1) - `GET /v1/policies/effective` What the calling key may do on its own, and whether a human is needed otherwise - `GET /v1/policies/{policy_id}` Read a policy (its current version) - `PATCH /v1/policies/{policy_id}` Change a policy: a new immutable version - `GET /v1/policies/{policy_id}/versions` Every version, oldest first - `POST /v1/policy-evaluations` Dry run: would the calling key's policies allow this send? Never executes ### GET `/v1/policies` List the organization's policies #### Responses GET /v1/policies responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/policies response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Policy | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/policies \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/policies` Create a delegated policy (version 1) #### Request body application/json, required POST /v1/policies request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `enabled` | boolean | no | | | `name` | string | yes | | | `purpose` | string or null | no | Why this policy exists. Shown, never evaluated. | | `rules` | object | yes | See the PolicyRules schema in docs/APPROVALS.md. | #### Responses POST /v1/policies responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/policies response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `current_version_id` | string or null | yes | | | `enabled` | boolean | yes | | | `id` | string | yes | | | `name` | string | yes | | | `purpose` | string or null | yes | | | `rules` | object | yes | | | `updated_at` | string | yes | | | `version` | integer | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/policies \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/policies/effective` What the calling key may do on its own, and whether a human is needed otherwise #### Responses GET /v1/policies/effective responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/policies/effective response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `actor` | string | yes | | | `approval_required_without_policy` | boolean | yes | | | `environment` | string (live, test) | yes | | | `policies` | array of object Policy | yes | | | `rules_order` | array of string | yes | | | `under_policy` | boolean | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/policies/effective \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/policies/{policy_id}` Read a policy (its current version) #### Parameters GET /v1/policies/{policy_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `policy_id` | path | string | yes | | #### Responses GET /v1/policies/{policy_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/policies/{policy_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `current_version_id` | string or null | yes | | | `enabled` | boolean | yes | | | `id` | string | yes | | | `name` | string | yes | | | `purpose` | string or null | yes | | | `rules` | object | yes | | | `updated_at` | string | yes | | | `version` | integer | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/policies/POLICY_ID \ -H "Authorization: Bearer ffa_test_..."` ``` ### PATCH `/v1/policies/{policy_id}` Change a policy: a new immutable version #### Parameters PATCH /v1/policies/{policy_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `policy_id` | path | string | yes | | #### Request body application/json, required PATCH /v1/policies/{policy_id} request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `enabled` | boolean or null | no | | | `name` | string or null | no | | | `purpose` | string or null | no | | | `rules` | object or null | no | | #### Responses PATCH /v1/policies/{policy_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PATCH /v1/policies/{policy_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `current_version_id` | string or null | yes | | | `enabled` | boolean | yes | | | `id` | string | yes | | | `name` | string | yes | | | `purpose` | string or null | yes | | | `rules` | object | yes | | | `updated_at` | string | yes | | | `version` | integer | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/policies/POLICY_ID \ -X PATCH \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/policies/{policy_id}/versions` Every version, oldest first #### Parameters GET /v1/policies/{policy_id}/versions parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `policy_id` | path | string | yes | | #### Responses GET /v1/policies/{policy_id}/versions responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/policies/{policy_id}/versions response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object PolicyVersion | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/policies/POLICY_ID/versions \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/policy-evaluations` Dry run: would the calling key's policies allow this send? Never executes #### Request body application/json, required POST /v1/policy-evaluations request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `document_id` | string or null | no | Or plan: this document ... | | `from` | string or null | no | | | `quote_id` | string or null | no | Evaluate an existing quote. | | `to` | string or null | no | ... to this E.164 destination. | #### Responses POST /v1/policy-evaluations responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/policy-evaluations response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `decision` | string | yes | | | `denials` | array of object | yes | | | `executed` | boolean | no | | | `plan` | object | yes | | | `policy_id` | string or null | yes | | | `policy_name` | string or null | yes | | | `policy_version_id` | string or null | yes | | | `reason` | string | yes | | | `rule` | string or null | yes | | | `version` | integer or null | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/policy-evaluations \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ## sandbox - `DELETE /v1/sandbox/data` Reset this organization's test data - `POST /v1/sandbox/inbound-faxes` Create a synthetic inbound fax (the same pipeline as a real one) - `GET /v1/sandbox/scenarios` The scenario catalog and active fixtures - `POST /v1/sandbox/scenarios` Select the outcome of the next sends ### DELETE `/v1/sandbox/data` Reset this organization's test data #### Responses DELETE /v1/sandbox/data responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields DELETE /v1/sandbox/data response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `deleted` | object | yes | | | `reset` | boolean | yes | | | `wallet_available_micro` | integer | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/sandbox/data \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/sandbox/inbound-faxes` Create a synthetic inbound fax (the same pipeline as a real one) A synthetic PDF goes through ``inbox.service.receive_now``: the private document, the inbox item, the ``fax.received`` event with its download reference, exactly as a carrier delivery (M8). #### Request body application/json, required POST /v1/sandbox/inbound-faxes request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `from` | string or null | no | | | `pages` | integer | no | | | `partial_content` | boolean | no | Rehearse a cut-off transmission. | | `to` | string | yes | | #### Responses POST /v1/sandbox/inbound-faxes responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/sandbox/inbound-faxes response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | no | | | `document_id` | string or null | yes | | | `from` | string or null | no | | | `id` | string | yes | | | `note` | string or null | no | | | `pages` | integer | yes | | | `received_at` | string | yes | | | `status` | string | yes | | | `to` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/sandbox/inbound-faxes \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/sandbox/scenarios` The scenario catalog and active fixtures #### Responses GET /v1/sandbox/scenarios responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/sandbox/scenarios response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `destinations` | array of object | yes | | | `fixtures` | array of object SandboxFixture | yes | | | `scenarios` | array of object | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/sandbox/scenarios \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/sandbox/scenarios` Select the outcome of the next sends #### Request body application/json, required POST /v1/sandbox/scenarios request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `destination` | string or null | no | Apply to one destination; omit for every send. | | `scenario` | string | yes | | #### Responses POST /v1/sandbox/scenarios responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/sandbox/scenarios response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `destination` | string or null | yes | | | `expires_at` | string | yes | | | `id` | string | yes | | | `scenario` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/sandbox/scenarios \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ## webhooks - `GET /v1/webhook-deliveries` Delivery attempts (no payload secrets) - `POST /v1/webhook-deliveries/{delivery_id}/replay` Replay the event with the same event id - `GET /v1/webhooks` List webhooks - `POST /v1/webhooks` Register a destination (secret shown once) - `DELETE /v1/webhooks/{webhook_id}` Remove a destination - `PATCH /v1/webhooks/{webhook_id}` Change url, filters or pause - `POST /v1/webhooks/{webhook_id}/rotate-secret` New secret; the old one signs too for 24 hours - `POST /v1/webhooks/{webhook_id}/test` Send a signed webhook.test event to this destination ### GET `/v1/webhook-deliveries` Delivery attempts (no payload secrets) #### Parameters GET /v1/webhook-deliveries parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `webhook_id` | query | string or null | no | | | `event_id` | query | string or null | no | | | `cursor` | query | string or null | no | | | `limit` | query | integer | no | Default `50` . | #### Responses GET /v1/webhook-deliveries responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields GET /v1/webhook-deliveries response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Delivery | yes | | | `next_cursor` | string or null | no | | #### Example ``` `curl https://api.faxforagents.com/v1/webhook-deliveries \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/webhook-deliveries/{delivery_id}/replay` Replay the event with the same event id #### Parameters POST /v1/webhook-deliveries/{delivery_id}/replay parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `delivery_id` | path | string | yes | | #### Responses POST /v1/webhook-deliveries/{delivery_id}/replay responses | Status | Meaning | | --- | --- | | `202` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/webhook-deliveries/{delivery_id}/replay response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `attempt` | integer | yes | | | `created_at` | string | yes | | | `delivered_at` | string or null | yes | | | `duration_ms` | integer or null | yes | | | `error` | string or null | yes | | | `event_id` | string | yes | | | `event_type` | string | yes | | | `id` | string | yes | | | `next_attempt_at` | string or null | yes | | | `replay_of` | string or null | yes | | | `response_status` | integer or null | yes | | | `schedule_s` | array of integer | yes | | | `status` | string | yes | | | `webhook_id` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/webhook-deliveries/DELIVERY_ID/replay \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### GET `/v1/webhooks` List webhooks #### Responses GET /v1/webhooks responses | Status | Meaning | | --- | --- | | `200` | Successful Response | #### Response fields GET /v1/webhooks response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Webhook | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/webhooks \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/webhooks` Register a destination (secret shown once) #### Request body application/json, required POST /v1/webhooks request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `description` | string or null | no | | | `event_types` | array of string or null | no | Empty or omitted: every event type. | | `url` | string | yes | | #### Responses POST /v1/webhooks responses | Status | Meaning | | --- | --- | | `201` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/webhooks response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `description` | string or null | yes | | | `event_types` | array of string | yes | | | `id` | string | yes | | | `previous_secret_expires_at` | string or null | yes | | | `secret` | string | yes | whsec_... Shown once. Verify webhook-signature with it. | | `status` | string | yes | | | `updated_at` | string | yes | | | `url` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/webhooks \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### DELETE `/v1/webhooks/{webhook_id}` Remove a destination #### Parameters DELETE /v1/webhooks/{webhook_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `webhook_id` | path | string | yes | | #### Responses DELETE /v1/webhooks/{webhook_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields DELETE /v1/webhooks/{webhook_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `deleted` | boolean | no | | | `id` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/webhooks/WEBHOOK_ID \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."` ``` ### PATCH `/v1/webhooks/{webhook_id}` Change url, filters or pause #### Parameters PATCH /v1/webhooks/{webhook_id} parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `webhook_id` | path | string | yes | | #### Request body application/json, required PATCH /v1/webhooks/{webhook_id} request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `description` | string or null | no | | | `event_types` | array of string or null | no | | | `status` | string (active, paused) or null | no | | | `url` | string or null | no | | #### Responses PATCH /v1/webhooks/{webhook_id} responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields PATCH /v1/webhooks/{webhook_id} response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `description` | string or null | yes | | | `event_types` | array of string | yes | | | `id` | string | yes | | | `previous_secret_expires_at` | string or null | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | | `url` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/webhooks/WEBHOOK_ID \ -X PATCH \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/webhooks/{webhook_id}/rotate-secret` New secret; the old one signs too for 24 hours #### Parameters POST /v1/webhooks/{webhook_id}/rotate-secret parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `webhook_id` | path | string | yes | | #### Responses POST /v1/webhooks/{webhook_id}/rotate-secret responses | Status | Meaning | | --- | --- | | `200` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/webhooks/{webhook_id}/rotate-secret response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `description` | string or null | yes | | | `event_types` | array of string | yes | | | `id` | string | yes | | | `previous_secret_expires_at` | string or null | yes | | | `secret` | string | yes | whsec_... Shown once. Verify webhook-signature with it. | | `status` | string | yes | | | `updated_at` | string | yes | | | `url` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/webhooks/WEBHOOK_ID/rotate-secret \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ### POST `/v1/webhooks/{webhook_id}/test` Send a signed webhook.test event to this destination #### Parameters POST /v1/webhooks/{webhook_id}/test parameters | Name | In | Type | Required | Notes | | --- | --- | --- | --- | --- | | `webhook_id` | path | string | yes | | #### Responses POST /v1/webhooks/{webhook_id}/test responses | Status | Meaning | | --- | --- | | `202` | Successful Response | | `422` | Validation Error | #### Response fields POST /v1/webhooks/{webhook_id}/test response fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `correlation_id` | string or null | yes | | | `created_at` | string | yes | | | `data` | object | yes | | | `environment` | string (live, test) | yes | | | `fax_job_id` | string or null | yes | | | `id` | string | yes | | | `org_id` | string | yes | | | `project_id` | string or null | yes | | | `resource_id` | string | yes | | | `resource_type` | string | yes | | | `schema_version` | integer | yes | | | `type` | string | yes | | #### Example ``` `curl https://api.faxforagents.com/v1/webhooks/WEBHOOK_ID/test \ -X POST \ -H "Authorization: Bearer ffa_test_..."` ``` ## Schemas 147 named objects, as the API sends and accepts them. ### Accepted Accepted fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `organization` | object OrganizationSummary | yes | | ### Agent Agent fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `budget_id` | string or null | yes | | | `budgets` | array of object | yes | | | `contact_email` | string or null | yes | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `disabled_at` | string or null | yes | | | `enabled` | boolean | yes | | | `id` | string | yes | | | `keys` | array of object ApiKey | yes | | | `name` | string | yes | | | `policy_ids` | array of string | yes | | | `project_id` | string or null | yes | | | `purpose` | string or null | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | ### AgentActivity AgentActivity fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `action` | string | yes | | | `actor` | string | yes | | | `correlation_id` | string or null | yes | | | `created_at` | string | yes | | | `data` | object | yes | | | `id` | string | yes | | | `resource_id` | string or null | yes | | | `resource_type` | string | yes | | ### AgentActivityPage AgentActivityPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object AgentActivity | yes | | | `next_cursor` | string or null | no | | ### AgentCreate AgentCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `contact_email` | string or null | no | Where decided approvals are mailed. | | `name` | string | yes | | | `policy_ids` | array of string | no | | | `purpose` | string or null | no | Shown to humans, never evaluated. | ### AgentKeyCreate AgentKeyCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `environment` | string (live, test) | no | | | `expires_in_days` | integer or null | no | | | `label` | string | no | | | `scopes` | array of string or null | no | Omit for the default agent set. Never approval:decide, key:admin, policy:admin, agent:admin. | ### AgentList AgentList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Agent | yes | | ### AgentUpdate AgentUpdate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `contact_email` | string or null | no | | | `enabled` | boolean or null | no | false revokes every key of the agent. | | `name` | string or null | no | | | `policy_ids` | array of string or null | no | | | `purpose` | string or null | no | | ### ApiKey ApiKey fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `label` | string | yes | | | `last_used_at` | string or null | yes | | | `prefix` | string | yes | | | `project_id` | string or null | yes | | | `revoked_at` | string or null | yes | | | `rotated_from_id` | string or null | yes | | | `scopes` | array of string | yes | | ### ApiKeyCreate ApiKeyCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `environment` | string (live, test) | no | | | `expires_in_days` | integer or null | no | | | `label` | string | yes | | | `scopes` | array of string or null | no | Omit for the default agent set; '*' for all. | ### ApiKeyList ApiKeyList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object ApiKey | yes | | ### ApiKeyWithSecret ApiKeyWithSecret fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `label` | string | yes | | | `last_used_at` | string or null | yes | | | `prefix` | string | yes | | | `project_id` | string or null | yes | | | `revoked_at` | string or null | yes | | | `rotated_from_id` | string or null | yes | | | `scopes` | array of string | yes | | | `secret` | string | yes | Shown once. Store it now; it cannot be read back. | ### ApprovalDecision ApprovalDecision fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `reason` | string or null | no | | ### ApprovalRequest ApprovalRequest fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `bound` | object | yes | | | `consumed_by_job_id` | string or null | yes | | | `created_at` | string | yes | | | `decided_at` | string or null | yes | | | `decided_by` | string or null | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `kind` | string (send, help) | yes | | | `message` | string or null | yes | | | `notified` | boolean or null | no | On creation: whether the organization was mailed. false when the request repeats a pending one or the organization's daily cap of request mails is reached; the request is still shown in the dashboard. | | `quote_id` | string or null | yes | | | `reason` | string or null | yes | | | `requested_by` | string | yes | | | `status` | string (pending, approved, rejected, revoked, expired) | yes | | ### ApprovalRequestCreate ApprovalRequestCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `fax_job_id` | string or null | no | | | `kind` | string (send, help) | no | | | `message` | string or null | no | What the person should know (kind help). Shown as data. | | `quote_id` | string or null | no | The quote to bind (kind send). | ### ApprovalRequestPage ApprovalRequestPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object ApprovalRequest | yes | | | `next_cursor` | string or null | no | | ### Attempt Attempt fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `attempt_number` | integer | yes | | | `error` | string or null | yes | | | `evidence` | object or null | yes | | | `failure_code` | string or null | yes | | | `fax_job_id` | string | yes | | | `finished_at` | string or null | yes | | | `id` | string | yes | | | `pages_sent` | integer | yes | | | `provider` | string | yes | | | `provider_ref` | string or null | yes | | | `started_at` | string | yes | | | `status` | string | yes | | ### AttemptPage AttemptPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Attempt | yes | | ### AutoTopup AutoTopup fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `created_at` | string | yes | | | `daily_cap_micro` | integer | yes | | | `disabled_at` | string or null | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `last_attempt_at` | string or null | yes | | | `payment_method_saved` | boolean | yes | | | `setup_session_id` | string or null | yes | | | `setup_url` | string or null | no | Where to save the card while status is pending_setup. | | `status` | string (pending_setup, active, disabled) | yes | | | `threshold_micro` | integer | yes | | | `updated_at` | string | yes | | ### AutoTopupCreate AutoTopupCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | Whole dollars, 10 to 1000 USD. | | `daily_cap_micro` | integer | yes | Never more than this per UTC day. | | `threshold_micro` | integer | yes | Top up when the available balance drops below this. | ### AutoTopupList AutoTopupList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object AutoTopup | yes | | ### Balance Balance fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `available_micro` | integer | yes | | | `currency` | string | yes | | | `environment` | string (live, test) | yes | | | `held_micro` | integer | yes | | ### BillingSettings BillingSettings fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `billing_email` | string or null | yes | | | `disputed` | boolean | yes | | | `low_balance_active` | boolean | yes | | | `low_balance_threshold_micro` | integer | yes | | | `org_id` | string | yes | | | `stripe_customer` | boolean | yes | | | `workspace` | boolean | yes | | ### BillingSettingsPatch BillingSettingsPatch fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `billing_email` | string or null | no | | | `low_balance_threshold_micro` | integer or null | no | | ### Budget Budget fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `limit_micro` | integer | yes | | | `period` | string (daily, monthly) | yes | | | `project_id` | string or null | yes | | | `remaining_micro` | integer | yes | | | `resets_at` | string | yes | | | `scope` | string (organization, project, agent) | yes | | | `spent_micro` | integer | yes | | | `status` | string (active, disabled) | yes | | | `updated_at` | string | yes | | ### BudgetCreate BudgetCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `agent_id` | string or null | no | | | `limit_micro` | integer | yes | | | `period` | string (daily, monthly) | yes | | | `project_id` | string or null | no | | | `scope` | string (organization, project, agent) | no | | ### BudgetList BudgetList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Budget | yes | | ### BudgetPatch BudgetPatch fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `limit_micro` | integer or null | no | | | `status` | string (active, disabled) or null | no | | ### Capabilities Capabilities fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `approvals` | boolean | yes | | | `environment` | string (live, test) | yes | | | `formats` | array of string | yes | | | `limits` | object | yes | | | `max_pages` | integer | yes | | | `max_upload_bytes` | integer | yes | | | `numbers` | boolean | yes | | | `price_version` | string | yes | | | `provider` | string | yes | | | `rates` | object | yes | | | `receive` | boolean | yes | | | `routes` | array of string | yes | | | `scopes` | array of string | yes | | | `send` | boolean | yes | | ### ClassificationCreate ClassificationCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `abstain_below` | number | no | | | `document_id` | string or null | no | | | `document_types` | array of string | yes | | | `inbox_item_id` | string or null | no | | | `max_pages` | integer or null | no | | | `strict` | boolean | no | Refuse with page_limit_exceeded when the document has more pages than would be read, instead of reading the first ones and warning. | | `strong_model_if_below` | number | no | | ### Correction Correction fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `extraction_id` | string | yes | | | `field` | string | yes | | | `id` | string | yes | | | `note` | string or null | yes | | | `value` | any | yes | | ### CorrectionCreate CorrectionCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `field` | string or null | no | | | `note` | string or null | no | | | `value` | any | no | | ### Coverage Coverage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `coverage_version` | string or null | yes | | | `excluded` | object | yes | | | `generated_at` | string or null | yes | | | `inbound` | object | yes | | | `note` | string or null | yes | | | `orderable_now` | array of string | yes | | | `outbound` | object | yes | | ### Deleted Deleted fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `deleted` | boolean | no | | | `id` | string | yes | | ### Delivery Delivery fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `attempt` | integer | yes | | | `created_at` | string | yes | | | `delivered_at` | string or null | yes | | | `duration_ms` | integer or null | yes | | | `error` | string or null | yes | | | `event_id` | string | yes | | | `event_type` | string | yes | | | `id` | string | yes | | | `next_attempt_at` | string or null | yes | | | `replay_of` | string or null | yes | | | `response_status` | integer or null | yes | | | `schedule_s` | array of integer | yes | | | `status` | string | yes | | | `webhook_id` | string | yes | | ### DeliveryPage DeliveryPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Delivery | yes | | | `next_cursor` | string or null | no | | ### Document Document fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | yes | | | `created_at` | string | yes | | | `deleted_at` | string or null | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string or null | yes | | | `filename` | string | yes | | | `id` | string | yes | | | `page_count` | integer | yes | | | `render_reason` | string or null | yes | | | `render_status` | string | yes | | | `sha256` | string | yes | | | `size_bytes` | integer | yes | | | `source` | string | yes | | | `status` | string | yes | | | `upload_id` | string or null | yes | | | `validation` | object | yes | | ### DocumentCreate DocumentCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `filename` | string or null | no | | | `html` | string or null | no | HTML 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. | | `markdown` | string or null | no | Markdown (headings, lists, emphasis, code) rendered to PDF. | | `text` | string or null | no | Plain text rendered to a letter PDF. | | `upload_id` | string or null | no | | ### DocumentPage DocumentPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Document | yes | | | `next_cursor` | string or null | no | | ### DocumentPages DocumentPages fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `document_id` | string | yes | | | `items` | array of object PagePreview | yes | | | `page_count` | integer | yes | | | `render_reason` | string or null | yes | | | `render_status` | string | yes | | ### DocumentValidation DocumentValidation fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | | `page_count` | integer | yes | | | `render_reason` | string or null | yes | | | `render_status` | string | yes | | | `status` | string | yes | | | `warnings` | array of object | yes | | ### EffectivePolicies EffectivePolicies fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `actor` | string | yes | | | `approval_required_without_policy` | boolean | yes | | | `environment` | string (live, test) | yes | | | `policies` | array of object Policy | yes | | | `rules_order` | array of string | yes | | | `under_policy` | boolean | yes | | ### Event Event fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `correlation_id` | string or null | yes | | | `created_at` | string | yes | | | `data` | object | yes | | | `environment` | string (live, test) | yes | | | `fax_job_id` | string or null | yes | | | `id` | string | yes | | | `org_id` | string | yes | | | `project_id` | string or null | yes | | | `resource_id` | string | yes | | | `resource_type` | string | yes | | | `schema_version` | integer | yes | | | `type` | string | yes | | ### EventPage EventPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Event | yes | | | `next_cursor` | string or null | no | | ### Extraction Extraction fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `confidence` | number or null | yes | | | `corrections` | array of object or null | no | | | `created_at` | string | yes | | | `created_by` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | The document's page count. Above pages_processed on a completed row means the cap left pages unread; output.truncated and a warning say so. | | `environment` | string (live, test) | yes | | | `error_code` | string or null | yes | | | `id` | string | yes | | | `inbox_item_id` | string or null | yes | | | `kind` | string (extract, classify, summarize) | yes | | | `models` | object | yes | | | `options` | object | yes | | | `output` | object or null | yes | | | `pages` | array of object ExtractionPage | yes | | | `pages_escalated` | integer | yes | | | `pages_processed` | integer | yes | | | `pages_requested` | integer | yes | | | `price_micro` | integer | yes | | | `provider_cost_micro` | integer | yes | | | `schema` | object or null | no | | | `schema_id` | string or null | yes | | | `started_at` | string or null | yes | | | `status` | string (pending, queued, running, completed, failed) | yes | | ### ExtractionCreate ExtractionCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `document_id` | string or null | no | | | `inbox_item_id` | string or null | no | | | `max_pages` | integer or null | no | | | `schema` | object or null | no | A JSON Schema object of string, number, boolean or date fields with descriptions. | | `schema_id` | string or null | no | | | `strict` | boolean | no | Refuse with page_limit_exceeded when the document has more pages than would be read, instead of reading the first ones and warning. | | `strong_model_if_below` | number | no | | ### ExtractionPage ExtractionPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `confidence` | number | yes | | | `escalated` | boolean | no | | | `language` | string or null | no | | | `model` | string | yes | | | `page` | integer | yes | | | `status` | string | yes | | | `text` | string | yes | | | `warnings` | array of string | no | | ### ExtractionPageList ExtractionPageList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Extraction | yes | | | `next_cursor` | string or null | no | | ### ExtractionSchema ExtractionSchema fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `description` | string or null | yes | | | `id` | string | yes | | | `name` | string | yes | | | `schema` | object | yes | | | `status` | string | yes | | | `version` | integer | yes | | ### ExtractionSchemaList ExtractionSchemaList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object ExtractionSchema | yes | | ### FaxCreate M9: an approved request authorizes the send; without one a quote that needs authorization is evaluated against the caller's delegated policies. FaxCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `approval_id` | string or null | no | An approved approval request bound to this quote. Consumed by this one send. | | `client_reference` | string or null | no | | | `quote_id` | string | yes | | ### FaxJob FaxJob fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `attempt_count` | integer | yes | | | `billable_pages` | integer | yes | | | `cancel_requested_at` | string or null | yes | | | `captured_micro` | integer | yes | | | `client_reference` | string or null | yes | | | `completed_at` | string or null | yes | | | `cover_pages` | integer | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `direction` | string | yes | | | `document_id` | string | yes | | | `document_sha256` | string | yes | | | `environment` | string (live, test) | yes | | | `evidence` | object or null | no | The carrier's evidence once delivered or partial; null before. | | `failure_code` | string or null | yes | | | `from` | string or null | no | | | `id` | string | yes | | | `pages_sent` | integer | yes | | | `provider` | string | yes | | | `provider_ref` | string or null | yes | | | `quote_id` | string or null | yes | | | `route` | string | yes | | | `state` | string (queued, active, partial, failed, unknown, delivered) | yes | | | `to` | string | yes | | | `transmitted_sha256` | string or null | yes | | | `updated_at` | string | yes | | ### FaxJobPage FaxJobPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object FaxJob | yes | | | `next_cursor` | string or null | no | | ### HTTPValidationError HTTPValidationError fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `detail` | array of object ValidationError | no | | ### InboxItemDetail InboxItemDetail fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | no | | | `document` | object or null | no | | | `document_id` | string or null | yes | | | `duration_secs` | integer or null | no | | | `environment` | string (live, test) | no | | | `expires_at` | string or null | no | | | `extraction_id` | string or null | no | | | `from` | string or null | no | | | `id` | string | yes | | | `media_status` | string | no | | | `note` | string or null | no | | | `number_id` | string or null | no | | | `pages` | integer | yes | | | `partial_content` | boolean | no | | | `project_id` | string or null | no | | | `provider` | string | no | | | `read_at` | string or null | no | | | `received_at` | string | yes | | | `status` | string | yes | | | `text` | string or null | no | | | `to` | string | yes | | ### InboxPage InboxPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object faxforagents__api__routes__inbox__InboxItem | yes | | | `next_cursor` | string or null | no | | ### InboxUpdate InboxUpdate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | string (received, read, archived) or null | no | | ### Inventory Inventory fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `country` | string | yes | | | `items` | array of object InventoryNumber | yes | | | `locality` | string or null | yes | | | `note` | string | yes | | | `number_type` | string | yes | | | `recurring_period` | string | yes | | ### InventoryNumber InventoryNumber fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `country` | string | yes | | | `currency` | string | yes | | | `e164` | string | yes | | | `locality` | string or null | yes | | | `monthly_micro` | integer | yes | | | `number_type` | string | yes | | | `setup_micro` | integer | yes | | ### Invitation Invitation fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `accepted_at` | string or null | yes | | | `created_at` | string | yes | | | `email` | string | yes | | | `expires_at` | string | yes | | | `id` | string | yes | | | `invited_by` | string | yes | | | `role` | string (owner, admin, member, viewer) | yes | | ### InvitationCreate InvitationCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `email` | string (email) | yes | | | `role` | string (owner, admin, member, viewer) | no | | ### InvitationList InvitationList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Invitation | yes | | ### Invoice Invoice fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_due_micro` | integer | yes | | | `amount_paid_micro` | integer | yes | | | `created_at` | string or null | yes | | | `currency` | string | yes | | | `hosted_invoice_url` | string or null | yes | | | `id` | string | yes | | | `invoice_pdf` | string or null | yes | | | `number` | string or null | yes | | | `status` | string or null | yes | | | `subscription_id` | string or null | yes | | ### InvoicePage InvoicePage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Invoice | yes | | | `next_cursor` | string or null | no | | ### LedgerEntry LedgerEntry fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `account` | string | yes | | | `amount_micro` | integer | yes | | | `created_at` | string | yes | | | `id` | string | yes | | | `kind` | string | yes | | | `memo` | string or null | yes | | | `ref_id` | string or null | yes | | | `ref_type` | string or null | yes | | | `txn_id` | string | yes | | ### LedgerPage LedgerPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object LedgerEntry | yes | | | `next_cursor` | string or null | no | | ### MagicLinkRequest MagicLinkRequest fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `email` | string (email) | yes | | | `next` | string or null | no | An in-app path to open after sign-in. | ### Member Member fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `email` | string | yes | | | `id` | string | yes | | | `name` | string or null | yes | | | `role` | string (owner, admin, member, viewer) | yes | | | `user_id` | string or null | yes | | ### MemberList MemberList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Member | yes | | ### MemberPatch MemberPatch fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `role` | string (owner, admin, member, viewer) or null | no | Required. Optional in the schema so a foreign id is a 404 first. | ### Number Number fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `activated_at` | string or null | yes | | | `country` | string | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `e164` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `inbound_status` | string | yes | | | `inbound_webhook_id` | string or null | yes | | | `label` | string or null | yes | | | `locality` | string or null | yes | | | `monthly_micro` | integer | yes | | | `number_type` | string | yes | | | `order_id` | string or null | yes | | | `project_id` | string or null | yes | | | `provider` | string | yes | | | `released_at` | string or null | yes | | | `renews_at` | string or null | yes | | | `status` | string | yes | | ### NumberList NumberList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Number | yes | | ### NumberOrder NumberOrder fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `country` | string or null | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `e164` | string or null | yes | | | `environment` | string (live, test) | yes | | | `error` | string or null | yes | | | `first_month_hold_micro` | integer | yes | Held now, captured when the number activates. | | `id` | string | yes | | | `number` | object Number or null | yes | | | `number_id` | string or null | yes | | | `number_type` | string or null | yes | | | `provider` | string | yes | | | `recurring_charge_micro` | integer | yes | Charged every month on the activation day. | | `recurring_period` | string | yes | | | `status` | string | yes | | ### NumberOrderCreate NumberOrderCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `e164` | string | yes | A number from GET /v1/number-inventory, E.164. | | `inbound_webhook_id` | string or null | no | The webhook that receives fax.received for this number (default: all). | | `label` | string or null | no | | | `project_id` | string or null | no | Inbound faxes are filed under this project. | ### NumberUpdate NumberUpdate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `inbound_webhook_id` | string or null | no | | | `label` | string or null | no | | | `project_id` | string or null | no | | ### Organization Organization fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `id` | string | yes | | | `members` | integer | yes | | | `name` | string | yes | | | `role` | string (owner, admin, member, viewer) or null | yes | | ### OrganizationList OrganizationList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object OrganizationSummary | yes | | ### OrganizationPatch OrganizationPatch fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | ### OrganizationSummary OrganizationSummary fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | | `name` | string | yes | | | `role` | string (owner, admin, member, viewer) | yes | | ### PagePreview PagePreview fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `height_px` | integer or null | yes | | | `page_number` | integer | yes | | | `url` | string | yes | | | `width_px` | integer or null | yes | | ### Passkey Passkey fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `aaguid` | string or null | yes | | | `backup_eligible` | boolean | yes | | | `created_at` | string | yes | | | `id` | string | yes | | | `label` | string or null | yes | | | `last_used_at` | string or null | yes | | | `transports` | array of string or null | yes | | ### PasskeyList PasskeyList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Passkey | yes | | ### PasskeyLoginVerify PasskeyLoginVerify fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `credential` | object | yes | | ### PasskeyRegisterVerify PasskeyRegisterVerify fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `credential` | object | yes | | | `label` | string or null | no | | ### Policy Policy fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `current_version_id` | string or null | yes | | | `enabled` | boolean | yes | | | `id` | string | yes | | | `name` | string | yes | | | `purpose` | string or null | yes | | | `rules` | object | yes | | | `updated_at` | string | yes | | | `version` | integer | yes | | ### PolicyCreate PolicyCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `enabled` | boolean | no | | | `name` | string | yes | | | `purpose` | string or null | no | Why this policy exists. Shown, never evaluated. | | `rules` | object | yes | See the PolicyRules schema in docs/APPROVALS.md. | ### PolicyEvaluation PolicyEvaluation fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `decision` | string | yes | | | `denials` | array of object | yes | | | `executed` | boolean | no | | | `plan` | object | yes | | | `policy_id` | string or null | yes | | | `policy_name` | string or null | yes | | | `policy_version_id` | string or null | yes | | | `reason` | string | yes | | | `rule` | string or null | yes | | | `version` | integer or null | yes | | ### PolicyEvaluationCreate PolicyEvaluationCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `document_id` | string or null | no | Or plan: this document ... | | `from` | string or null | no | | | `quote_id` | string or null | no | Evaluate an existing quote. | | `to` | string or null | no | ... to this E.164 destination. | ### PolicyList PolicyList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Policy | yes | | ### PolicyUpdate PolicyUpdate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `enabled` | boolean or null | no | | | `name` | string or null | no | | | `purpose` | string or null | no | | | `rules` | object or null | no | | ### PolicyVersion PolicyVersion fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `created_by` | string | yes | | | `enabled` | boolean | yes | | | `id` | string | yes | | | `name` | string | yes | | | `policy_id` | string | yes | | | `purpose` | string or null | yes | | | `rules` | object | yes | | | `version` | integer | yes | | ### PolicyVersionList PolicyVersionList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object PolicyVersion | yes | | ### PortOrder PortOrder fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `direction` | string | yes | | | `environment` | string (live, test) | yes | | | `id` | string | yes | | | `milestones` | array of object | yes | | | `numbers` | array of string | yes | | | `provider` | string | yes | | | `request` | object | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | ### PortOrderCreate PortOrderCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `note` | string or null | no | | | `numbers` | array of string | yes | | ### PortOrderList PortOrderList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object PortOrder | yes | | ### PortOutRequestCreate PortOutRequestCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `note` | string or null | no | | | `number_id` | string | yes | | ### PortabilityCheck PortabilityCheck fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `checked_at` | string | yes | | | `items` | array of object | yes | | ### PortabilityCheckCreate PortabilityCheckCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `numbers` | array of string | yes | | ### PortalSession PortalSession fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `expires_at` | string | yes | | | `url` | string | yes | | ### Providers Providers fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `github` | boolean | yes | | | `google` | boolean | yes | | | `magic_link` | boolean | no | | | `passkeys` | boolean | no | | ### Quote Quote fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `authorization_required` | boolean | yes | | | `billable_pages` | integer | yes | | | `consumed_by_job_id` | string or null | yes | | | `cover` | object or null | yes | | | `cover_pages` | integer | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `document_id` | string | yes | | | `document_pages` | integer | yes | | | `document_sha256` | string | yes | | | `environment` | string (live, test) | yes | | | `expired` | boolean | yes | | | `expires_at` | string | yes | | | `from` | string or null | no | | | `id` | string | yes | | | `per_page_micro` | integer | no | | | `price_version` | string | yes | | | `route` | string | yes | | | `route_label` | string | no | | | `to` | string | yes | | | `warnings` | array of object | yes | | ### QuoteCreate QuoteCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `cover` | object or null | no | Cover sheet fields: to_name, from_name, subject, message. Counts as a page. | | `document_id` | string | yes | | | `from` | string or null | no | A number you own; omit for the shared pool. | | `to` | string | yes | Destination in E.164, for example +12025550123. | ### Reauth Reauth fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `credential` | object or null | no | | | `method` | string (passkey, magic_link) | yes | | ### Receipt Receipt fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `attempts` | array of object Attempt | yes | | | `billable_pages` | integer | yes | | | `captured_micro` | integer | yes | | | `completed_at` | string or null | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `document_sha256` | string | yes | | | `evidence` | object or null | yes | | | `failure_code` | string or null | yes | | | `fax_job_id` | string | yes | | | `from` | string or null | no | | | `generated_at` | string | yes | | | `note` | string | yes | | | `pages_sent` | integer | yes | | | `provider` | string | yes | | | `state` | string | yes | | | `to` | string | yes | | | `transmitted_sha256` | string or null | yes | | ### ReleaseRequest ReleaseRequest fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirm` | string or null | no | The number itself, typed out, to confirm. | ### Requested Requested fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string (export, deletion) | yes | | | `message` | string | yes | | | `requested` | boolean | yes | | ### Revoked Revoked fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `revoked` | integer | yes | | ### SandboxFixture SandboxFixture fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `destination` | string or null | yes | | | `expires_at` | string | yes | | | `id` | string | yes | | | `scenario` | string | yes | | ### SandboxInboundCreate SandboxInboundCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `from` | string or null | no | | | `pages` | integer | no | | | `partial_content` | boolean | no | Rehearse a cut-off transmission. | | `to` | string | yes | | ### SandboxReset SandboxReset fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `deleted` | object | yes | | | `reset` | boolean | yes | | | `wallet_available_micro` | integer | yes | | ### SandboxScenarioCreate SandboxScenarioCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `destination` | string or null | no | Apply to one destination; omit for every send. | | `scenario` | string | yes | | ### SandboxScenarios SandboxScenarios fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `destinations` | array of object | yes | | | `fixtures` | array of object SandboxFixture | yes | | | `scenarios` | array of object | yes | | ### SchemaCreate SchemaCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `description` | string or null | no | | | `name` | string | yes | | | `schema` | object | yes | | ### SenderVerification SenderVerification fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `from` | string | yes | | | `id` | string | yes | | | `method` | string | yes | | | `number_id` | string or null | yes | | | `status` | string | yes | | | `verified_at` | string or null | yes | | ### SenderVerificationCreate SenderVerificationCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `from` | string | yes | A number your organization owns, E.164. | ### Sent Sent fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `message` | string | yes | | | `sent` | boolean | yes | | ### ServiceStatus ServiceStatus fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `database` | boolean | yes | | | `environment` | string (live, test) | yes | | | `provider` | string | yes | | | `status` | string (ok, degraded) | yes | | | `status_page` | string or null | no | | | `version` | string | yes | | | `worker_beat_age_s` | integer or null | no | | ### SessionInfo SessionInfo fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `auth_method` | string | yes | | | `authenticated_at` | string | yes | | | `expires_at` | string | yes | | | `fresh` | boolean | yes | | | `fresh_until` | string | yes | | | `id` | string | yes | | ### SessionList SessionList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object SessionOut | yes | | ### SessionOut SessionOut fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `auth_method` | string | yes | | | `authenticated_at` | string | yes | | | `created_at` | string | yes | | | `current` | boolean | yes | | | `expires_at` | string | yes | | | `id` | string | yes | | | `ip` | string or null | yes | | | `last_seen_at` | string | yes | | | `user_agent` | string or null | yes | | ### SignedUrl SignedUrl fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | yes | | | `expires_at` | string | yes | | | `url` | string | yes | | ### Subscription Subscription fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `cancel_at_period_end` | boolean | yes | | | `canceled_at` | string or null | yes | | | `created_at` | string | yes | | | `current_period_end` | string or null | yes | | | `entitled` | boolean | yes | | | `id` | string | yes | | | `product` | string | yes | | | `status` | string | yes | | | `stripe_price_id` | string or null | yes | | | `stripe_subscription_id` | string | yes | | | `updated_at` | string | yes | | ### SubscriptionCheckout SubscriptionCheckout fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `product` | string | yes | | | `stripe_session_id` | string | yes | | | `url` | string | yes | | ### SubscriptionList SubscriptionList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Subscription | yes | | | `next_cursor` | string or null | no | | | `workspace` | boolean | yes | | ### SummaryCreate SummaryCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `document_id` | string or null | no | | | `inbox_item_id` | string or null | no | | | `max_pages` | integer or null | no | | | `strict` | boolean | no | Refuse with page_limit_exceeded when the document has more pages than would be read, instead of reading the first ones and warning. | | `strong_model_if_below` | number | no | | ### SwitchOrganization SwitchOrganization fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `org_id` | string | yes | | ### TopupCreate TopupCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_usd` | integer | yes | Whole US dollars, 10 to 1000 per session. | ### TopupSession TopupSession fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `auto_topup_id` | string or null | yes | | | `completed_at` | string or null | yes | | | `created_at` | string | yes | | | `currency` | string | yes | | | `environment` | string (live, test) | yes | | | `error` | string or null | yes | | | `expires_at` | string or null | yes | | | `id` | string | yes | | | `kind` | string (manual, auto, setup) | yes | | | `status` | string (open, completed, expired, canceled, failed) | yes | | | `stripe_payment_intent_id` | string or null | yes | | | `stripe_session_id` | string or null | yes | | | `url` | string or null | no | The hosted Checkout page while the session is open. | ### TopupSessionPage TopupSessionPage fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object TopupSession | yes | | | `next_cursor` | string or null | no | | ### UploadComplete UploadComplete fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `sha256` | string or null | no | Optional: fail if the bytes differ. | ### UploadCreate UploadCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | no | application/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. | | `filename` | string | yes | | | `size_bytes` | integer | yes | | ### UploadSession UploadSession fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `completed_at` | string or null | yes | | | `content_type` | string | yes | | | `created_at` | string | yes | | | `declared_size` | integer | yes | | | `expires_at` | string | yes | | | `filename` | string | yes | | | `id` | string | yes | | | `received_size` | integer | yes | | | `sha256` | string or null | yes | | | `status` | string | yes | | | `upload_method` | string or null | no | | | `upload_url` | string or null | no | | | `upload_url_expires_at` | string or null | no | | ### UsageExport UsageExport fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `content_type` | string | yes | | | `created_at` | string | yes | | | `environment` | string (live, test) | yes | | | `expires_at` | string | yes | | | `id` | string | yes | | | `params` | object | yes | | | `row_count` | integer | yes | | | `status` | string (ready, expired) | yes | | | `url` | string or null | yes | | ### UsageExportCreate UsageExportCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `from` | string or null | no | ISO date; default 30 days ago. | | `to` | string or null | no | ISO date; default today. | ### UsageItem UsageItem fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | yes | | | `day` | string or null | no | | | `destination` | string or null | no | | | `direction` | string or null | no | | | `jobs` | integer | yes | | | `pages` | integer | yes | | | `project_id` | string or null | no | | | `route` | string or null | no | | ### UsageReport UsageReport fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `currency` | string | yes | | | `environment` | string (live, test) | yes | | | `from` | string | yes | | | `group_by` | array of string | yes | | | `items` | array of object UsageItem | yes | | | `to` | string | yes | | | `totals` | object | yes | | ### UserOut UserOut fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `email` | string | yes | | | `email_verified` | boolean | yes | | | `id` | string | yes | | | `last_login_at` | string or null | yes | | | `name` | string or null | yes | | ### ValidationError ValidationError fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `ctx` | object | no | | | `input` | any | no | | | `loc` | array of string or integer | yes | | | `msg` | string | yes | | | `type` | string | yes | | ### Webhook Webhook fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `description` | string or null | yes | | | `event_types` | array of string | yes | | | `id` | string | yes | | | `previous_secret_expires_at` | string or null | yes | | | `status` | string | yes | | | `updated_at` | string | yes | | | `url` | string | yes | | ### WebhookCreate WebhookCreate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `description` | string or null | no | | | `event_types` | array of string or null | no | Empty or omitted: every event type. | | `url` | string | yes | | ### WebhookList WebhookList fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of object Webhook | yes | | ### WebhookUpdate WebhookUpdate fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `description` | string or null | no | | | `event_types` | array of string or null | no | | | `status` | string (active, paused) or null | no | | | `url` | string or null | no | | ### WebhookWithSecret WebhookWithSecret fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `created_at` | string | yes | | | `description` | string or null | yes | | | `event_types` | array of string | yes | | | `id` | string | yes | | | `previous_secret_expires_at` | string or null | yes | | | `secret` | string | yes | whsec_... Shown once. Verify webhook-signature with it. | | `status` | string | yes | | | `updated_at` | string | yes | | | `url` | string | yes | | ### faxforagents__api__routes__inbox__InboxItem faxforagents__api__routes__inbox__InboxItem fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | no | | | `document_id` | string or null | yes | | | `duration_secs` | integer or null | no | | | `environment` | string (live, test) | no | | | `expires_at` | string or null | no | | | `from` | string or null | no | | | `id` | string | yes | | | `media_status` | string | no | | | `note` | string or null | no | | | `number_id` | string or null | no | | | `pages` | integer | yes | | | `partial_content` | boolean | no | | | `project_id` | string or null | no | | | `provider` | string | no | | | `read_at` | string or null | no | | | `received_at` | string | yes | | | `status` | string | yes | | | `to` | string | yes | | ### faxforagents__api__schemas__InboxItem faxforagents__api__schemas__InboxItem fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount_micro` | integer | no | | | `document_id` | string or null | yes | | | `from` | string or null | no | | | `id` | string | yes | | | `note` | string or null | no | | | `pages` | integer | yes | | | `received_at` | string | yes | | | `status` | string | yes | | | `to` | string | yes | | ### faxforagents__api__schemas__Me faxforagents__api__schemas__Me fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `actor` | string | yes | | | `environment` | string (live, test) | yes | | | `key_id` | string | yes | | | `key_prefix` | string | yes | | | `org_id` | string | yes | | | `project_id` | string | yes | | | `scopes` | array of string | yes | | ### faxforagents__auth__schemas__Me faxforagents__auth__schemas__Me fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `csrf_token` | string | yes | | | `organization` | object OrganizationSummary or null | yes | | | `organizations` | array of object OrganizationSummary | yes | | | `passkeys` | integer | yes | | | `providers` | object Providers | yes | | | `session` | object SessionInfo | yes | | | `user` | object UserOut | yes | | --- # Security > The controls in place, with the mechanism behind each. Signed webhooks, idempotency keys, private documents, bound approvals, EU processing, subprocessors. Page: https://faxforagents.com/security/ Last changed: 2026-10-28 Updated Oct 28, 2026 This page lists the controls that are in place today, with the mechanism behind each one. It does not list certifications, because there are none yet, and it does not claim HIPAA, because the service is not one. ## Controls What is enforced and how, as of 2026-10-28 | Control | How it works | | --- | --- | | Signed webhooks | Every delivery carries `webhook-id` , `webhook-timestamp` and `webhook-signature` , an HMAC-SHA256 over `id.timestamp.body` . The receiver rejects a timestamp older than 300 seconds. A rotated secret keeps signing for 24 hours. | | Idempotency | Every mutating call accepts an `Idempotency-Key` . A repeat returns the same resource with `Idempotent-Replayed: true` . A different body under the same key is refused. Provider and payment events are deduplicated by a row written in the same transaction as their effect. | | Private documents | No document has a public URL. The only way to fetch bytes is a signed URL minted for the owner, valid ten minutes. Documents are deleted after 30 days by default, inbox items after 90, and the owner can delete earlier. | | Approval binding | An approval covers one document hash, one destination, one sender, one amount and an expiry. A change to any of them invalidates it. A delegated policy is evaluated on the server from the quote and the document row, never from prompt text. | | Untrusted inbound content | Text from a received fax is listed under `untrusted` on every MCP result and rendered as plain text. The policy engine and every tool decision never read it. Ids passed as tool arguments are format-checked before any request leaves the tool. | | Agent keys cannot widen themselves | An agent key is never issued the scopes that decide approvals, write policies, mint keys, change budgets or manage webhooks. Routes that do those things refuse any key with an agent id. Disabling an agent revokes its keys in the same transaction. | | Tenant isolation | Every table and every query carries the organization id. A foreign id answers 404. A sweep in CI walks every id route with a second organization and fails the build when a route has no fixture. | | Audit trail | Every mutation writes an append-only audit row in the same transaction. The actor is a user, an API key prefix, an agent or a system job. Never a document body. The ledger and the audit table refuse updates and deletes at the database. | | Access-gated admin | The admin host sits behind Cloudflare Access. The API verifies the Access assertion on every admin call, RS256 against the team's keys, and checks the email against an allow list. Headless access uses a service token with its own identity in the audit log. | | Secrets | Production configuration lives on the host as environment, never in the repository or CI. API keys are stored as a peppered HMAC and shown once. Webhook secrets are encrypted at rest. Logs run through a redaction processor that is unit-tested. | | Mail authentication | SPF with `-all` , DKIM for both senders, DMARC, MTA-STS, TLS-RPT, DNSSEC and CAA on faxforagents.com. Mail from us carries no image and no document. | | Processing region | Nuremberg, Germany (EU). Backups are encrypted and stored in the EU. No residency promise is made beyond that statement. | ## What delivered means A job is written as delivered only when the provider's report is stored on the attempt. The receipt names the provider reference, the document hash, the pages and the time. A job without a report stays active or ends unknown, and an unknown job is never retransmitted on its own. The receipt is a record, not legal proof that a person read the document. ## Subprocessors Subprocessors as of 2026-10-28 | Who | What for | What they see | | --- | --- | --- | | Hetzner Online GmbH | hosting, Nuremberg, Germany | every service, the database and document storage | | Cloudflare, Inc. | DNS, tunnel, edge, Access for the admin host | every request to a faxforagents.com host | | Telnyx LLC | fax transmission and numbers | the documents sent and received, destination and sender numbers | | Stripe, Inc. | card payments, invoices, the customer portal | billing email, card details on Stripe only, never on our servers | | Resend | transactional mail | the recipient address and the mail body, which never carries a document | | Google Workspace | the hello@ mailbox | mail you send to us | | Backblaze, Inc. | encrypted backups | database dumps, in the EU region | ## Not in place No SOC 2 report, no ISO 27001 certificate, no HIPAA business associate agreement, no penetration test report yet. OAuth 2.1 for the MCP server is planned and not live, so an API key is the only credential. When any of these changes, this page changes with it and the changelog records the date. ## Reporting a problem Write to [hello@faxforagents.com](mailto:hello@faxforagents.com). The same address is in [security.txt](https://faxforagents.com/.well-known/security.txt). We answer within two working days and we do not run a bounty. --- # About > Fax for Agents is made by JSB Holdings LLC, a Delaware company formed in 2023. What the product is, who runs it and how to reach it. Page: https://faxforagents.com/about/ Last changed: 2026-10-30 Updated Oct 30, 2026 Fax for Agents is a fax API, a hosted MCP server and SDKs built so AI agents can send and receive faxes. It is made by JSB Holdings LLC. ## The company JSB Holdings LLC is a limited liability company formed in Delaware on 2023-05-08, file number 7447451. Its postal address is 8 The Green, Dover, DE 19901, US. Fax for Agents is its trading name for this product. [Synapse](https://synapsereality.io/), a software and AI development company, built the product for JSB Holdings LLC. The service runs on one server in Nuremberg, Germany (EU). The team is small and does not publish names or photos. The mark on this site is the logo, and it is the only image we use of ourselves. The OpenAPI document, the SDK sources, the MCP server and runnable examples are public at [github.com/faxforagents](https://github.com/faxforagents). ## Why fax Fax still carries contracts, orders, prescriptions and filings in places where email is not accepted. Software that acts on its own needs a way to send one that a person can bound and later check. That is the whole product: a quote before a send, an authorization the server can name, and a receipt with the provider's evidence. ## What we promise and what we do not We promise the receipt is true to what the provider reported. Money moves only with a quote and an authorization. Every price on this site carries a date. We do not promise delivery, we do not sign HIPAA agreements, and we do not claim a country we have not faxed. The [security page](https://faxforagents.com/security/) lists the controls and the [changelog](https://faxforagents.com/changelog/) lists what shipped when. ## Contact Write to [hello@faxforagents.com](mailto:hello@faxforagents.com). There is no phone line and no form. The [contact page](https://faxforagents.com/contact/) says what to include. --- # Changelog > Every production release, dated. The API, the MCP server and the site, from v0.1.0 on 2026-10-28 onward. Page: https://faxforagents.com/changelog/ Last changed: 2026-10-28 Every production release is a `v*` tag built and deployed through CI. The list is read from the repository's tags, newest first. A release that changes a price, a limit or a control is also reflected on the page that states it. ## `v1.3.3` 2026-10-31 - The sign-in page has two columns on wide screens. The form keeps the mail link, passkey, GitHub and Google options. A sample job panel shows the three steps prepare, quote and send, priced for a three-page fax. - The GitHub and Google buttons carry their real marks. ## `v1.3.2` 2026-10-31 - The MCP server keeps 15 tools with two swaps. get_document replaces preview_fax and returns the document by id with its warnings and preview URLs. get_review_status is new: the state of one approval or help request, with the approval id once a person approved it. - evaluate_fax_policy is gone. quote_fax now carries the policy verdict for the quote: allowed or denied, the first failing rule and whether a person must approve it. The listing tools and get_fax_capabilities say what sets them apart. - A paid wallet top-up now issues a Stripe invoice, listed on the Billing page. ## `v1.3.1` 2026-10-31 - The footer names Synapse Research Ltd as the builder and links its site. - The SDK pages show the published npm and PyPI packages with install lines. The status page links the hosted status page. The fax API hub defines the term and the legal pages carry the brand in their titles. ## `v1.3.0` 2026-10-30 - The MCP server has 15 tools. get_guide replaces guide. get_fax_status carries the carrier's delivery evidence, so get_delivery_evidence is gone. request_human_review replaces request_fax_approval and request_human_help. With a quote id it asks a person to approve that send. Without one it asks a person for help. - The manual is served as MCP resources, the whole guide and one per section. Two prompts, send_a_fax and handle_received_fax, give the tool order. The old tool names are gone from the server. ## `v1.2.5` 2026-10-30 - The about page names Synapse, the company that built the product, and links its site. ## `v1.2.4` 2026-10-30 - Microsoft Clarity loads after consent. The content security policy had blocked its script since v1.2.0. Clarity saw no session while Google Analytics and the Cloudflare beacon recorded every page. ## `v1.2.3` 2026-10-28 - The second Codex review, nine fixes. Production refuses a live send until a carrier is enabled, so a funded wallet is never charged for a simulated delivery. A stored secret is never replayed to a weaker caller. Image uploads convert one frame at a time. - The hosted MCP server renders HTML like the API. The readiness probe and the status page watch the renderer. - A Stripe customer is created once per organization, even when the first Checkout is refused. ## `v1.2.2` 2026-10-28 - Live top-ups work. Stripe had refused every Checkout session since the live keys went in. The session now carries the customer update Stripe requires for tax id collection. - A rejected Stripe request logs a warning instead of an info line. ## `v1.2.1` 2026-10-28 - The design pass. Phones get one Menu button. Docs start on the first screen. The international rate table has a destination filter. Dashboard rows fit their cards and label their values. - Every page paints earlier. One mono font weight, inline stylesheets, and a rate filter that reads its rows on the first keystroke. - Stripe live objects, the status page at status.faxforagents.com and the Search Console property exist. The Telnyx application waits for the account upgrade. ## `v1.2.0` 2026-10-28 - Passkeys that explain themselves and sign in through the browser autofill on phones. - The search and answer engine audit: the GitHub organization on every page, dated pages, question headings, IndexNow repaired. Microsoft Clarity and Google Analytics behind the consent gate. Outbound mail from the verified domain. ## `v1.1.0` 2026-10-28 - HTML rendering in its own browser container, with scripts off and no network. PNG, JPG and TIFF uploads, converted to grayscale letter pages. - The web analytics beacon, a Discord alert channel, and the deferred review rows. ## `v1.0.1` 2026-10-28 - /healthz reports the installed version. ## `v1.0.0` 2026-10-28 - The ten fixes from the Codex second-opinion review. Version 1.0.0, the first release called 1.0. ## `v0.1.15` 2026-10-28 - Every M14 review wave. Redis as its own user, an error trap in the deploy script, old image tags removed after a healthy deploy, and the launch checklist. ## `v0.1.14` 2026-10-28 - Failed its deploy: the recreated Redis container could not start and the service was restored by hand within the hour. Fixed in v0.1.15. ## `v0.1.13` 2026-10-28 - The approvals, policies, agents, numbers, inbox, billing and budgets pages, and the admin console. - The LLM application audit fix wave: the OCR engine runs in its own container, off until it is switched on. ## `v0.1.12` 2026-10-28 - Migration 0006: thirteen audit indexes and the least-privilege application role. - The worker takes its leader lock in autocommit and stops during migrations. Faster CI with cached browsers and three test workers. ## `v0.1.11` 2026-10-28 - Failed its deploy: migration 0006 blocked behind the worker session and the stack stayed on v0.1.10. Fixed in v0.1.12. ## `v0.1.10` 2026-10-28 - faxforagents.com with the full site, the docs and the machine documents (llms.txt, facts.json, openapi.json). ## `v0.1.9` 2026-10-28 - OCR and structured extraction routes and the extract_fax_data tool, with the live engine off until it is switched on. - The MCP server answers on /mcp and /mcp/. ## `v0.1.8` 2026-10-28 - The Telnyx adapter: live sends, inbound faxes, numbers, the rate card with 213 international routes. - The admin API on admin.faxforagents.com behind Cloudflare Access. ## `v0.1.7` 2026-10-28 - The Telnyx adapter, inbound faxes and numbers merged behind the sandbox provider. ## `v0.1.6` 2026-10-28 - The admin API proxied on the admin host behind Access. ## `v0.1.5` 2026-10-28 - The customer area: magic link, passkeys, GitHub and Google sign-in, organizations and members, the dashboard pages for faxes, documents, inbox, numbers, keys, webhooks and billing. - The leak scan allows the browser mock's made-up secrets. ## `v0.1.4` 2026-10-28 - Admin: operator routes behind Cloudflare Access with a service token for headless reads, stuck-job reconcile, route pause, billing adjustments under dual control, audit search. - Status page monitors defined, synced when the token exists. ## `v0.1.3` 2026-10-28 - Approvals bound to a quote, delegated policies evaluated on the server, agent identities with bounded keys and budgets. - MCP tools request_fax_approval, evaluate_fax_policy, get_delivery_evidence, list_inbox, read_inbound_fax, request_human_help. ## `v0.1.2` 2026-10-28 - Mail: Resend transport, the receipt kit in light and dark, sign-in, receipt, failed, partial, unknown, inbound, low balance, invite and API key mails. - The wallet: Stripe top-ups, auto top-up with a daily cap, budgets, usage rollups, invoices, the Agent workspace subscription. ## `v0.1.1` 2026-10-28 - The MCP server runs over HTTP in the stack at mcp.faxforagents.com. - A missing pager is a warning, not a failed start. ## `v0.1.0` 2026-10-28 - First production release. The REST API, the sandbox provider, documents from PDF, text and Markdown, and quotes. The double-entry ledger, signed webhooks, the hosted MCP server and the ffa CLI. - Deploys only from a tag through CI with a health gate and automatic rollback. Telnyx as the live carrier, inbound numbers and OCR are in progress and will appear here when their release is tagged. Nothing on this site describes them as live until then. --- # Status > The hosted status page at status.faxforagents.com, the health endpoints, and what is monitored on the API, the MCP server and document creation. Page: https://faxforagents.com/status/ Last changed: 2026-10-30 Updated Oct 30, 2026 The public status page is at [status.faxforagents.com](https://status.faxforagents.com/), hosted by Better Stack, so it stays up when we are down. The health endpoints answer as well: [`api.faxforagents.com/healthz`](https://api.faxforagents.com/healthz) and [`mcp.faxforagents.com/healthz`](https://mcp.faxforagents.com/healthz) answer 200 when the service is up. ## What is monitored - The REST API: `GET /healthz` on api.faxforagents.com every minute. - The MCP server: `initialize` on mcp.faxforagents.com every minute. - Document creation: a synthetic text document through `POST /v1/documents` with a test key, so the credential path, the database and the renderer are exercised. - This site and the customer area. A true send synthetic, which would run a document, a quote and a send on the sandbox in sequence, is planned and not live. We do not publish an uptime figure, because the monitors have not run long enough to state one with a date and a method. ## Inside the API `GET /v1/service-status` answers with your key: whether the database answers, how old the worker's last heartbeat is, the version and the provider in use. It is customer-safe and never carries another organization's data. --- # Contact > Write to hello@faxforagents.com for support, billing and security questions. No form, no phone line. The postal address of JSB Holdings LLC is on this page. Page: https://faxforagents.com/contact/ Last changed: 2026-10-28 Updated Oct 28, 2026 One address for everything: hello@faxforagents.com. Copy it, or [open it in your mail client](mailto:hello@faxforagents.com). No form, no phone line, no chat widget. ## What to include - For a job: the job id (`fax_...`) and the `X-Request-Id` or `correlation_id` from the error, if there was one. Never the document itself. - For billing: the organization name and the payment reference from the receipt. - For a number outside the launch countries: the country and what the number is for. - For a security report: what you found and how to reproduce it. The same address is in [security.txt](https://faxforagents.com/.well-known/security.txt). We answer within two working days. Replies come from the same address, signed by a person. ## Post JSB Holdings LLC, 8 The Green, Dover, DE 19901, US. --- # Terms of service > The terms under which JSB Holdings LLC provides Fax for Agents. The account, the prepaid wallet, acceptable use, and what we do and do not promise. Page: https://faxforagents.com/terms/ Last changed: 2026-10-28 Effective 2026-10-28. Reviewed by: pending legal review. Drafted by JSB Holdings LLC and marked as such until a lawyer has read it. These terms govern the use of Fax for Agents, provided by JSB Holdings LLC, 8 The Green, Dover, DE 19901, United States. By creating an account or using an API key you agree to them. ## 1. The service Fax for Agents is an API, a hosted MCP server, SDKs and a web dashboard for sending and receiving faxes. Transmission goes through a telephone carrier we contract with. We transmit what you send to the number you name. We do not read, edit or vouch for the content. ## 2. Your account An organization is created by a person who signs in with a magic link, a passkey, GitHub or Google. That person is its owner and is responsible for every key, agent and member under it. Keys are shown once. Keep them secret. Anything sent with your key is sent by you. ## 3. Money The service is prepaid. You top up a wallet by card, $10 at minimum. Each page is charged at the price on the pricing page on the day of the send. The quote names the rate table version. A send holds its quoted amount and captures only what the carrier confirmed. Failed sends are not charged. A partial delivery charges the confirmed pages. An unknown outcome holds the amount until we settle it after review. The full rule is on the refunds page and forms part of these terms. Prepaid balance is not interest-bearing. Unspent balance is refunded on request, by the method you paid with, less any amount owed. The optional Agent workspace is a monthly subscription billed in advance through the card processor and cancellable from the dashboard at any time. Cancellation takes effect at the end of the paid month. ## 4. What you may not send The acceptable use policy is part of these terms. In short: no unsolicited faxes, no unlawful content, no protected health information, no sanctioned destinations, no attempt to reach another customer's data. We may suspend an organization that breaks it, with notice where the law allows, and refund unspent balance. ## 5. Delivery We report what the carrier reports. A job marked delivered carries the carrier's evidence. We do not guarantee that a fax will be delivered, that it will be delivered within any time, or that the receiving machine printed it. You are responsible for checking the receipt before relying on a send, and for any deadline a fax is meant to meet. ## 6. Your content and our records Documents stay yours. We keep them 30 days by default and received faxes 90 days, then delete them. You can delete earlier. We keep metadata and audit rows after that, because a receipt must still resolve. We process data on servers in the European Union and use the subprocessors listed on the security page. ## 7. Agents and automation You may let software act under your keys. You are responsible for its actions. The approval and policy controls are offered to help you bound it, and a send that passes them is still your send. ## 8. Availability and changes We aim for the service to be available and we publish a status page, but we give no uptime warranty. We may change the service, the prices and these terms. A price change is dated on the pricing page and never applies to a quote already issued. A change to these terms is announced by mail to the organization owner at least 14 days before it takes effect. The law can require sooner. ## 9. Liability To the extent the law allows, our total liability for any claim is the amount you paid us in the 12 months before it. We are not liable for indirect loss, lost profit or loss caused by a fax that was not delivered or was delivered late. Nothing here limits liability that cannot be limited by law. ## 10. Termination You can close your organization at any time from the dashboard or by writing to hello@faxforagents.com. We refund unspent balance and delete documents on the retention schedule. We can close an organization for a breach of these terms or for non-payment, with notice where the law allows. ## 11. Law These terms are governed by the law of the State of Delaware, United States, and disputes go to its courts. A consumer whose local law gives them their own courts keeps that right. ## 12. Contact JSB Holdings LLC, 8 The Green, Dover, DE 19901, US. hello@faxforagents.com. --- # Privacy policy > What personal data Fax for Agents stores, why, for how long and with which subprocessors. Documents are kept 30 days by default and processed in the EU. Page: https://faxforagents.com/privacy/ Last changed: 2026-10-28 Effective 2026-10-28. Reviewed by: pending legal review. Drafted by JSB Holdings LLC and marked as such until a lawyer has read it. This policy says what personal data Fax for Agents keeps, why, for how long, and who else sees it. The controller is JSB Holdings LLC, 8 The Green, Dover, DE 19901, United States, reachable at hello@faxforagents.com. ## What we keep and why Data, purpose and retention as of 2026-10-28 | Data | Why | How long | | --- | --- | --- | | Your email address and sign-in method | to sign you in and to send account mail | while the account exists | | Organization name, members, roles | to run the account | while the account exists | | Documents you upload or render | to send them | 30 days by default, or until you delete them | | Received faxes | to deliver them to your inbox | 90 days by default, or until you delete them | | Fax numbers, page counts, job states, provider references, timestamps | the receipt, billing and support | kept as records after the document is deleted | | Audit rows: who did what and when, without document content | security and accountability | kept | | Wallet transactions and invoices | accounting | as long as tax law requires | | Request logs with IP address and request id | security, rate limits, debugging | 30 days | | Card details | payments | held by Stripe, never on our servers | ## Where Data is processed and stored on a server in Nuremberg, Germany, in the European Union, with encrypted backups in the EU. We do not promise a residency beyond that statement. Fax transmission itself goes through the carrier's network to the destination number. ## Who else sees it The subprocessors are listed on the [security page](https://faxforagents.com/security/) with what each one sees. Hosting, DNS and edge, the carrier, payments, transactional mail, the support mailbox and backups. We do not sell personal data and we do not use it for advertising. ## Cookies and analytics The dashboard uses one session cookie, which is necessary. This site sets no cookie before you consent. With consent it loads a first-party analytics script with session storage persistence and a cookieless page-view beacon. Visitors in the EU, EEA, UK and Switzerland are asked first, and a failed region lookup counts as asked. The "Cookie settings" control in the footer reopens the choice. ## Your rights You can read, export, correct and delete your data. Documents and received faxes can be deleted from the dashboard or the API at once. For an export or an account deletion, write to hello@faxforagents.com from the owner's address and we answer within 30 days. If you are in the EU or UK you can also complain to your data protection authority. ## Mail from us We send transactional mail: sign-in links, receipts, failures, inbound notices, low balance and payment notices, approval requests, invites and API key notices. Each one says why you got it. The optional ones carry a settings link. We send no newsletter. ## Children The service is for businesses and developers and not for anyone under 18. ## Changes A change to this policy is dated at the top of the page. It is announced by mail to organization owners when it affects what we keep or who sees it. --- # Acceptable use policy > What may not be sent through Fax for Agents: unsolicited faxes, unlawful content, protected health information and sanctioned destinations. Dated 2026-10-28. Page: https://faxforagents.com/acceptable-use/ Last changed: 2026-10-28 Effective 2026-10-28. Reviewed by: pending legal review. Drafted by JSB Holdings LLC and marked as such until a lawyer has read it. What may not be sent or done through Fax for Agents. This policy is part of the terms of service. ## Do not send - Unsolicited advertising. In the United States the Telephone Consumer Protection Act and the Junk Fax Prevention Act forbid unsolicited advertisements by fax, and similar rules exist elsewhere. Send to people who asked for it or with whom you have the relationship the law requires. - Protected health information. Fax for Agents is not a HIPAA service, signs no business associate agreement and must not be used to transmit PHI. - Content that is unlawful where it is sent from or received: threats, fraud, harassment, material that infringes someone's rights. - Faxes to sanctioned or high-risk destinations. These are refused by the service and listed on the coverage page. Screening a recipient against a sanctions list is your responsibility. - Anything that impersonates another sender. The sender number on a job is one of yours or the pool, and the cover page says who sent it. ## Do not do - Try to read, guess or reach another organization's data. Every foreign id answers 404 and the attempt is logged. - Share an API key outside your organization or publish one. - Circumvent a rate limit, a budget, a policy or an approval, or help an agent do so. - Use the sandbox to load-test the service. It is for rehearsing outcomes, not for volume. - Resell the service as your own without an agreement with us. ## What we do about it A send that breaks a rule the server can check is refused before any money is held. For the rest, we may pause a route, revoke a key or suspend an organization, with notice where the law allows. Unspent balance is refunded. We cooperate with lawful requests from authorities and we tell you when we can. ## Reporting If you received a fax through us that you believe breaks this policy, write to hello@faxforagents.com with the sender number and the time. Do not send us the document unless we ask. --- # Refunds and partial delivery > Failed sends are not charged. A partial delivery charges only the pages the provider confirmed. An unknown outcome holds the amount until a review settles it. Page: https://faxforagents.com/refunds/ Last changed: 2026-10-28 Effective 2026-10-28. Reviewed by: pending legal review. Drafted by JSB Holdings LLC and marked as such until a lawyer has read it. What you pay when a fax does not end delivered, and how unspent balance comes back. These rules are enforced by the ledger, not by a support request. ## The rule by outcome What is captured from the hold, by job state | State | Charged | What happened | | --- | --- | --- | | delivered | every page, at the quoted rate | the provider reported every page received and the report is on the attempt | | partial | only the pages the provider confirmed | the call dropped. The rest of the hold is released in the same transaction. Nothing is retransmitted on its own | | failed | nothing | busy or no answer on every bounded attempt, an invalid number, a render failure, or a cancel. The hold is released | | unknown | nothing yet | the provider has not said. The hold stays until a reconcile decides, then the delivered or failed rule applies. We review every unknown job that is still open after 24 hours and release the hold when the provider cannot confirm | A send refused before the provider is called, for an empty wallet, a policy denial, an expired quote or an unsupported destination, never holds anything. ## Received pages Received pages are charged as they arrive on your number. A received fax that fails on the line is not charged. Numbers are charged monthly in advance and the charge stops from the month after release. ## Unspent balance Prepaid balance is yours. Write to hello@faxforagents.com from the owner's address. We refund the unspent part by the method you paid with, within 10 working days, less any amount owed. Promotional credit, if we ever grant any, is not refundable and the grant says so. ## Subscription The Agent workspace is billed monthly in advance. Cancelling stops the next charge. A month already started is not refunded, except where consumer law requires it. ## Disputes If a receipt looks wrong, send the job id. We compare it with the provider's record and correct the ledger with an audited adjustment when we were wrong. A card dispute pauses live sends on the organization until it is resolved, because the wallet behind them is in question.