SingleFax authentication docs
API keys, OAuth for MCP, and delegated Agent Auth.
Authentication guide for AI agents and developers.
How agents authenticate to SingleFax. HTML install: https://singlefax.com/agents
This file is the agent_auth.skill target advertised on RFC 8414 metadata at /.well-known/oauth-authorization-server.
SingleFax supports Auth.md anonymous and service_auth registration with user approval and service-signed identity assertions. Anonymous credentials have no account access before approval. External provider ID-JAGs are not accepted. The older Better Auth host/agent JWT flow is documented separately in Path C.
Auth.md registration and token exchange
Audience: AI agents registering to act for an existing SingleFax account. Owner-issued enrollment is required for every registration method. Public requests without a valid enrollment token are rejected before a registration is created. The OAuth issuer is https://api.singlefax.com/api/auth; the resource is https://singlefax.com/mcp. Registration never sends email, sends a fax, or charges a payment.
Supported registration methods: anonymous and service_auth. Credentials: identity_assertion and Bearer access_token. The account owner signs in on SingleFax, reviews the requested permissions, and enters the six-digit code from their agent. Supported scopes are fax:read, fax:draft, fax:send, inbox:read, credits:read and credits:checkout. Default permissions are fax:read and credits:read; request additional scopes explicitly.
- The owner signs in at https://singlefax.com/dashboard/agent-access, chooses the agent name and allowed permissions, and explicitly creates an enrollment token. This short-lived secret is shown once, expires after ten minutes and can register only one agent. Give it to that agent. API provisioning uses the first-party owner browser session at
POST https://singlefax.com/api/auth/agent/identity/managewith{ "action": "enroll", "name": "My agent", "scopes": ["fax:read", "credits:read"] }. Browser cookies are never shared with the agent. Owners can revoke unused tokens on the same page. - Register using
POST https://api.singlefax.com/api/auth/agent/identitywithContent-Type: application/jsonand{ "type": "service_auth", "enrollment_token": "<owner-issued token>", "login_hint": "<enrolling owner email>" }. This returnsregistration_id, a secretclaim_token, expiry andclaimwithuser_code,verification_uriand a five-second polling interval. No assertion is issued before approval. - Alternatively register with
{ "type": "anonymous", "enrollment_token": "<owner-issued token>" }. Here anonymous describes the pre-claim credential, not open public signup. This returns an immediate signedidentity_assertionand aclaim_token. Exchanging that pre-claim assertion gives an empty-scope token that cannot access any account. Start its approval usingPOST https://api.singlefax.com/api/auth/agent/identity/claimwith{ "claim_token": "<secret>", "email": "<account email>" }; read the returnedclaim_attempt. - Show the code separately and open
verification_urifor the user. The URL contains a secret claim-attempt token; do not include the code in that URL. Only the enrolling verified account can approve. The name is fixed by that owner; requested scopes may be narrowed but cannot exceed the enrollment permissions. The email hint and claim email must match the enrolling owner. The user may decline. Claims expire after ten minutes; a new claim attempt invalidates the old link. No account is auto-created. - Poll
POST https://api.singlefax.com/api/auth/oauth2/tokenwithContent-Type: application/x-www-form-urlencodedandgrant_type=urn:workos:agent-auth:grant-type:claim&claim_token=<secret>&resource=https%3A%2F%2Fsinglefax.com%2Fmcp. Handleauthorization_pending,slow_down(increase the interval by five seconds),expired_tokenandaccess_denied. Approval returns an opaqueaccess_token,token_type,expires_in,scope,identity_assertionandassertion_expires. Pre-claim tokens and assertions are invalidated at approval. - Use
Authorization: Bearer <access_token>on MCP or A2A with the granted scopes. Tokens last at most one hour. To refresh, use the same token endpoint withgrant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>&resource=https%3A%2F%2Fsinglefax.com%2Fmcp. No refresh token is issued. Assertions remain re-exchangeable for up to 30 days unless the registration is revoked. Their EdDSA signatures use the issuer's/jwks;typisoauth-id-jag+jwt, andiss/audboth equal the issuer. - Revoke one token with
POST https://api.singlefax.com/api/auth/oauth2/revoke, form bodytoken=<access_token>&token_type_hint=access_token. This is idempotent and leaves the assertion usable. Revoke the registration withPOST https://api.singlefax.com/api/auth/agent/identity/revoke, JSON{ "identity_assertion": "<identity_assertion>" }; all access tokens and further exchanges stop.
The account owner can review and revoke grants at https://singlefax.com/dashboard/agent-access. Revoking a registration stops every associated access token and assertion refresh.
Claim and approval responses are private and never cacheable. Keep claim tokens, assertions and access tokens secret. Enrollment issuance is limited per owner across API replicas, with at most five live unused tokens. Unenrolled public requests do not consume an owner registration allowance. Missing tokens return enrollment_required; expired, revoked or consumed tokens return invalid_enrollment. A failed registration does not consume the enrollment token. Do not probe registration endpoints during passive readiness scans.
Discovery
Resource server (MCP + API BFF): https://singlefax.com
GET https://singlefax.com/.well-known/oauth-protected-resource
GET https://singlefax.com/.well-known/oauth-protected-resource/mcp
Protected resource metadata (RFC 9728): the root document describes the origin (resource https://singlefax.com); /.well-known/oauth-protected-resource/mcp describes the MCP server (resource https://singlefax.com/mcp, the URL WWW-Authenticate points to). Both list authorization_servers (issuer https://api.singlefax.com/api/auth) and scopes_supported. Request tokens for https://singlefax.com/mcp.
GET https://singlefax.com/.well-known/oauth-authorization-server
GET https://singlefax.com/.well-known/oauth-authorization-server/api/auth
Authorization-server metadata includes issuer, authorization_endpoint, token_endpoint, registration_endpoint, jwks_uri, scopes_supported, and agent_auth (skill points here).
API origin https://api.singlefax.com serves the same well-known documents.
Path A. MCP OAuth (chat apps)
Use this for Cursor, Claude, ChatGPT, Codex, VS Code.
- Add MCP
https://singlefax.com/mcp(Streamable HTTP). - On 401, read
WWW-Authenticateand fetch PRM above. - Dynamic client registration:
POST {issuer}/oauth2/register(PKCE S256,token_endpoint_auth_method=nonefor public clients). - Authorization code + PKCE at
{issuer}/oauth2/authorize. - Token at
{issuer}/oauth2/token. POST /mcpwithAuthorization: Bearer. For a typical document, callcreate_upload, give the useruploadPageUrl, thencreate_fax_draftwithtoanduploadToken.base64is only for tiny files. PUT touploadUrluses the HMAC token in the path, no OAuth header. Send the original Word, Excel, PDF, or image; do not convert to PDF first.
Scopes: openid, profile, email, offline_access, fax:read, fax:draft, fax:send, inbox:read, credits:read, credits:checkout. Request credits:checkout explicitly for AP2 credit purchases; each charge still requires separate passkey approval.
Unauthenticated POST /mcp accepts server/discover only. initialize, tools/list, and tools/call require OAuth.
Path A2. A2A (agent-to-agent)
Agent card: GET https://singlefax.com/.well-known/agent-card.json (A2A 1.0). Endpoint: POST https://singlefax.com/a2a (JSON-RPC: SendMessage, GetTask, CancelTask; 0.3 names message/send, tasks/get, tasks/cancel also work).
- Use the same OAuth server, scopes and tokens as Path A (request resource
https://singlefax.com/mcp). SendAuthorization: Bearer. DPoP-bound tokens are not accepted on/a2a. - Send one data part:
{ "skill": "<skill id from the card>", "arguments": { ... } }. Free text is not interpreted. - Fax and number skills return a Stripe Checkout link (
checkoutUrl). A2A never spends account credits or plan pages. A draft waiting for payment is task stateinput-required. Poll withGetTask.
For AP2 credit purchases, request credits:checkout and negotiate the merchant extension. Signed cart and payment mandates, separate owner passkey approval, Stripe Shared Payment Token redemption and ledger-confirmed receipts are documented at https://singlefax.com/ap2.md.
GetTask with fax:read follows fax delivery for fax drafts and payment/provisioning for number checkouts. A number task completes when the number becomes active. Credit-pack checkout has no order-backed task to poll; check the credit balance after payment.
Path B. API key (backends and CLI)
- Create a key at https://singlefax.com/dashboard/api-keys (
sf_live_…). - Buy prepaid credits.
Authorization: Bearer sf_live_…onhttps://singlefax.com/api/v1/…(orhttps://api.singlefax.com/api/v1/…).- CLI:
npx @singlefax/cli send FILE --to +E164withSINGLEFAX_API_KEY. Pass the original Word, Excel, PDF, or image, do not convert to PDF first.
Language examples (curl, Node, Python, Go, PHP): https://singlefax.com/developers.md
OpenAPI: https://singlefax.com/openapi.json
Path C. Better Auth delegated host/agent JWTs
Delegated capabilities (validate_fax, send_fax, get_fax_status, get_credits, create_credit_checkout) use device approval.
- Read
GET https://api.singlefax.com/.well-known/agent-configurationfor algorithms, endpoints, modes and approval methods. Its Agent Auth issuer ishttps://api.singlefax.com, distinct from the OAuth issuer ending in/api/auth. Use this origin as the JWT audience. Generate separate host and agent signing keys using a supported algorithm; keep private keys private. - Provision the host first. Dynamic host registration is disabled. The account owner must sign in on SingleFax and call
POST https://singlefax.com/api/auth/host/create(API origin equivalent: https://api.singlefax.com/api/auth/host/create) using their first-party browser session. Supply{ "name": "My agent host", "public_key": <host public JWK>, "default_capabilities": [] }. The response'shostIdidentifies the registered host. Empty defaults ensure capabilities require separate approval. Alternatively omitpublic_keyto receive an expiring, one-useenrollmentToken; the host then callsPOST https://api.singlefax.com/api/auth/host/enrollwith{ "token": <enrollment token>, "public_key": <host public JWK>, "name": "My agent host" }. Never share browser cookies or private signing keys with an agent. - Register at
POST https://api.singlefax.com/api/auth/agent/register. SendAuthorization: Bearer <signed host+jwt>and JSON{ "name": "My agent", "mode": "delegated", "capabilities": ["validate_fax", "get_fax_status", "get_credits"], "preferred_method": "device_authorization", "force_approval": true }. The JWT header usestyp: "host+jwt"and a supportedalg(and the signing JWK'skidif present). Claims includeiss: <registered hostId>,aud: "https://api.singlefax.com", a freshjti, currentiat,expwithin 60 seconds, andagent_public_key: <agent public JWK>. Sign with the provisioned host key. Reused JWT identifiers are rejected. - Registration returns
agent_id,host_id, status, capability grants and anapprovalobject when needed. Show its device code and open itsverification_uri_completeon the SingleFax site. The user signs in and approves at the returned verification URL. Registration, provisioning and approval change account state; passive scanners must only read discovery documents. - Poll
GET https://api.singlefax.com/api/auth/agent/status?agent_id=<registered id>using a fresh host JWT (iss: <host_id>). After approval, sign agent JWTs with the agent private key: headertyp: "agent+jwt", supportedalg/kid; claimsiss: <host_id>,sub: <agent_id>,aud: "https://api.singlefax.com", currentiat,expwithin 60 seconds and a freshjti. CallPOST https://api.singlefax.com/api/auth/capability/executewithAuthorization: Bearer <signed agent+jwt>and{ "capability": "get_credits", "arguments": {} }, or another granted capability with its documented arguments. These credentials are signed proofs, not API keys or OAuth access tokens. - For a new device code on a registered agent,
POST https://api.singlefax.com/api/auth/agent/device/codewith{ "agent_id": "<registered id>" }and a signed host JWT. This endpoint cannot register an agent. Revoke throughPOST https://api.singlefax.com/api/auth/agent/revokewith{ "agent_id": "<registered id>" }and the owning host JWT or owner session.send_faxrequires user verification with a passkey; it is not autonomous.
Do not POST ID-JAGs or { "type": "anonymous" } here. Those identity types are not supported.
Machine payments (not OAuth)
Autonomous pay is MPP HTTP 402 on POST /api/v1/machine/faxes with a Stripe Shared Payment Token or Tempo USDC. See https://singlefax.com/mpp.md. Cursor cannot mint an SPT; use Stripe Link CLI against that REST URL, not /mcp. Tempo wallets pay the Tempo challenge on the same URL.
HIPAA
Do not send PHI on MCP, MPP, or the public API. Covered Entities use HIPAA Secure Mode on the website with an electronic BAA.