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

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

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

Response fields

GET /v1/organizations/current/auto-send response fields
FieldTypeRequiredDescription
daily_max_jobsintegeryesFaxes per UTC day, at most, across every connected app.
enabledbooleanyes
environmentstring (live, test)yes
max_amount_microintegeryesAmount per fax, at most, in micro-units of USD.
policy_idstring or nullyesThe managed policy; null before the first enable.
versioninteger or nullyes

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

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

Request body application/json, required

PUT /v1/organizations/current/auto-send request body
FieldTypeRequiredDescription
daily_max_jobsintegernoFaxes per UTC day.
enabledbooleanyes
max_amount_microintegernoAmount per fax, at most, in micro-units of USD (5000000 = $5.00).

Responses

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

Response fields

PUT /v1/organizations/current/auto-send response fields
FieldTypeRequiredDescription
daily_max_jobsintegeryesFaxes per UTC day, at most, across every connected app.
enabledbooleanyes
environmentstring (live, test)yes
max_amount_microintegeryesAmount per fax, at most, in micro-units of USD.
policy_idstring or nullyesThe managed policy; null before the first enable.
versioninteger or nullyes

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

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

Response fields

GET /v1/policies response fields
FieldTypeRequiredDescription
itemsarray of object Policyyes

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

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

Request body application/json, required

POST /v1/policies request body
FieldTypeRequiredDescription
enabledbooleanno
namestringyes
purposestring or nullnoWhy this policy exists. Shown, never evaluated.
rulesobjectyesSee the PolicyRules schema in docs/APPROVALS.md.

Responses

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

Response fields

POST /v1/policies response fields
FieldTypeRequiredDescription
created_atstringyes
created_bystringyes
current_version_idstring or nullyes
enabledbooleanyes
idstringyes
managedstring or nullnoconnected_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.
namestringyes
purposestring or nullyes
rulesobjectyes
updated_atstringyes
versionintegeryes

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

GET /v1/policies/effective parameters
NameInTypeRequiredNotes
key_idquerystring or nullnoAn API key in your organization. Uses the key's environment. Choose either key_id or agent_id.
agent_idquerystring or nullnoAn agent in your organization. Uses the current environment and excludes bindings to individual keys.
connection_idquerystring or nullnoA 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

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

Response fields

GET /v1/policies/effective response fields
FieldTypeRequiredDescription
actorstringyes
approval_required_without_policybooleanyes
environmentstring (live, test)yes
policiesarray of object Policyyes
requires_human_approvalbooleannoServer-set for hosted integration clients and their keys: every send needs a person's approval, whatever the policies say.
rules_orderarray of stringyes
under_policybooleanyes

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

GET /v1/policies/{policy_id} parameters
NameInTypeRequiredNotes
policy_idpathstringyes

Responses

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

Response fields

GET /v1/policies/{policy_id} response fields
FieldTypeRequiredDescription
created_atstringyes
created_bystringyes
current_version_idstring or nullyes
enabledbooleanyes
idstringyes
managedstring or nullnoconnected_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.
namestringyes
purposestring or nullyes
rulesobjectyes
updated_atstringyes
versionintegeryes

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

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

Request body application/json, required

PATCH /v1/policies/{policy_id} request body
FieldTypeRequiredDescription
enabledboolean or nullno
namestring or nullno
purposestring or nullno
rulesobject or nullno

Responses

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

Response fields

PATCH /v1/policies/{policy_id} response fields
FieldTypeRequiredDescription
created_atstringyes
created_bystringyes
current_version_idstring or nullyes
enabledbooleanyes
idstringyes
managedstring or nullnoconnected_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.
namestringyes
purposestring or nullyes
rulesobjectyes
updated_atstringyes
versionintegeryes

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

GET /v1/policies/{policy_id}/versions parameters
NameInTypeRequiredNotes
policy_idpathstringyes

Responses

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

Response fields

GET /v1/policies/{policy_id}/versions response fields
FieldTypeRequiredDescription
itemsarray of object PolicyVersionyes

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

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

Request body application/json, required

POST /v1/policy-evaluations request body
FieldTypeRequiredDescription
document_idstring or nullnoOr plan: this document ...
fromstring or nullno
quote_idstring or nullnoEvaluate an existing quote.
tostring or nullno... to this E.164 destination.

Responses

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

Response fields

POST /v1/policy-evaluations response fields
FieldTypeRequiredDescription
decisionstringyes
denialsarray of objectyes
executedbooleanno
planobjectyes
policy_idstring or nullyes
policy_namestring or nullyes
policy_version_idstring or nullyes
reasonstringyes
rulestring or nullyes
versioninteger or nullyes

Example

curl https://api.faxforagents.com/v1/policy-evaluations \
  -X POST \
  -H "Authorization: Bearer ffa_test_..." \
  -H "Content-Type: application/json" \
  -d '{}'