# API reference

Every `/v1` endpoint, grouped by tag, with its parameters, schemas and a curl example.

## Before you start

This page is generated from the API's OpenAPI document. Every example reads your key from `$FENFILL_API_KEY`; base URL, authentication, errors and limits are on the [API overview](https://fenfill.com/docs/api). The live reference is at [api.fenfill.com/v1/docs](https://api.fenfill.com/v1/docs), and the OpenAPI document at [api.fenfill.com/v1/openapi.json](https://api.fenfill.com/v1/openapi.json).

> **Privacy note:** The API has no endpoint that fills a form, and no request takes answers. Your agent fills forms on your machine with the MCP server's `fill_form` or `fill_template` ([MCP tools](https://fenfill.com/docs/mcp/tools)); only the blank form is uploaded, to analyze it.

## Forms

Analyze a blank PDF form into a fillable schema, or download a template as a fillable PDF.

### Analyze a blank PDF form

`POST /v1/forms/analyze` (auth: Bearer API key)

Queue a blank PDF for analysis. Poll `Location` (GET /v1/jobs/{job_id}) until it is done. A PDF with native form fields costs nothing (unless `detect_extra_blanks` adds a vision pass); otherwise one page scan per analyzed page is reserved now and refunded if the job fails.

> **Privacy note:** Upload the **blank** form only. Answers never go through this API: your agent fills the form on your machine with the MCP server's `fill_form` ([MCP tools](https://fenfill.com/docs/mcp/tools)).

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `file` | form | `file` | yes | The blank PDF form, at most your plan's max_file_mb. |
| `name` | form | `string` | no | A name for the form. Defaults to the file name. |
| `pages` | form | `string` | no | 1-based pages to analyze, e.g. "1-3,5". Omit for every page. One AI job covers at most max_pages_per_job pages (GET /v1/account): split a longer document into several jobs. If the chosen pages carry native form fields, the PDF's native fields are read instead (every page up to max_pages, with no per-job cap), at no cost. |
| `save_as_template` | form | `boolean` | no | true: save the result as a template in the workspace. false: keep it only as this job's result (24 h, or 1 h after the first fetch). Default `false`. |
| `access` | form | `"public" \| "restricted"` | no | The saved template's access; only with save_as_template=true. public (the default): anyone with the template's link can fill it. restricted: workspace members and per-recipient links only. Recipient links (POST /v1/templates/{id}/recipients) need a restricted template. |
| `detect_extra_blanks` | form | `boolean` | no | Only for a PDF with native form fields (ignored otherwise: AI analysis already finds every blank). true: after reading the native fields, a vision pass over the chosen `pages` (at most max_pages_per_job) adds blanks that have no native field, such as a signature or date line; native fields win where they overlap. Costs one page scan per page (the job is then reported as mode `ai`). false: uses no page scans; the 202 says whether such blanks seem likely (`extra_blanks_available`). Default `false`. |

Request body: `multipart/form-data`.

Returns `202` with `AnalyzeAccepted`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | yes | – |
| `status` | `string` | yes | – |
| `mode` | `"ai" \| "acroform"` | yes | `acroform`: the PDF has native form fields, read using no page scans. `ai`: page scans are reserved: analyzed with AI, or an AcroForm PDF analyzed with detect_extra_blanks=true (its native fields are still read using no page scans; the scans pay for the vision pass over extra_blanks_pages). |
| `pages` | `array of integer` | yes | The 1-based pages the job covers, sorted. |
| `cost` | `integer` | yes | Page scans reserved for this job (0 for acroform). |
| `extra_blanks` | `boolean \| null` | no | Only on an AcroForm PDF analyzed with detect_extra_blanks=true: always true. The native fields are read using no page scans, then a vision pass over extra_blanks_pages adds blanks that have no native field (native fields win where they overlap). Costs one scan per page in extra_blanks_pages. |
| `extra_blanks_pages` | `array of integer \| null` | no | With extra_blanks: the 1-based pages the vision pass covers (what `cost` pays for). |
| `extra_blanks_available` | `boolean \| null` | no | Only on an AcroForm job that uses no page scans: true when the chosen pages show blanks no native field covers (underscore runs, label-and-line blanks, signature/date captions). A heuristic hint that uses no page scans: analyze again with detect_extra_blanks=true to find them. |

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `402` | `insufficient_scans` (with need, have) |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `408` | `invalid_request` (the upload was too slow) |
| `409` | `template_limit` |
| `413` | `file_too_large` (with `max_bytes`) |
| `422` | `not_pdf` / `pdf_password_required` / `pdf_fill_forbidden` / `filled_output` / `too_many_pages` / `invalid_request` |
| `429` | `rate_limited` / `daily_limit` / `too_many_active_jobs` / `queue_full` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl -X POST "https://api.fenfill.com/v1/forms/analyze" \
  -H "Authorization: Bearer $FENFILL_API_KEY" \
  -F "file=@blank-form.pdf" \
  -F "save_as_template=true"
```

### Download a template as a fillable PDF

`POST /v1/forms/acroform` (auth: Bearer API key)

A saved template's PDF with a fillable field on every blank, for any PDF reader. The fields are empty: this returns the blank form only and takes no values (fill it on your machine). Needs the Team plan or above. Shares the PDF download limits of GET /templates/{template_id}/pdf: past either, 429 with Retry-After. Fields that can't be placed (a page outside the PDF, a broken box) are left out and the rest exported; the response then carries `X-Fenfill-Skipped-Fields` with their count.

> **Privacy note:** This returns the saved template as a **blank** PDF with a fillable field on every blank, for any PDF reader. The body is only `template_id`: a value can't be sent, and the fields come back empty. Needs the Team plan or above (`403 plan_required` otherwise).

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `template_id` | body | `string` | yes | The id of a template in this workspace. |

Request body: `application/json` (`AcroformExportRequest`).

Returns `200` with `application/pdf`: The blank form with fillable fields, as an attachment.

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `422` | `pdf_password_required` / `pdf_fill_forbidden` / `invalid_schema` / `template_needs_migration` / `invalid_request` |
| `429` | `rate_limited` (with Retry-After) |
| `503` | `unavailable` |

```sh
# Needs the Team plan or above. Saves the blank form with fillable fields; no values are sent.
curl -X POST "https://api.fenfill.com/v1/forms/acroform" \
  -H "Authorization: Bearer $FENFILL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template_id": "<template id>"}' \
  -o blank-form.pdf
```

## Jobs

Poll an analyze job and fetch its result.

### Get an analyze job's status and result

`GET /v1/jobs/{job_id}` (auth: Bearer API key)

Status while queued/running, with a polling hint: `Retry-After: 10` while queued, `3` while running (so three jobs polled as told stay under the 60/min key limit). Once done, `result` is the agent schema. An unsaved result is kept 24 hours after the job finishes and 1 hour after its first fetch, then 410 (as is a saved one whose template was deleted).

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `job_id` | path | `string` | yes | – |
| `include` | query | `string \| null` | no | `render` adds the raw v5 schema (page images blanked) once done. |

Returns `200` with `JobStatus`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | yes | – |
| `status` | `"queued" \| "running" \| "done" \| "error"` | yes | – |
| `mode` | `"ai" \| "acroform"` | yes | `acroform`: the PDF has native form fields, read using no page scans. `ai`: page scans are reserved: analyzed with AI, or an AcroForm PDF analyzed with detect_extra_blanks=true (its native fields are still read using no page scans; the scans pay for the vision pass over extra_blanks_pages). |
| `phase` | `string \| null` | yes | – |
| `progress` | `integer` | yes | – |
| `pages` | `array of integer` | yes | – |
| `cost` | `integer` | yes | – |
| `created_at` | `string (date-time)` | yes | – |
| `template_id` | `string \| null` | yes | Set when the job saved a template (save_as_template, or a template re-analysis). |
| `error` | `JobError \| null` | yes | – |
| `result` | `AgentSchema \| null` | yes | The agent schema, once the job is done. |
| `render` | `object \| null` | no | Only with ?include=render on a done job: the raw v5 schema, page images blanked. |

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `template_frozen` (the saved template) |
| `404` | `not_found` |
| `410` | `result_expired` |
| `422` | `invalid_request` |
| `429` | `rate_limited` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl "https://api.fenfill.com/v1/jobs/$JOB_ID" \
  -H "Authorization: Bearer $FENFILL_API_KEY"
```

## Templates

The workspace's templates: their agent schemas and blank PDFs.

### List the workspace's templates

`GET /v1/templates` (auth: Bearer API key)

Active templates, newest first. Frozen templates and unfinished drafts (still awaiting the owner's AI/manual setup choice) are not listed.

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | `integer` | no | Page size (1–100). Default `50`. |
| `cursor` | query | `string \| null` | no | next_cursor from the previous page. |

Returns `200` with `TemplateList`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `templates` | `array of TemplateSummary` | yes | – |
| `next_cursor` | `string \| null` | yes | Pass as ?cursor= for the next page; null at the end. |

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `422` | `invalid_request` |
| `429` | `rate_limited` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl "https://api.fenfill.com/v1/templates" \
  -H "Authorization: Bearer $FENFILL_API_KEY"
```

### Get a template's agent schema

`GET /v1/templates/{template_id}` (auth: Bearer API key)

The fillable inputs of a template, in reading order, as agent-schema v1. `updated_at` is the version to pass back when correcting it (PATCH).

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `template_id` | path | `string` | yes | – |
| `include` | query | `string \| null` | no | `render` adds the raw v5 schema (page images blanked). |

Returns `200` with `AgentSchema`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `schema_version` | `integer` | yes | – |
| `template_id` | `string \| null` | yes | – |
| `name` | `string \| null` | yes | – |
| `updated_at` | `string \| null` | no | Only on a saved template: when its schema last changed. Pass it back unchanged as `expected_updated_at` to PATCH /v1/templates/{id}. |
| `page_count` | `integer` | yes | – |
| `pages` | `array of AgentPage` | yes | – |
| `fields` | `array of ScalarEntry \| SignatureEntry \| CombEntry \| ChoiceEntry \| TableEntry \| TableRowsEntry` | yes | – |
| `render` | `object \| null` | no | Only with ?include=render: the raw v5 template schema, page images blanked. |

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `422` | `invalid_request` |
| `429` | `rate_limited` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl "https://api.fenfill.com/v1/templates/$TEMPLATE_ID" \
  -H "Authorization: Bearer $FENFILL_API_KEY"
```

### Correct a template's layout

`PATCH /v1/templates/{template_id}` (auth: Bearer API key)

Save corrections to a template's fields, groups and sections: send the whole edited v5 schema (as `?include=render` returns it) with the `updated_at` it was based on. Only layout and the form's own wording are stored (labels, descriptions, placeholders, options), never fill values: an unknown or value-bearing key is refused. Page images, sizes and the page count stay as they are. If the template changed meanwhile, 409 template_conflict with `current_updated_at`: fetch it again and re-apply. 422 template_needs_migration when the stored template carries data from an older version that can't be saved back unchanged: make that edit in the web editor.

> **Privacy note:** This saves a correction to the form's **layout and wording** (labels, descriptions, placeholders, options), not answers. Every part of the body has a fixed set of keys, and a key that could carry a value is refused with `422`.

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `template_id` | path | `string` | yes | – |
| `schema` | body | `V5TemplateSchema` | yes | The full v5 template schema, as GET /v1/templates/{id}?include=render returns it, edited. Taken from it: `fields`, `groups` and each page's `sections`. Everything else (the page list, page images and sizes) stays the server's; the page count and `version` (5) must match. |
| `expected_updated_at` | body | `string (date-time)` | yes | The template's `updated_at` the edit is based on (from GET). A newer one is 409. |

Request body: `application/json` (`TemplateUpdate`).

Returns `200` with `TemplateUpdated`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `template_id` | `string` | yes | – |
| `updated_at` | `string (date-time)` | yes | – |
| `result` | `AgentSchema` | yes | – |

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `409` | `template_conflict` (with `current_updated_at`) / `job_in_progress` (a re-analysis is running) |
| `413` | `request_too_large` (with `max_bytes`) |
| `422` | `invalid_schema` (with details: [{path, reason}]) / `invalid_request` / `template_needs_migration` (the stored template holds data from an older version that the API can't save back; edit it in the web editor) |
| `429` | `rate_limited` (with Retry-After): too many updates, or too many in progress |
| `503` | `unavailable` |

```sh
# corrected-template.json: {"schema": <the edited v5 schema from ?include=render>, "expected_updated_at": "<updated_at from GET>"}
curl -X PATCH "https://api.fenfill.com/v1/templates/$TEMPLATE_ID" \
  -H "Authorization: Bearer $FENFILL_API_KEY" \
  -H "Content-Type: application/json" \
  -d @corrected-template.json
```

### Download the blank template PDF

`GET /v1/templates/{template_id}/pdf` (auth: Bearer API key)

The template's blank PDF (decrypted server-side), for filling locally. Rate-limited per key per minute, and to a few at once across the service: past either, 429 with Retry-After.

> **Tip:** This is the blank form, for filling on your machine. The MCP server's `fill_template` downloads it for you ([MCP tools](https://fenfill.com/docs/mcp/tools)).

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `template_id` | path | `string` | yes | – |

Returns `200` with `application/pdf`: The blank template PDF.

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `422` | `invalid_request` |
| `429` | `rate_limited` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl "https://api.fenfill.com/v1/templates/$TEMPLATE_ID/pdf" \
  -H "Authorization: Bearer $FENFILL_API_KEY" \
  -o blank-form.pdf
```

### Re-analyze pages of a saved template

`POST /v1/templates/{template_id}/analyze` (auth: Bearer API key)

Run AI again on chosen pages of a saved template, using the PDF already stored with it (nothing is uploaded). Analyze pages never analyzed (`pages[].analyzed` false on GET) with `scratch`; `find` adds missed blanks; `relabel` / `label_missing` fix labels. Poll `Location` (GET /v1/jobs/{id}) until done: the result is the updated template. One page scan per page is reserved now and refunded if the job fails (`label_missing` uses the monthly included label pages first). With `estimate: true` the price is returned (200) and nothing starts.

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `template_id` | path | `string` | yes | – |
| `pages` | body | `string` | yes | The 1-based pages to analyze, e.g. "2" or "1-3,5". |
| `kind` | body | `"scratch" \| "find" \| "relabel" \| "label_missing" \| null` | no | `scratch`: analyze the pages from nothing, replacing their fields, groups and sections (for a page never analyzed, or one whose layout is wrong). `find`: add blanks the first pass missed, keeping everything there (deleted items stay deleted). `relabel`: re-derive labels, types, groups and sections, keeping the geometry. `label_missing`: fill in only missing labels (uses the monthly included label pages, then one scan per page). The others cost one scan per page. Omitted: `scratch`, allowed only when every chosen page has no fields. |
| `expected_updated_at` | body | `string \| null` | no | The template's `updated_at` from GET. Required unless `estimate` is true; a newer one is 409 template_conflict. |
| `estimate` | body | `boolean` | no | true: price the run (200 RerunEstimate) and start nothing. Default `false`. |

Request body: `application/json` (`TemplateAnalyze`).

Returns `200` with `RerunEstimate` (With estimate=true: the price; nothing is started.):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `"scratch" \| "find" \| "relabel" \| "label_missing"` | yes | – |
| `pages` | `array of integer` | yes | The 1-based pages the run would cover, sorted. |
| `cost` | `integer` | yes | Page scans the run would charge now. |
| `free_label_pages` | `integer` | yes | How many of `pages` the monthly included label pages would cover (label_missing only). |
| `free_label_pages_left` | `integer` | yes | Included label pages left this month after the run. |
| `scans_left` | `integer` | yes | The workspace's page scans left now. |
| `insufficient` | `boolean` | yes | true: the run would be refused with 402 insufficient_scans. |

Returns `202` with `AnalyzeAccepted`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | yes | – |
| `status` | `string` | yes | – |
| `mode` | `"ai" \| "acroform"` | yes | `acroform`: the PDF has native form fields, read using no page scans. `ai`: page scans are reserved: analyzed with AI, or an AcroForm PDF analyzed with detect_extra_blanks=true (its native fields are still read using no page scans; the scans pay for the vision pass over extra_blanks_pages). |
| `pages` | `array of integer` | yes | The 1-based pages the job covers, sorted. |
| `cost` | `integer` | yes | Page scans reserved for this job (0 for acroform). |
| `extra_blanks` | `boolean \| null` | no | Only on an AcroForm PDF analyzed with detect_extra_blanks=true: always true. The native fields are read using no page scans, then a vision pass over extra_blanks_pages adds blanks that have no native field (native fields win where they overlap). Costs one scan per page in extra_blanks_pages. |
| `extra_blanks_pages` | `array of integer \| null` | no | With extra_blanks: the 1-based pages the vision pass covers (what `cost` pays for). |
| `extra_blanks_available` | `boolean \| null` | no | Only on an AcroForm job that uses no page scans: true when the chosen pages show blanks no native field covers (underscore runs, label-and-line blanks, signature/date captions). A heuristic hint that uses no page scans: analyze again with detect_extra_blanks=true to find them. |

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `402` | `insufficient_scans` (with need, have) |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `409` | `template_conflict` (with `current_updated_at`) / `job_in_progress` |
| `422` | `invalid_request` / `too_many_pages` / `kind_required` |
| `429` | `rate_limited` / `daily_limit` / `too_many_active_jobs` / `queue_full` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl -X POST "https://api.fenfill.com/v1/templates/$TEMPLATE_ID/analyze" \
  -H "Authorization: Bearer $FENFILL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pages": "2", "kind": "find", "expected_updated_at": "<updated_at from GET>"}'
```

## Recipients

Per-recipient magic links on restricted templates.

### Create a per-recipient link

`POST /v1/templates/{template_id}/recipients` (auth: Bearer API key)

A magic link for one recipient of a restricted template. The link's status moves pending → opened → completed as the recipient fills (no answers are ever sent back).

> **Privacy note:** This creates a link and nothing more. The recipient fills the form in their browser; their answers are never sent back, to this API or to fenfill. The link's `status` says only whether it was opened and completed.

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `template_id` | path | `string` | yes | – |
| `label` | body | `string` | yes | Who the link is for, as the sender will see it (e.g. 'Anna K.'). |
| `expires_at` | body | `string (date-time) \| null` | no | When the link stops working (ISO 8601, UTC if no offset). Omit for no expiry. |

Request body: `application/json` (`RecipientCreate`).

Returns `201` with `Recipient`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | – |
| `label` | `string \| null` | yes | – |
| `url` | `string` | yes | – |
| `status` | `"pending" \| "opened" \| "completed" \| "sent_back"` | yes | pending → opened → completed. `sent_back` is reserved (not yet emitted). |
| `opened_at` | `string (date-time) \| null` | yes | – |
| `completed_at` | `string (date-time) \| null` | yes | – |
| `created_at` | `string (date-time)` | yes | – |
| `expires_at` | `string (date-time) \| null` | yes | – |

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `409` | `template_not_restricted` / `template_frozen` |
| `422` | `invalid_request` |
| `429` | `rate_limited` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl -X POST "https://api.fenfill.com/v1/templates/$TEMPLATE_ID/recipients" \
  -H "Authorization: Bearer $FENFILL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "Anna K."}'
```

### List a template's recipient links

`GET /v1/templates/{template_id}/recipients` (auth: Bearer API key)

The template's live (not revoked) recipient links, newest first, at most 500. Revoking happens in the fenfill app.

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `template_id` | path | `string` | yes | – |

Returns `200` with `RecipientList`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `recipients` | `array of Recipient` | yes | – |

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `422` | `invalid_request` |
| `429` | `rate_limited` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl "https://api.fenfill.com/v1/templates/$TEMPLATE_ID/recipients" \
  -H "Authorization: Bearer $FENFILL_API_KEY"
```

## Account

The workspace behind the API key: plan, limits and branding.

### The workspace behind this key

`GET /v1/account` (auth: Bearer API key)

Plan, remaining page scans, limits and branding flags. `scans_remaining` is the balance at the time of this request. `max_pages` is how far into a document the plan reads; `max_pages_per_job` is how many pages one AI analyze job may cover (split a longer document into several jobs with `pages`; a PDF with native form fields is read whole, up to `max_pages`).

No parameters.

Returns `200` with `Account`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `workspace_id` | `string` | yes | – |
| `tier` | `string` | yes | – |
| `scans_remaining` | `integer \| null` | yes | – |
| `max_pages` | `integer \| null` | yes | How far into a document the plan reads (its first N pages). |
| `max_pages_per_job` | `integer` | yes | The most pages one AI analyze job may cover; split a longer document with `pages`. A PDF with native form fields is read whole (up to max_pages), with no per-job cap. |
| `max_file_mb` | `integer \| null` | yes | – |
| `branding` | `BrandingFlags` | yes | – |

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `422` | `invalid_request` |
| `429` | `rate_limited` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl "https://api.fenfill.com/v1/account" \
  -H "Authorization: Bearer $FENFILL_API_KEY"
```

### The workspace's branding logo

`GET /v1/account/logo` (auth: Bearer API key)

The logo to stamp on filled PDFs; 404 when the plan has no custom branding or no logo is uploaded.

No parameters.

Returns `200` with `image/png` or `image/jpeg`: The logo image.

Errors ([codes](https://fenfill.com/docs/api#errors)):

| Status | Code |
| --- | --- |
| `401` | `invalid_api_key` / `key_revoked` |
| `403` | `plan_required` / `template_frozen` |
| `404` | `not_found` |
| `422` | `invalid_request` |
| `429` | `rate_limited` (with Retry-After) |
| `503` | `unavailable` |

```sh
curl "https://api.fenfill.com/v1/account/logo" \
  -H "Authorization: Bearer $FENFILL_API_KEY" \
  -o logo
```

---

This is the Markdown version of https://fenfill.com/docs/api/reference.
