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.
| Scope | What it grants |
|---|---|
user:read | Read your account profile, capabilities, and usage stats. |
form_templates:read | List and inspect form templates, notebooks, and fields. |
form_templates:write | Create, edit, scan, and delete form templates. |
form_instances:read | List and view filled form instances and field values. |
form_instances:write | Create, edit, scan, submit, and bulk-operate on form instances. |
notebooks:write | Create, edit, scan, and save values on notebooks (reads use form_templates:read). |
media_notes:read | List and view your recordings, their transcripts, and processing status. |
media_notes:write | Upload, delete, and restore recordings. |
value_sets:read | List and view value sets, entries, and export. |
value_sets:read_one | View a single value set and its entries/export (no listing). |
value_sets:write | Create, edit, delete value sets, and import entries. |
automations:read | List and inspect automations (name and description). |
search:read | Search everything you own by meaning — documents and value-set entries. |
indexing:read | Read which of your content is searchable by meaning. |
indexing:write | Turn meaning-search indexing on or off. |
partners:read | List and view partner-enabled templates and their configuration. |
partners:write | Enable and configure partner mode on templates you publish. |
partner_onboarding:read | View partner onboarding/compliance status and guidance. |
partner_onboarding:write | Complete partner onboarding/compliance setup. |
partner_instances:read | Read a partner template's field map. |
partner_instances:write | Create instances via the partner API. |
partner_service:read | View the account-level partner service status. |
partner_service:write | Activate 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.
| Parameter | Meaning |
|---|---|
q | The query. Two characters minimum. |
scopes | documents, value_sets, or both (comma-separated). Defaults to both. |
limit | Maximum 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:
- Request the resource again (e.g.,
GET /api/v1/form_templates/{id}) - 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
- AI Integration — Connect an AI assistant using the same API (REST or MCP), including scopes and resource claims
- Forms Guide — Learn about templates and instances
- Notebook Guide — Quick document capture
- Value Sets Guide — Reference lists for forms
Need More Help?