Policies API reference
Updated
9 operations of the Fax for Agents API, from the OpenAPI document version 1.20.0, snapshot of 2026-11-13. The base URL is https://api.faxforagents.com/v1. This is one group of the full reference.
- Changing a policy creates a new immutable version, and earlier versions stay readable.
- A policy evaluation is a dry run that never executes a send.
- The purpose of a policy is shown to people and is never evaluated.
Operations
GET /v1/organizations/current/auto-sendRead the auto-send setting for connected apps in this environmentPUT /v1/organizations/current/auto-sendLet connected apps send without asking, inside two limits (owner)GET /v1/policiesList the organization's policiesPOST /v1/policiesCreate a delegated policy (version 1)GET /v1/policies/effectiveRead the rules bound to the caller, an API key, an agent or a connected appGET /v1/policies/{policy_id}Read a policy (its current version)PATCH /v1/policies/{policy_id}Change a policy: a new immutable versionGET /v1/policies/{policy_id}/versionsEvery version, oldest firstPOST /v1/policy-evaluationsDry run: would the calling key's policies allow this send? Never executes
What does a policy decide?
Whether a send may go out without a person. A live key that has no policy needs an approval for each fax. A delegated policy lists limits, and a send inside every one of them is allowed at once. The server evaluates the limits from the quote and the document. Nothing the agent writes is an input.
POST /v1/policies creates a policy at version 1 with a name, a purpose and rules. The purpose is for the people who read the list later. The rules are what counts. The policies guide has each rule field and the order they are checked in.
Why are policy versions immutable?
So that a job can say which rules allowed it. PATCH /v1/policies/{policy_id} does not edit the rules in place. It writes a new version, and the policy points at it in current_version_id. GET /v1/policies/{policy_id}/versions returns every version, oldest first.
A send that a policy allowed records the version. Months later the record still shows the limits as they were on that day, even if the policy has changed ten times since.
enabled turns a policy off without deleting it. A disabled policy allows nothing.
How do I test a send against the rules?
POST /v1/policy-evaluations is a dry run. It takes a quote_id, or a plan made of a document_id, a to and a from. The answer has the decision, the policy and version that decided, the rule that failed first and every denial in denials. executed is false. The call never sends.
The verdict is for the calling key. An agent can run it before it asks a person, and skip the request when the answer is allow.
What may this key do on its own?
GET /v1/policies/effective answers that. Without parameters it returns the rules bound to the caller. With key_id, agent_id or connection_id an administrator reads them for another key, an agent or a connected app. approval_required_without_policy says whether a person is needed for whatever the policies do not cover, and rules_order names the order of evaluation.
What is the auto-send setting?
A managed policy for connected apps. PUT /v1/organizations/current/auto-send lets the connected apps of the organization send without asking, inside two limits. One is an amount per fax in max_amount_micro. The other is a number of faxes per UTC day in daily_max_jobs. Connected apps are OAuth connections, such as ChatGPT or Claude, and API keys flagged connected_app.
The description of the operation sets out the rest. The environment comes from X-FFA-Environment. The first enable creates the managed policy. Every later save is a new policy version with its audit row. A hosted integration the server marks requires_human_approval still needs a person. A browser session needs a sign-in from the last 15 minutes.
GET /v1/organizations/current/auto-send reads the setting with the policy id and the version behind it.
Which scopes guard policies?
Reads and the dry run need policy:read, which the default API key carries. Creating and changing a policy need policy:admin. The auto-send write needs org:admin, the owner's scope. An agent key is never issued policy:admin or org:admin, so an agent can read its limits and test against them, and cannot move them.
Each operation in detail
GET /v1/organizations/current/auto-send
Read the auto-send setting for connected apps in this environment
Credential: an API key or a signed-in session. Scope: policy:read.
Responses
| Status | Meaning |
|---|---|
200 | Successful Response |
401 | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
403 | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
429 | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
daily_max_jobs | integer | yes | Faxes per UTC day, at most, across every connected app. |
enabled | boolean | yes | |
environment | string (live, test) | yes | |
max_amount_micro | integer | yes | Amount per fax, at most, in micro-units of USD. |
policy_id | string or null | yes | The managed policy; null before the first enable. |
version | integer or null | yes |
Example
curl https://api.faxforagents.com/v1/organizations/current/auto-send \
-H "Authorization: Bearer ffa_test_..."
PUT /v1/organizations/current/auto-send
Let connected apps send without asking, inside two limits (owner)
The environment comes from X-FFA-Environment. When enabled, every connected app of the organization (OAuth connections such as ChatGPT or Claude, and API keys flagged connected_app) sends in that environment with no approval per fax, inside the amount per fax and the faxes per day. A hosted integration the server marks requires_human_approval still needs a person. The first enable creates a managed policy; every later save is a new policy version with its audit row. A browser session needs a sign-in from the last 15 minutes.
Credential: an API key or a signed-in session. Scope: org:admin.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
Idempotency-Key | header | string | no | Replay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay. |
Request body application/json, required
| Field | Type | Required | Description |
|---|---|---|---|
daily_max_jobs | integer | no | Faxes per UTC day. |
enabled | boolean | yes | |
max_amount_micro | integer | no | Amount per fax, at most, in micro-units of USD (5000000 = $5.00). |
Responses
| Status | Meaning |
|---|---|
200 | Successful Response |
401 | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
403 | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
409 | The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state. |
422 | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
429 | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
daily_max_jobs | integer | yes | Faxes per UTC day, at most, across every connected app. |
enabled | boolean | yes | |
environment | string (live, test) | yes | |
max_amount_micro | integer | yes | Amount per fax, at most, in micro-units of USD. |
policy_id | string or null | yes | The managed policy; null before the first enable. |
version | integer or null | yes |
Example
curl https://api.faxforagents.com/v1/organizations/current/auto-send \
-X PUT \
-H "Authorization: Bearer ffa_test_..." \
-H "Content-Type: application/json" \
-d '{"enabled":false}'
GET /v1/policies
List the organization's policies
Credential: an API key or a signed-in session. Scope: policy:read.
Responses
| Status | Meaning |
|---|---|
200 | Successful Response |
401 | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
403 | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
429 | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |
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)
Credential: an API key or a signed-in session. Scope: policy:admin.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
Idempotency-Key | header | string | no | Replay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay. |
Request body application/json, required
| 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
| Status | Meaning |
|---|---|
201 | Successful Response |
401 | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
403 | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
409 | The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state. |
422 | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
429 | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |
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 | |
managed | string or null | no | connected_apps when a Settings switch (the auto-send setting) manages this policy. Such a policy is read here and changed only through PUT /v1/organizations/current/auto-send. |
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_..." \
-H "Content-Type: application/json" \
-d '{"name":"example","rules":{}}'
GET /v1/policies/effective
Read the rules bound to the caller, an API key, an agent or a connected app
Credential: an API key or a signed-in session. Scope: policy:read.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
key_id | query | string or null | no | An API key in your organization. Uses the key's environment. Choose either key_id or agent_id. |
agent_id | query | string or null | no | An agent in your organization. Uses the current environment and excludes bindings to individual keys. |
connection_id | query | string or null | no | A connected app (an OAuth grant, GET /v1/oauth/grants) in your organization; a signed-in person sees their own. Uses the connection's environment. Choose at most one of key_id, agent_id and connection_id. |
Responses
| Status | Meaning |
|---|---|
200 | Successful Response |
401 | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
403 | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
404 | not_found: no such resource for this credential. A resource of another organization answers the same. |
422 | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
429 | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |
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 | |
requires_human_approval | boolean | no | Server-set for hosted integration clients and their keys: every send needs a person's approval, whatever the policies say. |
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)
Credential: an API key or a signed-in session. Scope: policy:read.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
policy_id | path | string | yes |
Responses
| Status | Meaning |
|---|---|
200 | Successful Response |
401 | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
403 | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
404 | not_found: no such resource for this credential. A resource of another organization answers the same. |
422 | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
429 | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |
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 | |
managed | string or null | no | connected_apps when a Settings switch (the auto-send setting) manages this policy. Such a policy is read here and changed only through PUT /v1/organizations/current/auto-send. |
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
Credential: an API key or a signed-in session. Scope: policy:admin.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
policy_id | path | string | yes | |
Idempotency-Key | header | string | no | Replay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay. |
Request body application/json, required
| 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
| Status | Meaning |
|---|---|
200 | Successful Response |
401 | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
403 | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
404 | not_found: no such resource for this credential. A resource of another organization answers the same. |
409 | The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state. |
422 | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
429 | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |
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 | |
managed | string or null | no | connected_apps when a Settings switch (the auto-send setting) manages this policy. Such a policy is read here and changed only through PUT /v1/organizations/current/auto-send. |
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_..." \
-H "Content-Type: application/json" \
-d '{}'
GET /v1/policies/{policy_id}/versions
Every version, oldest first
Credential: an API key or a signed-in session. Scope: policy:read.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
policy_id | path | string | yes |
Responses
| Status | Meaning |
|---|---|
200 | Successful Response |
401 | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
403 | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
404 | not_found: no such resource for this credential. A resource of another organization answers the same. |
422 | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
429 | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |
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
Credential: an API key or a signed-in session. Scope: policy:read.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
Idempotency-Key | header | string | no | Replay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay. |
Request body application/json, required
| 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
| Status | Meaning |
|---|---|
200 | Successful Response |
401 | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
403 | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
404 | not_found: no such resource for this credential. A resource of another organization answers the same. |
409 | The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state. |
422 | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
429 | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |
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_..." \
-H "Content-Type: application/json" \
-d '{}'