API

Nocarta provides an API that lets you build your own integrations, scripts, or tools that work with your documents, forms, notebooks, and reference lists programmatically.

The same API powers the AI Integration feature (both REST and MCP). Most of what your AI assistant can do, your own code can do too — though a few write operations (value sets, text extraction scans, form submissions, and editing notebooks) are REST-only and have no MCP equivalent yet.


Getting Started

Authentication

Every API request requires an access token. You get one from Settings → Profile and Connections (the same token works for both AI assistants and direct API calls).

Include your token in every request as a header:

Authorization: Bearer nc_token_your_token_here

Alternatively, you can pass it via the X-API-Key header instead:

X-API-Key: nc_token_your_token_here

Base URL

  • Production: https://app.nocarta.ai/api/v1
  • Local development: http://localhost:3000/api/v1

Format

All requests and responses use JSON. When sending data, set the header:

Content-Type: application/json

Quick test: Try this in your terminal to verify your token works:

curl -H "Authorization: Bearer nc_token_your_token" \
     https://app.nocarta.ai/api/v1/form_templates

Scopes

Tokens are scoped — each one grants only the specific capabilities you select when creating it. Requests using a scope the token doesn't hold get a 403 Forbidden response with a required_scope field.

ScopeWhat it grants
user:readRead your account profile, capabilities, and usage stats.
form_templates:readList and inspect form templates, notebooks, and fields.
form_templates:writeCreate, edit, scan, and delete form templates.
form_instances:readList and view filled form instances and field values.
form_instances:writeCreate, edit, scan, submit, and bulk-operate on form instances.
notebooks:writeCreate, edit, scan, and save values on notebooks (reads use form_templates:read).
media_notes:readList and view your recordings, their transcripts, and processing status.
media_notes:writeUpload, delete, and restore recordings.
value_sets:readList and view value sets, entries, and export.
value_sets:read_oneView a single value set and its entries/export (no listing).
value_sets:writeCreate, edit, delete value sets, and import entries.
automations:readList and inspect automations, including their NAL scripts; check a script; and read what a run of one did.
automations:writeCreate, edit and delete automations and their NAL scripts.
automations:runStart runs of your automations on your desktop. Separate from automations:write: editing a script and running one are different permissions.
system_scripts:readList and inspect scripts and their source; check a script; read runs and their outputs.
system_scripts:writeCreate, edit (each edit is a new version) and archive scripts; delete finished runs.
system_scripts:runStart and stop script runs on your desktop or a Nocarta Runner. Separate from system_scripts:write, for the same reason.
remote_execution:readSee where a remote run would land, where your account sends runs, your machines, and whether a Nocarta Runner is up for your account.
remote_execution:writeChoose where your account's remote runs go, rename or forget your machines, and shut down your Nocarta Runner.
search:readSearch everything you own by meaning — documents and value-set entries.
indexing:readRead which of your content is searchable by meaning.
indexing:writeTurn meaning-search indexing on or off.
partners:readList and view partner-enabled templates and their configuration.
partners:writeEnable and configure partner mode on templates you publish.
partner_onboarding:readView partner onboarding/compliance status and guidance.
partner_onboarding:writeComplete partner onboarding/compliance setup.
partner_instances:readRead a partner template's field map.
partner_instances:writeCreate instances via the partner API.
partner_service:readView the account-level partner service status.
partner_service:writeActivate and update partner service settings.

You can also restrict a token's reach to specific records (e.g. "only this template") using resource claims when creating it in Settings → Profile and Connections. See AI Integration for the full walkthrough, including the super_user wildcard scope.


Pagination

List endpoints return paginated results. Use these parameters:

Parameter Default Description
page 1 Which page of results to return
per_page 25 Results per page (max 100; value set entries max 250)

Every paginated response includes a meta object:

{
  "meta": {
    "total_count": 142,
    "page": 1,
    "per_page": 25,
    "total_pages": 6
  }
}

Form Templates

Templates define the structure of your forms — what fields exist, their names, and their order. You create a template once, then fill it many times by creating instances.

Required scope: form_templates:read for reads, form_templates:write for writes.

List your templates

GET /api/v1/form_templates

Filters:

Parameter Description
name Search by name (partial match, case-insensitive)
published true or false
archived true to include archived templates (hidden by default)
shared true to show only templates shared with you

Example:

GET /api/v1/form_templates?name=invoice&published=true&per_page=10

Get a template's details

GET /api/v1/form_templates/{id}

Returns the template's metadata and its blocks. Field-level detail lives at the /fields endpoint below.

Get a template's fields

GET /api/v1/form_templates/{id}/fields

Returns fields grouped by block. Field IDs are the keys you use when submitting instance values.

Create a template

POST /api/v1/form_templates

Field Required Description
form_template[name] Yes Template name
form_template[description] No Description
form_template[tag] No Tag for organization
form_template[published] No true or false
form_template[original_uploaded_image_file] No PDF or image file. Use multipart/form-data

Update a template

PATCH /api/v1/form_templates/{id}

Partial update. Accepts name, description, tag, published, notify_on_submission.

Update a template's fields

PATCH /api/v1/form_templates/{id}/fields

Sync field creation/updates/deletion in one transaction. Send a fields array of field operation objects.

Delete a template

DELETE /api/v1/form_templates/{id}

Archives the template (soft delete). Owner only. Returns 204 No Content on success.

Scan a template (text extraction)

POST /api/v1/form_templates/{id}/scan

Triggers field detection on the template's uploaded file. The scan runs in the background — check the template details to see when it finishes.

The template must have an uploaded file and must not have been scanned before (returns 409 Conflict if it has).

Scan to markdown

POST /api/v1/form_templates/{id}/scan_markdown

Extracts the document's full text content as structured markdown, page by page. Runs in the background.


Form Instances

Instances are filled-in copies of a template. When someone fills out a form, they create an instance. Each instance holds the actual values for that template's fields.

Required scope: form_instances:read for reads, form_instances:write for writes.

List instances

GET /api/v1/form_instances

Filters:

Parameter Description
template_id Show only instances of a specific template
status active (in progress), closed (submitted), or canceled
instance_type IMAGE-FILL, MANUAL, CHAT, or DIGITAL-TWIN
name Search by name (partial match)

Example:

GET /api/v1/form_instances?template_id=abc-123&status=active

Get an instance's details

GET /api/v1/form_instances/{id}

Returns the instance's metadata and full field values.

Create an instance

POST /api/v1/form_instances

Field Required Description
form_instance[form_template_id] Yes The template to create an instance from
form_instance[name] No A name for this instance
form_instance[values] No Initial field values (a JSON object keyed by field ID)
form_instance[original_uploaded_image_file] No Image to scan for auto-fill later (use multipart/form-data)

Fill in an instance

PATCH /api/v1/form_instances/{id}

Send the fields you want to update. Values are merged with existing values — you don't need to send all fields every time, only the ones you're changing.

{
  "form_instance": {
    "values": {
      "field-id-1": "Jane Smith",
      "field-id-2": "2026-02-17"
    }
  }
}

Create many instances at once

POST /api/v1/form_instances/bulk_create

Create or upsert many instances under one template in a single all-or-nothing transaction:

{
  "form_template_id": "abc-123",
  "idempotency_key_fields": ["field-id-1"],
  "instances": [
    { "values": { "field-id-1": "INV-001", "field-id-2": "100.00" } },
    { "name": "Invoice 2", "values": { "field-id-1": "INV-002" } }
  ]
}

Response: { created, updated, skipped, total, instance_ids, unmatched_keys, errors }.

Use field names instead of IDs

Values can be keyed by field name, so you don't have to look up a single UUID first. Names and IDs can be mixed freely:

{
  "form_template_id": "abc-123",
  "idempotency_key_fields": ["Invoice number"],
  "instances": [
    { "values": { "Invoice number": "INV-001", "Amount": "100.00" } }
  ]
}

If two fields on the template share a name, that name is rejected rather than guessed at — use those fields' IDs. Any key matching no field at all is stored as given and listed back in unmatched_keys, so a typo shows up immediately instead of as a mysteriously empty field.

Send a CSV instead

For many rows — or a template with a lot of fields — send a CSV document instead of JSON. The column names are stated once in the header rather than beside every value, which is dramatically smaller. A column headed name sets the instance label:

{
  "form_template_id": "abc-123",
  "csv": "name,Invoice number,Amount\nInvoice 1,INV-001,100.00\nInvoice 2,INV-002,250.00"
}

Blank cells are left alone rather than written as empty, so a blank column won't erase a stored value when the row updates an existing instance. Send either csv or instances — not both.

Note on idempotency_key_fields: matching is scoped to the template, not to you specifically. If a template is shared with other people who also have write access, avoid reusing the same key values across different callers of that shared template — a match could update a record someone else created.

Submit an instance

POST /api/v1/form_instances/{id}/submit

Closes the instance — status becomes closed and it becomes read-only. You can optionally include final field values to save before closing: { "fields": { "field-id-1": "..." } }.

Scan an instance (auto-fill via text extraction)

POST /api/v1/form_instances/{id}/scan

Triggers text extraction on the instance's attached image (or its template's image) and automatically fills in matching fields. Runs in the background.

Bulk scan instances

POST /api/v1/form_instances/bulk_scan

Scan up to 20 of your own instances at once. Send an array of instance IDs:

{ "instance_ids": ["id-1", "id-2", "id-3"] }

Delete an instance

DELETE /api/v1/form_instances/{id}

Archives the instance (soft delete). Returns 204 No Content.


Notebooks

Notebooks are the quickest way to capture data from a single image — take a photo, and Nocarta reads the text automatically. Each notebook holds one image and its extracted data.

Required scope: form_templates:read for reads, notebooks:write for writes.

List notebooks

GET /api/v1/notebooks

Filters:

Parameter Description
name Search by name (partial match)
archived true to include archived notebooks
tag Filter by tag
image_name Search by original file name

Get a notebook

GET /api/v1/notebooks/{id}

Returns the notebook's details. Use /fields below for field values.

Create a notebook

POST /api/v1/notebooks

Upload an image and Nocarta will automatically scan it for text. Use multipart/form-data:

Field Required Description
notebook[name] Yes Notebook name
notebook[original_uploaded_image_file] No Image file
scan No false to skip automatic text extraction (default: true, scans automatically)

Update a notebook

PATCH /api/v1/notebooks/{id}

Accepts name, description, tag, settings.

Get fields and values

GET /api/v1/notebooks/{id}/fields

Returns all extracted fields, their current values, and formatting settings.

Save field values

PATCH /api/v1/notebooks/{id}/save_values

Values are merged into the notebook's primary instance, same as form instances.

{
  "values": {
    "field-id-1": "John Doe",
    "field-id-2": "2026-02-17"
  }
}

Get extracted text

GET /api/v1/notebooks/{id}/text_content

Returns the full extracted text content from the notebook's fields.

Re-scan a notebook

POST /api/v1/notebooks/{id}/scan

Triggers a new text extraction scan on the notebook's image. Requires an attached image. Runs in the background.

Delete a notebook

DELETE /api/v1/notebooks/{id}

Archives the notebook (soft delete). Owner only.


Value Sets

Value sets are reference lists — think country codes, product categories, department names, or any structured list you want to reuse across forms. They support columns, search, CSV import/export, and optional hierarchy.

Required scope: value_sets:read or value_sets:read_one for reads, value_sets:write for writes.

List value sets

GET /api/v1/value_sets

Filters:

Parameter Description
name Search by name (partial match)
archived true to include archived sets

Get a value set

GET /api/v1/value_sets/{id}

Returns the full schema (column names, display column, value column) and settings.

Create a value set

POST /api/v1/value_sets

Field Required Description
value_set[name] Yes Set name
value_set[description] No Description
value_set[columns] No Comma-separated column names (e.g., name,code,region)
value_set[display_column] No Which column to show as the label
value_set[value_column] No Which column to store as the value
value_set[key_columns] No Comma-separated columns used to detect duplicate entries on import
value_set[hierarchical] No true to enable parent-child hierarchy
value_set[hierarchy_column] No Column that identifies each entry's parent (hierarchical sets only)

Update a value set

PATCH /api/v1/value_sets/{id}

List entries

GET /api/v1/value_sets/{id}/entries

Parameter Description
q Search within entries
parent_id Filter by parent entry (hierarchical sets only)
per_page Up to 250 entries per page

Import entries from CSV

POST /api/v1/value_sets/{id}/import_entries

Upload a CSV file (use multipart/form-data, field name file). The CSV columns should match the value set's column names. Response: { imported, skipped, linked, errors }.

Export entries as CSV

GET /api/v1/value_sets/{id}/export_entries

Downloads all entries as a CSV file. You can filter with q to export a subset.

Delete a value set

DELETE /api/v1/value_sets/{id}

Archives the value set (soft delete).


Automations

An automation is a script — written in NAL — that does work for you on a website, against an API, or in a Windows application. Over the API you can list automations, read and edit their scripts, check a script without saving it, start a run, and read what a run did.

Required scopes: automations:read for reads, automations:write for writes, and automations:run to start a run. Running is a separate permission from writing, because editing a script and making one act are different powers: a token that may rewrite an automation cannot also fire it unless you grant that too.

List your automations

GET /api/v1/automations

Summary rows, most recently updated first: id, name, description, mode and enabled.

Get an automation

GET /api/v1/automations/{id}

Everything in the summary plus the script itself, its allowed_hosts, and the inputs and outputs it declares. Read inputs before you start a run — those are the values the run expects.

Create an automation

POST /api/v1/automations

Field Required Description
automation[name] Yes Automation name
automation[mode] No desktop (the default), browser or windows. Decides which commands the script may use
automation[script] No The NAL source
automation[description] No Description
automation[allowed_hosts] No Array of hosts a browser automation may contact — exact (api.example.com) or wildcard (*.example.com). An empty list reaches nothing at all
automation[inputs] No Array of {name, label, type, required, default, help}. Names are lower_snake_case; the script reads them as input["name"]
automation[outputs] No Array of {name, type, description} — what the script promises to emit with output
automation[enabled] No true or false

The response carries a check alongside the automation: every write parses the script, so a syntax error, or a command the chosen mode does not offer, comes back in the same reply with line numbers.

Update an automation

PATCH /api/v1/automations/{id}

Partial update, same fields. The response carries check too.

Delete an automation

DELETE /api/v1/automations/{id}

Permanent, and it deletes the automation's runs with it. Returns 204 No Content.

Check a script without saving it

POST /api/v1/automations/check

Body: { "script": "…", "mode": "desktop" }. Saves nothing. Returns { "check": { "ok": …, "errors": [], "violations": [] } } — an error is a parse problem with a line, a message and often a hint; a violation names a command the chosen mode does not offer. Write, check, fix the lines it names, then save.

Start a run

POST /api/v1/automations/{automation_id}/runs

Requires automations:run. Body: { "input": { … } }, with a value for each input the automation declares.

The run does not happen inside this request. Automations drive real applications and real websites, so they execute on a desktop, never on Nocarta's servers. The reply is 202 Accepted with the run — created and queued, not finished:

{
  "run": { "id": "…", "status": "dispatched", "trigger": "api", … },
  "delivered": true,
  "target": "attended"
}

delivered: false is not a failure — it means a desktop is still coming up and the run is waiting for it. target says where it went: attended is your own connected desktop app, runner a Nocarta Runner, machine the one machine your account sends runs to. Where an automation run goes follows your account's choice — see Remote execution.

Either way, poll GET /api/v1/automations/{automation_id}/runs/{run_id} until status is completed, failed or cancelled. A run usually begins within seconds when your desktop app is already connected, and within a couple of minutes when one has to be started for you. A run nothing ever picks up is failed after 20 minutes, so polling always ends.

Plan for one difference from running an automation yourself: nobody is at the screen, so ask fails the run instead of waiting for an answer.

If the run could never have executed, you are told now rather than left polling:

Status error What to do
403 signing_blocked The automation is signed and its signature no longer verifies. Sign it again in the app
403 remote_dispatch_not_authorized Turn on desktop execution and browser dispatch under Settings → Remote Dispatch
422 vault_unlock_required The automation uses vault credentials, which only you can unlock, at a desktop. Run it from the desktop app instead
422 input_invalid An input is missing or the wrong type — errors names which
409 already_running One automation at a time per account. Wait for the current run or batch to finish
409 target_offline Your account sends runs to one machine and it is not connected; the message names it. Open the desktop app there, or choose another place to run

List an automation's runs

GET /api/v1/automations/{automation_id}/runs

Newest first. limit defaults to 20, maximum 100.

Read one run

GET /api/v1/automations/{automation_id}/runs/{run_id}

{
  "run": {
    "id": "…", "automation_id": "…", "status": "completed",
    "trigger": "api", "started_at": "…", "finished_at": "…", "duration_ms": 1500,
    "inputs": { "customer_ref": "AB-1024" },
    "output": { "invoice_number": "2026-0041" },
    "error": null,
    "log": ["[10:00:00] Opening the portal", "…"],
    "log_truncated": false,
    "steps": { "total": 9, "failed": 0 },
    "screenshots_uploaded": 2
  }
}
  • output — what the script emitted with output. It is null when the script emitted nothing, and it is recorded even for a run that stopped part-way, so you can see what it produced before it stopped.
  • error — null unless the run failed. source_line is the line of the script it stopped on, the same line number the check endpoint reports, and stuck_context carries the detail when the script stopped itself with stuck.
  • log — the last 200 lines the script wrote with log; log_truncated says whether earlier lines were dropped. Lines arrive while the run is still going, so polling shows progress, not just the ending.
  • screenshots_uploaded — how many screenshots the run saved. The images themselves are viewed in the app.

Scripts

A script is a small Node or Shell program that works on your data — converting files, generating reports, calling other APIs (see the Scripts Guide). Over the API you can list scripts, read and edit their source, check a script without saving it, start a run, read what a run did, stop it, and delete it once finished.

Required scopes: system_scripts:read for reads (scripts, their source, runs and their outputs), system_scripts:write for writes (create, edit — every edit is a new version — archive, and delete finished runs), and system_scripts:run to start and stop runs. As with automations, running is a separate permission from writing.

List your scripts

GET /api/v1/system_scripts

Get a script

GET /api/v1/system_scripts/{id}

Everything about the script, including its source, its kind and the parameters it declares. Read the parameters before you start a run — those are the values the run expects.

Create a script

POST /api/v1/system_scripts

The same information as the script form: name, description, kind (Node or Shell), run command and source filename for Shell, source code, parameters, the time limits for remote runs, and remote_target — where the script runs when started remotely: account (the default — follow your account's choice), nocarta_runner, or machine:<id> for one of your machines (GET /api/v1/remote_execution lists them). The response carries a check: every write parses the source with the kind's own checker, so a syntax error comes back in the same reply.

Update a script

PATCH /api/v1/system_scripts/{id}

Partial update, same fields. A change to the source creates a new version; past runs keep pointing at the version they used.

Delete a script

DELETE /api/v1/system_scripts/{id}

Check a script without saving it

POST /api/v1/system_scripts/check

Parses the source with the kind's own checker and saves nothing. Write, check, fix the lines it names, then save.

Start a run

POST /api/v1/system_scripts/{script_id}/runs

Requires system_scripts:run. Send a value for each parameter the script declares. A file parameter is sent as a file upload with the request — this endpoint is the only way to supply file inputs; the MCP tool run_script takes text parameters only.

Optionally send target to choose where this one run goes: nocarta_runner, or machine:<id> for one of your machines. Without it, the script's remote_target decides, and when that is account, your account's choice does.

The run does not happen inside this request. Like an automation, a script executes on a desktop, never on Nocarta's servers: on your own desktop app or one of your machines, or on a Nocarta Runner that Nocarta starts for you. The reply is 202 Accepted with the run, delivered and target, exactly as for an automation run — attended is your own connected desktop app, runner a Nocarta Runner, machine the machine the run was aimed at. Poll GET /api/v1/system_scripts/{script_id}/runs/{run_id} until status is completed, failed or cancelled.

Two things to plan for. Runs started this way are remote runs, so the script's time limits apply (5 minutes by default; up to 30 minutes on a Nocarta Runner, any value or no limit on your own machine). And secrets decide where a script can run: Tier 1 secrets work everywhere; Tier 2 (master-password) secrets mean your own desktop app, open and with the vault unlocked. A Nocarta Runner has only bash, python3 and curl for Shell scripts; Node scripts and npm packages are fully supported.

If the run could never have executed, you are told now rather than left polling:

Status error What to do
403 remote_dispatch_not_authorized Turn on desktop execution and browser dispatch under Settings → Remote Dispatch
422 vault_unlock_required The script uses master-password (Tier 2) secrets and would land on a Nocarta Runner, which cannot unlock them. Open the desktop app on your machine with the vault unlocked, or bind Tier 1 secrets instead
422 input_invalid A parameter is missing or the wrong type — errors names which
409 already_running One run at a time per account, scripts and automations alike. Wait for the current one to finish
409 target_offline The run is aimed at one of your machines and it is not connected; the message names it. Nocarta cannot start your machine, so it does not wait. Open the desktop app there, or send the run elsewhere
422 target_invalid target is not account, nocarta_runner or one of your machines

List a script's runs

GET /api/v1/system_scripts/{script_id}/runs

Read one run

GET /api/v1/system_scripts/{script_id}/runs/{run_id}

Status, where it ran, how it was started, exit code, the time limit that applied, the logs when the script keeps them, and its output files. Each output file carries a download_url.

Download an output file

GET /api/v1/system_scripts/{script_id}/runs/{run_id}/outputs/{output_id}

Requires system_scripts:read. Returns the file itself, with the same token you used to read the run — this is the address in the output's download_url.

Stop a run

POST /api/v1/system_scripts/{script_id}/runs/{run_id}/stop

Requires system_scripts:run. The machine running the script sends it a stop signal and force-kills it a few seconds later if needed; the run is marked cancelled.

Delete a finished run

DELETE /api/v1/system_scripts/{script_id}/runs/{run_id}

Requires system_scripts:write. Removes the run with its logs and output files. Stop a run before deleting it.


Remote execution

Where your remote runs go, your machines, and the one Nocarta Runner your account may have. All of it is account-level: there is no record id to restrict a token to. It is the same setting as Settings → Profile and Connections → Where my automations and scripts run.

Where would a run land?

GET /api/v1/remote_execution

Requires remote_execution:read. Returns:

  • situation — desktop (your desktop app will take the run), runner (a Nocarta Runner is up and will take it), none (a run would start a Nocarta Runner and wait for it), machine_offline (your account sends runs to one machine and it is not connected — runs are refused) or off (desktop execution and browser dispatch are not both on in Settings).
  • target — where your account sends runs: automatic, nocarta_runner, or one machine, with its name and whether it is connected.
  • holder — what is taking runs right now, or null.
  • machines — the computers you run the desktop app on: id, name, online, service_mode, last_seen_at, and target_value — the value to send to choose it.
  • The two remote-execution settings, and the Nocarta Runner that is up for your account, if any.

Choose where your runs go

PATCH /api/v1/remote_execution

Requires remote_execution:write. Body: { "execution_target": "…" } with one of:

  • automatic — your connected desktop app takes runs; when none is connected, Nocarta starts a Nocarta Runner.
  • nocarta_runner — always a Nocarta Runner, even while the desktop app is open.
  • machine:<id> — only that machine (its target_value). While it is not connected, runs are refused with target_offline instead of waiting.

Returns the same body as the GET. Any other value is 422 target_invalid. This applies to automations and scripts; a script can still have its own remote_target, and a single script run its own target. The two remote-execution settings themselves can only be changed in Settings.

Rename a machine

PATCH /api/v1/remote_execution/machines/{id}

Requires remote_execution:write. Body: { "name": "build-vm" } (up to 80 characters). Returns the machine.

Forget a machine

DELETE /api/v1/remote_execution/machines/{id}

Requires remote_execution:write. The machine is no longer offered as a place to run. If your account sent runs there, it goes back to automatic; scripts set to run there go back to account. If the desktop app connects from that machine again, it is listed again. Returns the same body as the GET.

Shut down the Nocarta Runner

POST /api/v1/remote_execution/shut_down_runner

Requires remote_execution:write. Shuts down your Nocarta Runner, running or idle. The next remote run starts a new one.


Search

Search everything you own

GET /api/v1/search?q=rental+contract

Requires search:read. Searches by meaning as well as by words, so a query finds a document about the topic even when it shares no words with it — and it works across languages.

ParameterMeaning
qThe query. Two characters minimum.
scopesdocuments, value_sets, or both (comma-separated). Defaults to both.
limitMaximum hits. Defaults to 10.

Every hit carries method: "semantic" when it was matched by meaning, "lexical" when matched by words. score is that hit's native score and is not comparable between the two methods — use the returned order, not the number.

Search never fails to an error. When meaning-search is unavailable — switched off, nothing indexed yet, or the backend is temporarily down — results fall back to word matching and every hit comes back as "lexical". You always get an answer; check method if you need to know which kind.

Why a search might return nothing

Meaning-search is opt-in per record type. Notebooks and automations are indexed by default; form templates are off until switched on per template, and value sets are off until switched on per set. Read the current state with GET /api/v1/indexing (indexing:read) before concluding the content does not exist.


Content warnings on write

Endpoints that store user content may return a warnings array alongside the normal response:

{
  "instance": { "id": "…", "name": "Invoice 4471" },
  "warnings": ["Saved. Heads up: this content contains instructions that look aimed at an AI assistant. …"]
}

It means the text you stored reads like an instruction to an AI rather than ordinary data. The write succeeded — nothing is rejected or altered. Show it to the user rather than dropping it: content written through the API is exactly the case where no human read it on the way in.

The key is absent when there is nothing to report. Returned by POST/PATCH /api/v1/form_instances and POST/PATCH /desktop/automations.


Error Handling

The API uses standard HTTP status codes:

Code Meaning What to do
200 Success Request completed
201 Created Resource was created successfully
202 Accepted Background job started (e.g., text extraction scan). Check back later for results
204 No Content Delete succeeded (no response body)
400 Bad Request Missing or malformed parameters
401 Unauthorized Token missing, invalid, revoked, or expired
403 Forbidden Your token doesn't have the required scope, or its resource claims exclude this record
404 Not Found Resource doesn't exist or you don't have access
409 Conflict Action conflicts with current state (e.g., scanning an already-scanned template)
422 Validation Error Check the validation_errors array in the response for details
429 Rate Limited Too many requests. Wait and try again

Error responses include a message explaining what went wrong:

{ "error": "Not Found", "message": "Resource not found" }

{ "error": "Unprocessable Entity", "message": "Name can't be blank", "validation_errors": ["Name can't be blank"] }

Rate Limits

The API allows 60 requests per minute, keyed by your token (or by IP if unauthenticated). When you hit the limit, you'll receive a 429 response — wait a minute and retry.


Background Jobs

Some operations (text extraction scans, markdown scans) run in the background because they take a few seconds to complete. These endpoints return 202 Accepted immediately.

To check if a background job has finished:

  1. Request the resource again (e.g., GET /api/v1/form_templates/{id})
  2. Check for new fields, updated values, or changed status

Permissions

  • You can only access your own documents and documents that have been shared with you
  • Shared documents respect the permissions set by the owner (read-only, read-write, etc.)
  • The API never exposes other users' data
  • The API never exposes secrets or credentials from your vault

Quick Reference

Action Method Endpoint
Templates
List templates GET /api/v1/form_templates
Get template GET /api/v1/form_templates/{id}
Get fields GET /api/v1/form_templates/{id}/fields
Create template POST /api/v1/form_templates
Update template PATCH /api/v1/form_templates/{id}
Update fields PATCH /api/v1/form_templates/{id}/fields
Delete template DELETE /api/v1/form_templates/{id}
Scan (text extraction) POST /api/v1/form_templates/{id}/scan
Scan to markdown POST /api/v1/form_templates/{id}/scan_markdown
Instances
List instances GET /api/v1/form_instances
Get instance GET /api/v1/form_instances/{id}
Create instance POST /api/v1/form_instances
Bulk create POST /api/v1/form_instances/bulk_create
Fill in fields PATCH /api/v1/form_instances/{id}
Submit POST /api/v1/form_instances/{id}/submit
Scan instance POST /api/v1/form_instances/{id}/scan
Bulk scan POST /api/v1/form_instances/bulk_scan
Delete instance DELETE /api/v1/form_instances/{id}
Notebooks
List notebooks GET /api/v1/notebooks
Get notebook GET /api/v1/notebooks/{id}
Create notebook POST /api/v1/notebooks
Get fields GET /api/v1/notebooks/{id}/fields
Save values PATCH /api/v1/notebooks/{id}/save_values
Get text GET /api/v1/notebooks/{id}/text_content
Re-scan POST /api/v1/notebooks/{id}/scan
Delete notebook DELETE /api/v1/notebooks/{id}
Value Sets
List value sets GET /api/v1/value_sets
Get value set GET /api/v1/value_sets/{id}
Create value set POST /api/v1/value_sets
List entries GET /api/v1/value_sets/{id}/entries
Import CSV POST /api/v1/value_sets/{id}/import_entries
Export CSV GET /api/v1/value_sets/{id}/export_entries
Delete value set DELETE /api/v1/value_sets/{id}

Next Steps

Need More Help?

Ask AI Assistant