One-time send by API
Updated
A one-time send is one fax paid by card, with no wallet and no top-up. This page is its API contract. A person always pays. The API answers a link, and the person opens it and pays there by card. Code cannot pay an order, and neither can an AI agent.
This is not the wallet send, which the send a fax page covers. A person who wants a form and no code uses the send a fax page of the site.
The flow
- Create the order. Without a key, from a file or text. Or with a live key, from a quote. The answer carries the price, a status token and a status link.
- A person pays. Give them the status link. For an order that software created, they first accept the acceptable use policy and the terms on that page. The card is authorized there and nothing is charged yet.
- The sender confirms. A mail goes to the address of the order. One click on its link proves the address and releases the fax. An address the organization has already proven skips this step.
- The fax goes out. The card is charged only for the pages the carrier confirms.
- Read the order until
nextsaysdone.
Is it open, and what does it cost?
export FFA=https://api.faxforagents.com/v1
curl -s $FFA/one-time-sends/config The config route takes no credential. It answers enabled, the routes with flat_micro, included_pages and extra_page_micro, then max_document_pages, max_upload_bytes, the accepted formats and the URLs of the policy the sender accepts. Money is in integer micro-units of a US dollar, so 1,000,000 is one dollar.
One fax costs $2.49 for up to 5 document pages to the United States and Canada, then $0.49 for each page after that. One order takes 20 pages at most, which is $9.84. The cover sheet is included and is not counted.
Create an order without a credential
POST /v1/one-time-sends takes no credential. Send multipart/form-data with a file, or application/json with text.
curl -s $FFA/one-time-sends \
-H "Idempotency-Key: lease-2026-11-12" \
-F "email=dana@example.com" \
-F "sender_name=Dana Whitfield" \
-F "to=+14155550142" \
-F "accept_aup=true" \
-F "to_name=Records office" \
-F "subject=Signed lease" \
-F "file=@lease.pdf;type=application/pdf" The same order from text, as JSON:
curl -s $FFA/one-time-sends -H "Content-Type: application/json" \
-d '{"email": "dana@example.com", "sender_name": "Dana Whitfield", "to": "+14155550142", "accept_aup": true, "subject": "Notice", "text": "Please find my notice below.", "text_format": "text"}' | Field | Required | What it is |
|---|---|---|
email | yes, without a key | Where the confirmation link and the receipt go. It is printed on the cover sheet. With a key it may be left out, and the order then uses the address of the organization. |
sender_name | yes | The name printed on the cover sheet, 2 to 120 characters. |
to | yes | The destination fax number in E.164 form. |
accept_aup | yes, without a credential | true: the sender accepts the acceptable use policy and the terms that the config route names. With an API credential the value is not recorded. The person who pays accepts on the order page instead. |
file | one of file or text | Multipart only. One PDF, PNG, JPEG or TIFF file. |
text | one of file or text | The document as plain text or Markdown. |
text_format | no | text or markdown. The default is text. |
to_name, subject, message | no | Lines for the cover sheet. |
delete_after_send | no | true deletes the document and the copy that was sent when the fax ends delivered, partial or failed. |
source_ref | no | A short tag that says where the sender came from. Never a URL. |
The answer is 201 with the order. Read id, status, next, amount_micro, document_pages, status_token and status_url. Nothing is charged and nothing is sent by this call. An account is opened for the email address so the receipt has a place to stay. If nobody pays, the order expires after 48 hours.
Send the header Idempotency-Key. A repeat with the same header value and the same content within 24 hours answers the same order. A file sent again counts as the same content.
Create an order from a quote
Software that already has a document and a quote can turn the quote into an order. This is the path for a wallet that does not cover a send. It needs a live key with the scope fax:send.
curl -s $FFA/one-time-sends/from-quote -H "Authorization: Bearer $FFA_KEY" \
-H "Content-Type: application/json" -H "Idempotency-Key: card-for-quote-1" \
-d '{"quote_id": "quote_...", "sender_name": "Acme Records", "approval_id": "apr_..."}' The body takes quote_id and sender_name, and optionally approval_id, email, delete_after_send, source_ref and accept_aup. The quote supplies the document, the destination and the cover sheet. The price is the one-time price above, not the per page amount on the quote. The quote then belongs to the order and the wallet route no longer sends it.
Who authorizes it
A card payment is money, not consent. An order made with an API credential is a send by software, so it needs exactly what POST /v1/faxes needs for the same quote. That is approval_id, an approved request from the approvals flow, or a delegated policy that allows the send. Whatever the wallet route would answer for that caller and that quote, this route answers too. Without an approval or a policy the answer is 403 approval_required and no order is made.
The policy is asked about what the order will send and cost: the one-time price and the pages with the cover sheet. The order records the approval or the policy version, and the fax job carries it. If the approval is revoked or the policy changes before the fax is released, the order ends refused and the card is not charged. The same rule holds for POST /v1/one-time-sends with a live credential, where only a policy can allow the order. An order from a signed-in session needs neither.
The accept_aup field is for a session. Software cannot accept the policy for a person, so an order it creates has aup_accepted false. The person who pays ticks the box on the order page before the payment opens.
Without email the order uses the address of the organization. An address that a member has signed in with is already proven, so the fax goes out as soon as the person has paid. Any other address gets the confirmation mail first. A test key is refused with invalid_request. An order is always live, and the sandbox is free.
When a wallet send is refused with insufficient_balance, details.one_time_offer is present if this route would take the same quote. It carries amount_micro for that document.
The status token
The create answers status_token. Without a key, send it as the header X-FFA-Order-Token. The query parameter t works too, but the header stays out of request logs.
curl -s $FFA/one-time-sends/$ORDER -H "X-FFA-Order-Token: $TOKEN" The token works for 7 days. It reads its one order and the receipt of that order's fax, opens the checkout and asks for the confirmation mail again. It cannot confirm an order, list orders or read another one. A wrong token answers 404 not_found, exactly like an order that does not exist.
A key or a signed-in session of the order's organization reads the same routes with no token. Its read also carries status_url again while the order is not done, so software that lost the first answer can still hand the link over. The scopes are fax:read to read and fax:send for the checkout and the mail.
The pay link
status_url is the order's page in the dashboard, with the token in it. Give it to the person who pays. The page shows the price, takes them to the card payment and then follows the fax. This is the link an agent hands over.
To send the person straight to the card payment, open the checkout yourself:
curl -s -X POST $FFA/one-time-sends/$ORDER/checkout -H "X-FFA-Order-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"accept_aup": true}' An order whose aup_accepted is false needs the body {"accept_aup": true} on this call, once. It is taken from the status token or a session, and refused from an API credential. That is the person's acceptance, and the order page sends it when they tick the box.
The answer is 201 with checkout_url, expires_at, amount_micro and currency. checkout_url is a payment page hosted by Stripe. A second call answers the same page while it can still be paid, and a new one after it expired. The payment authorizes the card. It does not charge it.
The confirm step
After the payment the order is awaiting_confirmation unless its address was already proven. A mail with a link goes to the address. The person clicks it, and the page behind the link makes these two calls with the token from the link:
# what the link is for, without using it
curl -s $FFA/one-time-sends/by-token/$CONFIRM_TOKEN
# use it: the address is proven and the fax is released
curl -s -X POST $FFA/one-time-sends/$ORDER/confirm -H "Content-Type: application/json" \
-d '{"token": "'$CONFIRM_TOKEN'"}' The link works once and for 48 hours after the payment. The status token cannot stand in for it, because the click is the proof that the sender reads that mailbox. An order nobody confirms in time expires, the authorization is given back and nothing is charged.
# mail the confirmation link again
curl -s -X POST $FFA/one-time-sends/$ORDER/resend-confirmation -H "X-FFA-Order-Token: $TOKEN"
# the receipt of the fax as a one-page PDF, once the fax has ended
curl -s -o receipt.pdf $FFA/one-time-sends/$ORDER/receipt.pdf -H "X-FFA-Order-Token: $TOKEN" The mail can be asked for again a few times per order, with a pause between two mails. resend_allowed_at on the order says when the next one may go, and a call before that answers 429 rate_limited.
States
next is what the caller does now: checkout, confirm, wait or done. status is the finer state.
| status | next | Meaning |
|---|---|---|
draft | checkout | Created and priced. Nobody has paid. |
checkout_open | checkout | A payment page is open. checkout_url carries it. |
awaiting_confirmation | confirm | The card is authorized. The sender has to click the link in the mail. |
authorized | wait | The card is authorized and the address is proven. The fax is being released. |
released | wait | The fax went to the carrier. Its own state is in fax.state. |
capturing | wait | The carrier confirmed pages. The charge is in progress and not confirmed yet. |
canceling | wait | The authorization is being given back and that is not confirmed yet. |
captured | done | The card was charged. captured_micro is the amount. |
released_hold | done | Nothing was charged. The authorization was given back, and failure_code says why. |
expired | done | Nobody paid, or nobody confirmed in time. Nothing was charged. |
refused | done | The order was refused after payment and the authorization was given back. |
settle_failed | done | The card settlement did not complete. An operator looks at it. Do not create a second order. |
Once the order is released, fax is the fax job with its own state and, when the carrier has reported, its evidence. Do not say charged or not charged from the fax state alone. capturing means pages arrived and the charge is not confirmed yet.
What is charged, and when
amount_microis the price of the order and the amount the card is authorized for.captured_microis what the card was charged. It is set only after the payment provider confirmed the charge.- Delivered: the card is charged the price of the order.
- Partial: the card is charged for the document pages the carrier confirmed, by the same price rule. With no confirmed document page nothing is charged.
- Failed: nothing is charged and the authorization is given back.
- No report from the carrier: the authorization is given back 48 hours after the fax went out, and nothing is charged.
The refunds page has the full rules. An organization with a webhook gets one_time_send.authorized, one_time_send.captured and one_time_send.released next to the fax events. The webhooks page covers signatures and retries.
Limits
- Fax numbers in the United States and Canada only. Other countries go through the prepaid wallet.
- One document of up to 20 pages and 10 MB: PDF, PNG, JPEG, TIFF, plain text or Markdown. No HTML and no office formats.
- Live only. No order is created with a test key.
- Daily limits per email address, per destination number and per network address. Over one, the create answers
429 rate_limitedanddetails.capnames it. - Sending only. An order has no fax number and receives nothing.
Error codes
Errors use the envelope of the errors page: error.code, error.message and error.details.
| Code | HTTP | What it means |
|---|---|---|
invalid_request | 422 | A field is missing or wrong, accept_aup is not true, or the credential is a test one |
invalid_destination, unsupported_destination | 422 | The number is not E.164, or it is outside the United States and Canada |
too_many_pages, document_too_large | 422 | The document is over the page limit or the upload limit. Use the prepaid wallet for a longer one |
unsupported_format, encrypted_pdf | 422 | The file is not one of the four formats, or the PDF has a password |
blocked_destination, account_suspended | 403 | The destination or the account is blocked. Not retryable |
invalid_token | 401 | The from-quote route was called without a live key |
approval_required | 403 | An order made with an API credential has no approval and no policy that allows it. details.policy carries the verdict. Paying by card does not replace either |
approval_not_granted, approval_expired, approval_mismatch, approval_consumed | 409 | The approval_id does not cover this quote as it is now. Quote and ask again |
scope_missing | 403 | The scope fax:send is missing, or fax:read for a read |
not_found | 404 | No such order for this caller. A wrong status token and an order of another organization answer the same |
quote_consumed, quote_expired, document_hash_mismatch | 409 | The quote was used, expired, or its document changed. Quote again |
already_paid, order_closed | 409 | The checkout was asked for an order that is paid or has ended |
link_invalid | 404 | The confirmation token is not one of this order |
link_used, link_expired, link_other_session | 409 | The confirmation link was used, ran out, or was opened in a browser signed in as someone else |
conflict | 409 | Nothing is waiting for a confirmation mail, or the receipt was asked before the fax ended |
idempotency_key_conflict | 409 | The same Idempotency-Key was sent with a different body |
rate_limited | 429 | A daily limit or the request limit was reached. details.cap names the limit and Retry-After says when to try again |
billing_not_configured, billing_unavailable | 503 | The payment provider cannot be reached. Try the checkout again later |
one_time_unavailable | 503 | One-time sends are closed on this server. The prepaid wallet still works |
From an MCP client
The hosted MCP server has two tools for this path. request_card_payment takes a quote id, the name for the cover sheet and, when the send needs one, the approval_id. It answers the order with status_url. The agent gives that link to a person, who accepts the policy and pays there. get_card_payment_status reads the order back with the fax and its evidence, and returns the link again while the person still has to act. Neither tool can accept, pay or confirm.
The authorization is the same as for send_approved_fax. An agent whose policy denies a number gets approval_required from both tools.
When send_approved_fax is refused because the wallet is short, its message names request_card_payment if the same quote can be paid by card.