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 SettingsProfile 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 (name and description).
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 decrypted 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).


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