Docs menu

API overview

The /v1 API's base URL, authentication, errors, rate limits and versioning.

View this page as Markdown

Base URL

Every endpoint lives under one base URL, over HTTPS:

Text
https://api.fenfill.com/v1

Requests and responses are JSON, except the analyze upload (multipart/form-data) and the two file downloads (a PDF, a logo image). Each endpoint is listed in the API reference. The same reference is served live, generated from the running API, at api.fenfill.com/v1/docs, and the OpenAPI document at api.fenfill.com/v1/openapi.json.

Authentication

Every request carries a workspace API key, as a Bearer token in the Authorization header:

Terminal
curl https://api.fenfill.com/v1/account \  -H "Authorization: Bearer $FENFILL_API_KEY"
  • Keys look like ff_live_ followed by 43 characters. Create and revoke them in Dashboard → Settings → API keys. API access is included on Pro and above.
  • The key is read only from the Authorization header, never from a query parameter or another header.
  • A key is shown once, when it is created. fenfill stores only a hash of it, so a lost key can't be shown again: create a new one.
  • A workspace can have up to 10 active keys. A key acts for its workspace, and stops working when it is revoked or when the person who created it leaves the workspace.
  • Every request is scoped to the key's workspace. A template or job in another workspace answers 404 not_found, exactly like one that doesn't exist.

Errors

Every response that isn't a 2xx has the same JSON body: a stable machine-readable code and a message written for people.

429 Too Many Requests
{  "error": {    "code": "rate_limited",    "message": "Too many analyze requests for this API key. Please slow down and retry.",    "retry_after": 6  }}

Some errors add fields next to code and message: retry_after (seconds) on a 429, need and have on insufficient_scans, max_bytes on a 413, current_updated_at on template_conflict, and details on invalid_schema and template_needs_migration. Branch on code, not on the message or the status: the list of codes is closed.

CodeStatusMeaning
invalid_api_key401The key is missing, malformed or unknown. Send it as Authorization: Bearer ff_live_….
key_revoked401The key was revoked, or the person who created it left the workspace. Create a new one.
plan_required403The workspace's plan doesn't include API access (Pro and above do), or the feature asked for (POST /v1/forms/acroform needs the Team plan or above). It works again after an upgrade.
template_frozen403The template is frozen because the workspace is over its plan's template limit. 409 on POST /v1/templates/{template_id}/recipients.
pdf_password_required422The PDF needs a password to open. Upload a copy without the open password.
pdf_fill_forbidden422The PDF's author doesn't allow it to be filled in.
filled_output422The PDF is a form already filled with fenfill. Only blank forms can be analyzed: upload the original blank PDF.
too_many_pages422The chosen pages are more than one analysis covers. Split the document into several jobs with pages (see max_pages_per_job on GET /v1/account).
not_pdf422The file is not a PDF.
daily_limit429The workspace reached its daily AI processing limit. Try again later.
too_many_active_jobs429The workspace already has as many analyze jobs queued or running as it may. Wait for one to finish.
queue_full429The analysis queue is full right now. Retry after Retry-After seconds.
rate_limited429The key sent too many requests. Retry after Retry-After seconds.
template_limit409Saving would take the workspace past its plan's template limit. Delete a template, or analyze without save_as_template.
template_not_restricted409Recipient links need a template whose access is restricted.
template_conflict409The template changed since expected_updated_at. The body carries current_updated_at: fetch the template again, re-apply your edits and retry.
invalid_schema422The edited template doesn't validate. details lists each problem as {path, reason} (at most 50).
template_needs_migration422The stored template holds data from an older version that the API can't save back. Make the change in the fenfill web editor.
insufficient_scans402The workspace has too few page scans left for this job. The body carries need and have.
job_in_progress409An analysis of this template is already queued or running. Wait for it to finish, then fetch the template again.
kind_required422The chosen pages already have fields: pass kind (find, relabel, label_missing, or scratch to replace them).
not_found404No such resource in this workspace. A resource in another workspace answers the same way.
result_expired410The job's unsaved result is gone (kept 24 hours after the job finishes and 1 hour after its first fetch), or its saved template was deleted. Analyze the PDF again.
invalid_request422The request is malformed; the message says what to change. Sometimes 400, and 408 when an upload was too slow and the server stopped waiting.
file_too_large413The file is larger than the plan allows. The body carries max_bytes.
request_too_large413The edited template is too large to save in one request. The body carries max_bytes.
unavailable503The service is unavailable for a moment. Retry shortly.
internal_error500Something went wrong on our side. The error has been reported.

A finished job that failed is not an error response: GET /v1/jobs/{job_id} answers 200 with status set to error and an error object of its own, with codes such as pdf_corrupt, no_fields_found or job_timeout.

Rate limits

LimitApplies toWhen it's exceeded
60 requests a minuteEach API key, every endpoint429 rate_limited
10 analyses a minuteEach API key, POST /v1/forms/analyze and POST /v1/templates/{template_id}/analyze429 rate_limited
10 PDF downloads a minuteEach API key, GET /v1/templates/{template_id}/pdf and POST /v1/forms/acroform together429 rate_limited
30 template updates a minuteEach API key, PATCH /v1/templates/{template_id}429 rate_limited
A few analyze jobs queued or running at onceEach workspace429 too_many_active_jobs
A daily AI processing limitEach workspace429 daily_limit

Each per-key limit allows a burst: a quiet key may spend a full minute's requests at once, and is then held to the steady rate.

Every 429 carries a Retry-After header in whole seconds, and the same number as retry_after in the body. Wait that long before retrying. While a job runs, GET /v1/jobs/{job_id} also sends Retry-After as a polling hint (10 seconds while queued, 3 while running), so a few jobs polled as told stay under the per-key limit.

The limits can change to protect the service; this page always shows the current ones. Don't spread requests over several keys or workspaces to get around them (Terms §11b).

Page scans

Analyzing forms uses page scans from your plan, at the same rate as in the web app:

  • Analyzing a flat or scanned form uses one page scan per analyzed page.
  • A form that already has fillable fields is read directly, with no AI, and uses no page scans, unless you opt in to detect_extra_blanks, which uses one page scan per page it covers.
  • Re-analyzing pages of a saved template (POST /v1/templates/{template_id}/analyze) uses one page scan per page. label_missing first uses the label pages your plan includes each month. Pass estimate: true to see the cost without starting anything.
  • Filling a form happens on your machine with the MCP server and uses no page scans.

The scans for a job are reserved when it is accepted, and returned automatically, usually within the hour, if the job fails on our side. A request refused before the analysis starts uses none. cost on the 202 and on the job says how many a job reserved, and GET /v1/account returns scans_remaining. Plans and their allowances are on Pricing.

Versioning

The API is versioned in its path: /v1. We may add to it at any time, such as new endpoints, new optional parameters or new fields in a response, so ignore fields you don't know.

Before we remove or change anything in a way that breaks existing integrations, we give at least 30 days' notice, by email or in the changelog, unless the change is urgently needed for security or legal reasons. See Terms §11b.