API Integration
How the API works
The Tavnit REST API lets your own code do what you do in the app: send documents to a flow, collection, splitter or pipeline, run cleaners, matchers, inspectors, fillers, signals and agents, read and write Buckets, manage Subject cases, and approve or reject human reviews.
- Base URL:
https://run.tavnit.io/api. Every path below is relative to it. - Every request carries your API key in the
X-API-Keyheader. - Processing is asynchronous. Endpoints that start work answer 202 Accepted with an id right away and the work runs in the background. You get the result by polling (runs, agent runs, Catches, cases) or on a webhook configured on the feature.
Pipelines, Subjects, Matchers, Inspectors, Fillers, Signals and Nets are in Beta. Their endpoints work today, but details can still change.
Authentication and IDs
API key
Open Integrations in the app sidebar. The API Key card shows your key (reveal and copy it there). Each member has their own key in each organization, and a request acts as that member in that organization.
Keep your API key secret. Regenerate on the same card invalidates the current key immediately, and anything still using it stops working.
A request without a valid key gets:
{
"success": false,
"error": "Authentication required",
"message": "Please provide a valid X-API-Key header"
}Roles
The key carries your role. Members with the HITL Only role can only use the human-review approve and reject endpoints and the API key endpoints. Every processing and data endpoint refuses them with 403 and Your role only permits HITL reviews. See user roles.
IDs
Each flow, collection, splitter, cleaner, bucket, agent, matcher, inspector, filler and pipeline shows its ID on its detail page, with a copy button. Resources must belong to the organization of your key: another organization's ID answers 403 or 404.
Sending files
Endpoints that take a document accept it in one of two ways:
Send the file as multipart/form-data in a field named file, with the other fields as form fields. Works on every file endpoint.
Send JSON with file_base64 (the file content) and filename (with its extension), plus the other fields. Useful when an automation tool gives you base64 instead of a file.
| Endpoint | Base64 | Files accepted |
|---|---|---|
| POST /runs/process | Yes | PDF, images (PNG, JPG, JPEG, JFIF, TIF, TIFF, WEBP, BMP, GIF), spreadsheets (XLSX, XLS, CSV) |
| POST /collections/process | Yes | Same as runs |
| POST /splits/run | Yes | Same as runs |
| POST /pipelines/{id}/execute | Yes | PDF and images |
| POST /signals/{id}/run | Yes | Audio: MP3, MP4, MPEG, MPGA, M4A, WAV, WEBM |
| POST /sweeps/run | No | Spreadsheets: CSV, XLSX, XLS |
| Inspectors, fillers, subjects and cases | No | The document as a multipart file |
A single request can be up to 150 MB. The file type is taken from the filename's extension.
Responses and errors
Responses are JSON with a success flag. Errors carry an error string, sometimes a message, and extra keys that explain the problem:
{
"success": false,
"error": "Bucket mismatch",
"message": "bucket_name does not match the target bucket. Verify both bucket_id and bucket_name before retrying."
}| Status | Meaning |
|---|---|
| 200 / 201 | Done (reads, cancels, approvals) / created (cases, fills, inspections). |
| 202 | Accepted: the work is queued. Keep the returned id. |
| 400 | A required field is missing or invalid, the file type is not supported, or the resource is inactive or not set up. |
| 401 | Missing or invalid X-API-Key. |
| 402 | Your organization can't start new work right now; contact the Tavnit team. |
| 403 | The resource belongs to another organization, your role is HITL Only, or you are not a reviewer of that item. |
| 404 | The ID does not exist in your organization. |
| 409 | Conflict with the current state: bucket name mismatch, already cancelled or finished, case closed, slot already filled, an agent name shared by several agents. |
| 429 | Too many runs already waiting for an agent. |
| 500 | Unexpected error. Safe to retry later. |
Endpoint reference
| Feature | Method and path | What it does |
|---|---|---|
| Flows | POST /runs/process | Extract a document with a flow |
| Flows | GET /runs/{run_id} | Run status and extracted data |
| Flows | GET /runs/{run_id}/source-file | The original document |
| Collections | POST /collections/process | AI routes a document to the right flow or splitter |
| Collections | GET /collection-runs/{id}/source-file | The original document |
| Splitters | POST /splits/run | Split a multi-document file |
| Splitters | GET /splits/{split_id}/source-file | The original bundle |
| Cleaners | POST /sweeps/run | Sweep a spreadsheet |
| Cleaners | POST /cleaners/{cleaner_id}/process | Sweep rows sent as JSON |
| Buckets | POST /buckets/write | Append or replace rows |
| Buckets | GET /buckets/read | Read rows, paginated |
| Agents | POST /bots/{agent_id}/runs | Start an agent run |
| Agents | POST /bots/runs | Start an agent run by bot_id or bot_name in the body |
| Agents | GET /bot-runs/{bot_run_id} | Poll one agent run |
| Agents | GET /bots/{agent_id}/runs | List an agent's runs |
| Agents | POST /bot-runs/{bot_run_id}/cancel | Cancel an agent run |
| Agents | POST /bot-runs/{bot_run_id}/human/resolve | Tell a waiting agent the person is done |
| Agents | POST /bot-runs/{bot_run_id}/resume | Resume a run paused for a person |
| Matchers | POST /matchers/{matcher_id}/run | Match completed runs |
| Inspectors | POST /inspectors/{inspector_id}/process | Add a document to an inspection |
| Fillers | POST /fillers/{filler_id}/fills | Open a fill and add documents |
| Pipelines | POST /pipelines/{pipeline_id}/execute | Execute a pipeline |
| Signals | POST /signals/{signal_id}/run | Structure an audio file (a Wave) |
| Subjects | POST /subjects/{subject_id}/process | Route a document to a case |
| Nets | POST /nets/{net_id}/catch | Start a Catch |
| Human review | POST /runs/{run_id}/hitl/approve | Approve or reject paused items |
| API key | GET /me/api-key | Read or regenerate your key |
Each feature is detailed below, with its other endpoints.
Flows: process a document
Send one document to a flow. Tavnit creates a run and extracts it in the background.
| Field | Required | Description |
|---|---|---|
| flow_id | Yes | The flow that extracts the document. |
| file | Yes* | The document (multipart). |
| file_base64 + filename | Yes* | Instead of file: base64 content and the filename with its extension. |
| content_type | No | MIME type for a base64 file. |
| source | No | Label stored on the run: api (default), email, manual_upload or collection. |
import requests
API_KEY = "YOUR_API_KEY"
FLOW_ID = "YOUR_FLOW_ID"
# ─────────────────────────────────────────────────────────────
# Option 1: Multipart file upload (binary)
# ─────────────────────────────────────────────────────────────
with open("document.pdf", "rb") as file:
response = requests.post(
"https://run.tavnit.io/api/runs/process",
headers={"X-API-Key": API_KEY},
data={
"flow_id": FLOW_ID,
"source": "api"
},
files={"file": file}
)
print(response.json())
# ─────────────────────────────────────────────────────────────
# Option 2: Base64-encoded file (JSON body)
# ─────────────────────────────────────────────────────────────
import base64
with open("document.pdf", "rb") as file:
file_base64 = base64.b64encode(file.read()).decode("utf-8")
response = requests.post(
"https://run.tavnit.io/api/runs/process",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
json={
"flow_id": FLOW_ID,
"source": "api",
"filename": "document.pdf",
"file_base64": file_base64
}
)
print(response.json()){
"success": true,
"run_id": "8f1c2b7e-4d3a-4a91-9c11-2f7b6e0d5a44",
"status": "queued",
"auto_created_run": true,
"message": "Run accepted for background processing. Poll the run for status or await the webhook."
}Errors: 400 (no file, no flow_id, unsupported or invalid file), 403 (flow of another organization), 404 (flow not found). A rejected request still creates a failed run when it can, and returns its run_id so you can find it in Runs.
Get the run and its data
Poll until status is terminal. Statuses: queued, running, awaiting_approval (paused for human review), completed, failed and cancelled. data holds the extracted rows and stays null until the run is completed, including while it waits for approval. When columns is set, use it for the column order. attempt and previous_attempts show automatic retries. Add ?include_document=true to also get a signed link to the original file. This works for every run, including runs created by collections, splitters, pipelines and email.
{
"success": true,
"run_id": "8f1c2b7e-4d3a-4a91-9c11-2f7b6e0d5a44",
"status": "completed",
"flow_id": "3a9d51c0-77b2-4e18-9f6d-0c4a1b8e2d63",
"flow_name": "Supplier Invoices",
"source": "api",
"original_filename": "invoice.pdf",
"mime_type": "application/pdf",
"byte_size": 182734,
"pages_detected": 3,
"pages_processed": 3,
"created_at": "2026-09-01T14:02:11Z",
"started_at": "2026-09-01T14:02:13Z",
"finished_at": "2026-09-01T14:02:41Z",
"attempt": 1,
"previous_attempts": [],
"error_message": null,
"data": [
{ "Invoice Number": "001", "Vendor": "Acme Corp", "Total": 270.30 }
],
"columns": null
}import time
import requests
API_KEY = "YOUR_API_KEY"
HEADERS = {"X-API-Key": API_KEY}
with open("invoice.pdf", "rb") as f:
submit = requests.post(
"https://run.tavnit.io/api/runs/process",
headers=HEADERS,
data={"flow_id": "YOUR_FLOW_ID"},
files={"file": f},
)
run_id = submit.json()["run_id"]
while True:
run = requests.get(f"https://run.tavnit.io/api/runs/{run_id}", headers=HEADERS).json()
if run["status"] in ("completed", "failed", "cancelled"):
break
time.sleep(5)
print(run["status"], run["data"])Get the original document
Returns a signed URL to the file as uploaded, whatever the run's status. Optional expires_in sets the link lifetime in seconds (60 to 604800, default 7 days). With ?download=true the response is the file itself.
{
"success": true,
"kind": "run",
"run_id": "8f1c2b7e-4d3a-4a91-9c11-2f7b6e0d5a44",
"original_filename": "invoice.pdf",
"mime_type": "application/pdf",
"byte_size": 182734,
"bucket": "files",
"path": "<org_id>/<run_id>/invoice.pdf",
"url": "https://...signed-url...",
"expires_in": 604800
}Collections: let AI route the document
Send a document without choosing the flow: AI picks the best flow or splitter of the collection. Use it when you receive mixed document types.
Same body as /runs/process, with collection_id instead of flow_id (or put the ID in the path: /collections/{collection_id}/process). The collection must be active and have at least one active flow or splitter, otherwise the call answers 400.
import requests
API_KEY = "YOUR_API_KEY"
COLLECTION_ID = "YOUR_COLLECTION_ID"
# ─────────────────────────────────────────────────────────────
# Option 1: Multipart file upload (binary)
# ─────────────────────────────────────────────────────────────
with open("document.pdf", "rb") as file:
response = requests.post(
"https://run.tavnit.io/api/collections/process",
headers={"X-API-Key": API_KEY},
data={
"collection_id": COLLECTION_ID,
"source": "api"
},
files={"file": file}
)
print(response.json())
# ─────────────────────────────────────────────────────────────
# Option 2: Base64-encoded file (JSON body)
# ─────────────────────────────────────────────────────────────
import base64
with open("document.pdf", "rb") as file:
file_base64 = base64.b64encode(file.read()).decode("utf-8")
response = requests.post(
"https://run.tavnit.io/api/collections/process",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
json={
"collection_id": COLLECTION_ID,
"source": "api",
"filename": "document.pdf",
"file_base64": file_base64
}
)
print(response.json()){
"success": true,
"data": {
"collection_run_id": "b21e9f34-8c55-4d70-a6e2-91f0c7d43a18",
"status": "queued"
}
}The chosen flow creates a normal run, and its webhook payload includes collection_run_id so you can match the result to your request. The original is available right away at GET /collection-runs/{collection_run_id}/source-file (same options as the run source file). See Collections.
Splitters: split a multi-document file
Send a file that contains several documents. The splitter finds where each one starts and ends, classifies it, and sends each part where its document type says.
Same body as /runs/process, with splitter_id.
import requests
API_KEY = "YOUR_API_KEY"
SPLITTER_ID = "YOUR_SPLITTER_ID"
# ─────────────────────────────────────────────────────────────
# Option 1: Multipart file upload (binary)
# ─────────────────────────────────────────────────────────────
with open("document.pdf", "rb") as file:
response = requests.post(
"https://run.tavnit.io/api/splits/run",
headers={"X-API-Key": API_KEY},
data={
"splitter_id": SPLITTER_ID,
"source": "api"
},
files={"file": file}
)
print(response.json())
# ─────────────────────────────────────────────────────────────
# Option 2: Base64-encoded file (JSON body)
# ─────────────────────────────────────────────────────────────
import base64
with open("document.pdf", "rb") as file:
file_base64 = base64.b64encode(file.read()).decode("utf-8")
response = requests.post(
"https://run.tavnit.io/api/splits/run",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
json={
"splitter_id": SPLITTER_ID,
"source": "api",
"filename": "document.pdf",
"file_base64": file_base64
}
)
print(response.json()){
"success": true,
"split_id": "c74a0b12-3e69-4f85-b0d7-58e2a9c61f70",
"status": "pending",
"pages_count": 12,
"message": "Split queued for processing."
}Parts sent to flows become runs of their own, and their webhooks carry split_id. The original bundle is at GET /splits/{split_id}/source-file. See Splitters.
Cleaners: run a sweep
A sweep runs a cleaner over rows of data to normalize, classify, look up or enrich values. Send the rows as a spreadsheet or as JSON.
| Field | Required | Description |
|---|---|---|
| cleaner_id | Yes | The cleaner to run. |
| file | Yes | CSV, XLSX or XLS, multipart only. Headers on the first row; every base field the cleaner requires must be a column, without duplicates. |
| sheet_name | No | Which sheet of a workbook to read (default: the first). |
Send JSON with rows (an array of objects), or a multipart file as above.
{
"rows": [
{ "Vendor": "acme corp.", "Country": "usa" },
{ "Vendor": "GLOBEX INC", "Country": "Mexico" }
]
}import requests
API_KEY = "YOUR_API_KEY"
CLEANER_ID = "YOUR_CLEANER_ID"
# ─────────────────────────────────────────────────────────────
# Option 1: Sweep a spreadsheet (CSV, XLSX or XLS, multipart only)
# Column headers must be on the first row.
# ─────────────────────────────────────────────────────────────
with open("vendors.csv", "rb") as f:
response = requests.post(
"https://run.tavnit.io/api/sweeps/run",
headers={"X-API-Key": API_KEY},
data={"cleaner_id": CLEANER_ID},
files={"file": ("vendors.csv", f, "text/csv")},
)
print(response.json()) # 202: {"sweep_id": "...", "status": "queued", ...}
# ─────────────────────────────────────────────────────────────
# Option 2: Send the rows as JSON
# ─────────────────────────────────────────────────────────────
response = requests.post(
f"https://run.tavnit.io/api/cleaners/{CLEANER_ID}/process",
headers={"X-API-Key": API_KEY},
json={
"rows": [
{"Vendor": "acme corp.", "Country": "usa"},
{"Vendor": "GLOBEX INC", "Country": "Mexico"}
]
},
)
print(response.json()){
"success": true,
"sweep_id": "5d2e8a61-0b4f-4c3e-9a7d-1f6b2c8e4a90",
"status": "queued",
"cleaner_id": "YOUR_CLEANER_ID",
"message": "Sweep queued for processing."
}A cleaner with an invalid setup answers 400 with validation_errors. See Cleaners.
Buckets: write and read rows
Push rows into a bucket from your own systems, or read what your flows stored there. Find the bucket ID and name by tapping the info icon on the bucket's page. The name is a safety check: if it does not match the ID, the call answers 409.
| Field | Required | Description |
|---|---|---|
| bucket_id | Yes | The bucket. |
| bucket_name | Yes | Its exact name. |
| overwrite | Yes | Boolean. false appends the rows; true replaces all existing rows. |
| rows | Yes | Array of objects, up to 50,000 per request. |
Send the body as application/json. Every row must have exactly the bucket's columns, by column name or display name: a missing or unknown column aborts the whole write with 400, listing missing_columns and extra_columns.
import requests
API_KEY = "YOUR_API_KEY"
BUCKET_ID = "YOUR_BUCKET_ID"
BUCKET_NAME = "YOUR_BUCKET_NAME"
# ─────────────────────────────────────────────────────────────
# Append rows to existing data (overwrite=False)
# ─────────────────────────────────────────────────────────────
response = requests.post(
"https://run.tavnit.io/api/buckets/write",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
json={
"bucket_id": BUCKET_ID,
"bucket_name": BUCKET_NAME,
"overwrite": False,
"rows": [
{"invoice_number": "INV-1001", "vendor": "Acme Corp", "amount": 1200.50},
{"invoice_number": "INV-1002", "vendor": "Globex", "amount": 430.00}
]
}
)
print(response.json())
# ─────────────────────────────────────────────────────────────
# Replace all rows (overwrite=True)
# ─────────────────────────────────────────────────────────────
response = requests.post(
"https://run.tavnit.io/api/buckets/write",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
json={
"bucket_id": BUCKET_ID,
"bucket_name": BUCKET_NAME,
"overwrite": True,
"rows": [
{"invoice_number": "INV-3001", "vendor": "NewCo", "amount": 400.00}
]
}
)
print(response.json()){
"success": true,
"message": "Bucket write queued for background processing.",
"bucket_id": "YOUR_BUCKET_ID",
"bucket_name": "Invoices 2026",
"bucket_write_id": "e3a1c9d2-7f40-4b8e-a5c6-2d9f1b0e7a34",
"overwrite": false,
"rows_queued": 2,
"status": "queued"
}Read rows
Query parameters: bucket_id and bucket_name (required), limit (1 to 1000, default 100) and offset (default 0). Page through with offset while has_more is true; total_count counts every row. Row data keys are the column names; use columns for display names. Stored file links come back as fresh signed URLs.
curl "https://run.tavnit.io/api/buckets/read?bucket_id=YOUR_BUCKET_ID&bucket_name=Invoices%202026&limit=100&offset=0" \
-H "X-API-Key: YOUR_API_KEY"{
"success": true,
"bucket_id": "YOUR_BUCKET_ID",
"bucket_name": "Invoices 2026",
"columns": [
{ "name": "invoice_number", "display_name": "Invoice #", "data_type": "text" },
{ "name": "amount", "display_name": "Amount", "data_type": "number" }
],
"rows": [
{
"id": "0c6f...",
"row_number": 1,
"source_type": "api",
"created_at": "2026-09-01T14:05:00Z",
"updated_at": "2026-09-01T14:05:00Z",
"data": { "invoice_number": "INV-1001", "amount": 1200.5 }
}
],
"total_count": 1234,
"limit": 100,
"offset": 0,
"has_more": true
}See Buckets.
Agents: trigger and poll runs
Start an agent from your code and collect what it captured. Agents are enabled per organization on request: contact support to turn them on.
Optional JSON {"inputs": {...}} overrides the agent's input variables for this run. Only variables the agent declares are used, and secret values are never accepted. Every call starts a new run.
curl -X POST "https://run.tavnit.io/api/bots/YOUR_AGENT_ID/runs" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"inputs": {"invoice_month": "2026-08"}}'
# 202
{ "success": true, "bot_run_id": "a7c3...", "status": "queued" }status is queued, or waiting when the agent limits its concurrent runs and every slot is taken (it starts by itself when its turn comes). 409 when the agent is archived, 429 when too many runs are already waiting.
Trigger by name
Name the agent in the body (or the query string) instead of the path: bot_id, or bot_name with the agent's name as shown in the app. The name match is exact, ignoring case and extra spaces, within your organization; archived namesakes are ignored. No match is 404; a name only an archived agent has is 409 (archived); a name several active agents share is 409 with their IDs in bot_ids. bot_id wins when both are sent. Renaming the agent changes what bot_name must say, so use bot_id where an integration must survive a rename.
curl -X POST "https://run.tavnit.io/api/bots/runs" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"bot_name": "Supplier prices", "inputs": {"invoice_month": "2026-08"}}'
# 409 when several active agents share the name
{ "success": false, "error": "2 agents are named \"Supplier prices\": use bot_id", "bot_ids": ["3f2a...", "9b10..."] }Poll, list and cancel
| Endpoint | What it does |
|---|---|
| GET /bot-runs/{bot_run_id} | One run: status (waiting, queued, running, awaiting_human, completed, failed, cancelled), timings, input (secrets removed) and output with signed file links. Also at GET /bots/{agent_id}/runs/{bot_run_id}. |
| GET /bots/{agent_id}/runs | Newest first, without input and output. Query: status (queued, running, completed, failed, cancelled), limit (1 to 100, default 20), offset. Returns runs and total. |
| POST /bot-runs/{bot_run_id}/cancel | Cancels a waiting, queued, running or paused (awaiting_human) run. 409 when the run already finished. Also at POST /bots/{agent_id}/runs/{bot_run_id}/cancel. |
{
"success": true,
"run": {
"id": "a7c3...",
"bot_id": "YOUR_AGENT_ID",
"status": "completed",
"source": "api",
"created_at": "2026-09-01T09:00:00Z",
"started_at": "2026-09-01T09:00:04Z",
"finished_at": "2026-09-01T09:03:10Z",
"duration_seconds": 186,
"llm_requests": 9,
"error_message": null,
"replay_url": "https://...",
"input": { "invoice_month": "2026-08" },
"output": { "...": "what the agent captured, file links signed" }
}
}Human help
An agent with human help turned on can stop and ask a person for something only a human can do in the browser, such as a captcha or an SMS code. While it waits, the run stays running and the poll carries human_request (reason, requested_at). If nobody helps in time the run pauses as awaiting_human: no browser and nothing billed while it waits, not terminal, resumable for 72 hours. attempt counts the browser sessions the run has used.
| Endpoint | What it does |
|---|---|
| POST /bot-runs/{bot_run_id}/human/resolve | The person is done: the agent continues in the same browser within a few seconds. Optional note (up to 500 characters) the agent reads. 200; 409 when the run is not waiting for a person, or is already paused (resume it instead). Also at POST /bots/{agent_id}/runs/{bot_run_id}/human/resolve. |
| POST /bot-runs/{bot_run_id}/resume | Continues a paused run in a new browser, on the page where it stopped and still signed in, with the same inputs. Optional note. 202 with status (queued, or waiting) and attempt; 402 like a new run; 409 when the run is not paused or has used 10 browser sessions. Also at POST /bots/{agent_id}/runs/{bot_run_id}/resume. |
# The agent is waiting for a person (status running, human_request present):
# after doing what it asked in the live session
curl -X POST "https://run.tavnit.io/api/bot-runs/YOUR_BOT_RUN_ID/human/resolve" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"note": "Typed the SMS code"}'
# Nobody came in time (status awaiting_human): resume the same run
curl -X POST "https://run.tavnit.io/api/bot-runs/YOUR_BOT_RUN_ID/resume" \
-H "X-API-Key: YOUR_API_KEY"
# 202
{ "success": true, "bot_run_id": "a7c3...", "status": "queued", "attempt": 2 }See Agents.
Matchers: match runs
Compare completed runs of the matcher's flow, for example invoices against purchase orders.
| Field | Required | Description |
|---|---|---|
| run_ids | Yes | Two or more completed runs of the matcher's flow. |
| benchmark_run_id | In benchmark mode | The run the others are compared against. |
curl -X POST "https://run.tavnit.io/api/matchers/YOUR_MATCHER_ID/run" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"run_ids": ["RUN_A", "RUN_B"], "benchmark_run_id": "RUN_A"}'
# 202
{
"success": true,
"match_id": "...",
"status": "queued",
"mode": "benchmark",
"run_ids": ["RUN_A", "RUN_B"]
}There is no API endpoint to read a match: get the result on the matcher's webhook or email output, or in the app. See Matchers.
Inspectors: check a set of documents
An inspection collects documents, routes each one into a slot of the inspector, extracts it and evaluates the checklist.
Multipart file. Without inspection_id a new inspection is created; pass the returned inspection_id to add more documents to it (it must still be collecting, 409 otherwise). The inspector must be active.
curl -X POST "https://run.tavnit.io/api/inspectors/YOUR_INSPECTOR_ID/process" \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@bill_of_lading.pdf"
# 202 — send the next files with -F "inspection_id=..."
{
"success": true,
"inspection_id": "...",
"inspection_file_id": "...",
"status": "queued"
}| Endpoint | What it does |
|---|---|
| POST /inspectors/{inspector_id}/inspections | Creates an empty inspection (201) to add documents to later. |
| POST /inspections/{inspection_id}/fire | Stops collecting and evaluates now. 400 with missing_inputs when a required slot has no document. |
The result arrives on the inspector's webhook or email output. See Inspectors.
Fillers: fill PDF forms
A fill gathers the documents a filler needs, extracts them and writes the values into the filler's PDF templates.
| Endpoint | What it does |
|---|---|
| POST /fillers/{filler_id}/fills | Opens a fill (201, returns fill_id). The filler needs a template and field mappings. |
| POST /fills/{fill_id}/route-upload | Multipart file; AI routes it into an open slot (202). |
| POST /fills/{fill_id}/inputs/{input_id}/upload | Multipart file for a specific slot; starts that slot's run (202). |
| POST /fills/{fill_id}/inputs/{input_id}/attach-run | JSON run_id: reuse a completed run for a slot (202). |
| POST /fills/{fill_id}/fire | Fills now instead of waiting for every slot. 400 with missing_inputs when a required slot is empty. |
| POST /fillers/{filler_id}/templates | Adds a template PDF (multipart file, optional name). Map its fields in the app. |
# 1. Open a fill (201)
curl -X POST "https://run.tavnit.io/api/fillers/YOUR_FILLER_ID/fills" \
-H "X-API-Key: YOUR_API_KEY"
# 2. Upload documents and let AI pick the slot (202)
curl -X POST "https://run.tavnit.io/api/fills/FILL_ID/route-upload" \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@passport.jpg"
# 3. Optional: fill now instead of waiting for every slot (202)
curl -X POST "https://run.tavnit.io/api/fills/FILL_ID/fire" \
-H "X-API-Key: YOUR_API_KEY"The filled PDFs arrive on the filler's webhook or email output. See Fillers.
Pipelines: execute
Start one execution of a pipeline with a document.
Multipart file or file_base64 + filename; PDF or image. 400 when the pipeline is inactive or its graph cannot run (with validation_errors).
curl -X POST "https://run.tavnit.io/api/pipelines/YOUR_PIPELINE_ID/execute" \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@invoice.pdf"
# 202
{
"success": true,
"execution_id": "...",
"pipeline_id": "YOUR_PIPELINE_ID",
"status": "running"
}Cancel with POST /pipelines/executions/{execution_id}/cancel (409 when it is not running). Results leave through the pipeline's Output nodes. See Pipelines.
Signals: structure an audio file
Send a recording and the signal turns it into structured data (a Wave).
Multipart file or base64. Audio up to 150 MB and 8 hours, with at most 2 hours of detected speech.
curl -X POST "https://run.tavnit.io/api/signals/YOUR_SIGNAL_ID/run" \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@sales_call.mp3"
# 202
{
"success": true,
"wave_id": "...",
"signal_id": "YOUR_SIGNAL_ID",
"status": "queued",
"audio_seconds": 754,
"auto_created_wave": true
}The result arrives on the signal's webhook or in the app. See Signals.
Subjects and cases
File documents into the cases of a Subject, and manage cases from your code.
| Endpoint | What it does |
|---|---|
| POST /subjects/{subject_id}/process | Multipart file; Tavnit routes it to a case (202, returns subject_doc_id). |
| POST /cases/{case_id}/docs | Multipart file straight into a case, no routing. doc_type_id is required unless the subject has one document type. Returns the run_id (202). 409 when the case is closed or that document type is already filled. |
| POST /subjects/{subject_id}/docs/{doc_id}/assign | JSON case_id and doc_type_id: file a held document by hand (202). |
| POST /subjects/{subject_id}/cases | JSON name (required, unique, up to 200 characters) and params (optional object). Creates a case (201). 409 when the name exists. |
| GET /subjects/{subject_id}/cases | Query: state (open or closed), status, search, limit (up to 200, default 50), offset. Returns cases and total. |
| GET /cases/{case_id} | The case plus its documents, each with the run_id to poll at GET /runs/{run_id}. |
| POST /cases/{case_id}/close | Closes the case (200). New documents for it are held instead of filed. 409 when already closed. |
| POST /cases/{case_id}/reopen | Reopens it (200). 409 when it is open. |
| POST /cases/{case_id}/inspections | JSON subject_inspector_id: runs a bound inspector over the case's completed runs (202). |
# Create a case (201)
curl -X POST "https://run.tavnit.io/api/subjects/YOUR_SUBJECT_ID/cases" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Shipment 4471", "params": {"customer": "Acme"}}'
# Upload a document straight into that case (202)
curl -X POST "https://run.tavnit.io/api/cases/CASE_ID/docs" \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@invoice.pdf" \
-F "doc_type_id=DOC_TYPE_ID"{
"success": true,
"case": {
"id": "...",
"subject_id": "YOUR_SUBJECT_ID",
"ref": "<minted from the subject's prefix>",
"seq": 42,
"name": "Shipment 4471",
"state": "open",
"status": "<the subject's default case status>",
"params": { "customer": "Acme" },
"created_by": "...",
"created_at": "2026-09-01T10:00:00Z",
"updated_at": "2026-09-01T10:00:00Z",
"closed_at": null,
"closed_by": null
}
}See Subjects.
Nets: start a Catch
Nets are enabled per organization. Without access, these endpoints answer 403.
Without a body, the Catch picks up where the last one ended. Send window_start and/or window_end (ISO timestamps) to backfill a range instead. 409 when the Net is inactive or a Catch that continues from the last one is already in progress.
curl -X POST "https://run.tavnit.io/api/nets/YOUR_NET_ID/catch" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
# 202
{ "success": true, "catch_id": "...", "net_id": "YOUR_NET_ID", "status": "queued", "window_mode": "since_last" }Poll GET /catches/{catch_id}: status, stage, counts and, once completed, output with columns and rows. Cancel with POST /catches/{catch_id}/cancel. See Nets.
Human review: approve or reject
Items paused for review can be approved or rejected from your own tools. Only a configured reviewer of that flow, matcher, inspector or filler can act (403 otherwise), and the item must be awaiting_approval (409 otherwise). These endpoints are open to the HITL Only role.
| Endpoint | Body |
|---|---|
| POST /runs/{run_id}/hitl/approve | output_json (required): the final rows; diff (optional list of edits). 400 with missing_fields when a required human input field is empty. |
| POST /matches/{match_id}/hitl/approve | groups (the final grouping), excluded, diff. |
| POST /inspections/{inspection_id}/hitl/approve | waivers (item_id and reason for each failed item to waive), diff. |
| POST /fills/{fill_id}/hitl/approve | values (field values per template), diff, allow_missing_human_fields. |
| .../hitl/reject | The same paths ending in /hitl/reject, with an optional reason. |
curl -X POST "https://run.tavnit.io/api/runs/RUN_ID/hitl/approve" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"output_json": {"rows": [{"Invoice Number": "001", "Total": 270.30}]}, "diff": []}'
curl -X POST "https://run.tavnit.io/api/runs/RUN_ID/hitl/reject" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Wrong vendor"}'Approving resumes delivery (webhook, email, bucket). Rejecting a run cancels it. See Human-in-the-loop.
API key endpoints
| Endpoint | What it does |
|---|---|
| GET /me/api-key | Returns your key for the organization. |
| POST /me/api-key/regenerate | Issues a new key and invalidates the old one at once. Update every integration. |
{
"success": true,
"api_key": "tvnt_...",
"user_id": "...",
"org_id": "..."
}Automation Tools Overview
You don't need to write code to connect Tavnit to your workflows. Any automation platform that can send an HTTP request can start work in Tavnit, and any platform that can receive a webhook can take the results.
Visual scenarios: an HTTP module calls Tavnit and a Custom webhook receives the results.
Webhooks by Zapier: Catch Hook receives the results and a POST action starts runs.
Self-hosted or cloud: a Webhook node receives the results and an HTTP Request node starts runs.
An HTTP action calls the API the same way.
Every request needs the X-API-Key header with your key from Integrations.
Receive results in your automation
- 1In your platform, create a webhook trigger (Make: Custom webhook; Zapier: Webhooks by Zapier → Catch Hook; n8n: Webhook node, POST) and copy its URL.
- 2In Tavnit, paste it as the webhook of a flow (Webhook panel) or of a pipeline Output node.
- 3Process one document so the platform learns the payload, then map the fields to Sheets, a CRM, an ERP and so on.
The payload is described on the webhooks page.
Make Integration
Make (formerly Integromat) is a visual automation platform that lets you connect apps and automate workflows without writing any code.
Visit MakeGetting Started with Make
- 1Go to make.com and create an account
- 2Click "Create a new scenario" from your dashboard
- 3You'll see a blank canvas where you can add modules
- 4Search for "HTTP" and add the "Make a request" module
A scenario is an automated workflow in Make. It consists of modules (apps) connected together. When one module triggers or receives data, it passes that data to the next module.
Configure the HTTP Module
Once you've added the HTTP module, configure it to send documents to Tavnit. You can use either of these two approaches:
Option 1: Multipart/form-data (when you have a File object)
- 1Add an HTTP "Make a request" module to your scenario
- 2Configure the request:
- URL:
https://run.tavnit.io/api/runs/process - Method: POST
- URL:
- 3In the Headers tab, add:
- Header name: X-API-Key
- Header value: YOUR_API_KEY
- 4Set Body type to "multipart/form-data"
- 5Add form fields:
- flow_id: YOUR_FLOW_ID
- file: (map from previous module)
- source: api (optional)
- 6Run your scenario to test
Option 2: JSON + base64 (when you have a base64 string)
If your previous module outputs a base64 string instead of a file, use this approach:
- 1Set Body type to "Raw" and select "JSON (application/json)"
- 2In the Headers tab, also add:
- Header name: Content-Type
- Header value: application/json
- 3Set the JSON body to:
{
"flow_id": "YOUR_FLOW_ID",
"source": "api",
"filename": "document.pdf",
"file_base64": "{{previous_module.base64_content}}"
}Replace {{previous_module.base64_content}} with the actual mapping from your previous module. In Make, click in the field and select the base64 output from the module that provides your file. Keep the extension in filename: Tavnit uses it to detect the file type.
The call answers right away with a run_id. The extracted data arrives later on the flow's webhook, or you can fetch it with a second HTTP module calling GET https://run.tavnit.io/api/runs/{run_id}.
Other Platforms (Zapier, Power Automate, n8n)
The same approach works for any automation platform that supports HTTP requests:
Use multipart/form-data with a “file” field containing the file, plus the flow_id field.
Use a JSON body with flow_id, filename, and file_base64 (the base64 content from the previous step).
Both methods call the same endpoint and produce identical extraction results. To process files that land in Google Drive, OneDrive or Dropbox, trigger on “New file in folder” and POST the file the same way.
Using the Collections API
If you receive different types of documents and want AI to route each one to the right flow, call the collection instead of a flow.
- URL:
https://run.tavnit.io/api/collections/process - Use
collection_idinstead offlow_id
{
"collection_id": "YOUR_COLLECTION_ID",
"source": "api",
"filename": "document.pdf",
"file_base64": "{{previous_module.base64_content}}"
}Using the Splitters API
If you receive combined PDFs with several documents and need them separated, call the splitter.
- URL:
https://run.tavnit.io/api/splits/run - Use
splitter_idinstead offlow_id
{
"splitter_id": "YOUR_SPLITTER_ID",
"source": "api",
"filename": "combined_docs.pdf",
"file_base64": "{{previous_module.base64_content}}"
}Starting a pipeline
To run a whole pipeline instead of a single flow, POST the file (multipart file, or JSON file_base64 + filename) to https://run.tavnit.io/api/pipelines/YOUR_PIPELINE_ID/execute with the same header. The pipeline ID goes in the URL.
Using the Cleaners API
To clean rows of data from your automation, send them as JSON to the cleaner. A spreadsheet file goes to https://run.tavnit.io/api/sweeps/run instead, as multipart with cleaner_id (that endpoint does not take base64).
- URL:
https://run.tavnit.io/api/cleaners/YOUR_CLEANER_ID/process - Method: POST, Content-Type: application/json
{
"rows": [
{ "Vendor": "acme corp.", "Country": "usa" },
{ "Vendor": "GLOBEX INC", "Country": "Mexico" }
]
}Using the Buckets API
To push rows into a bucket without sending a document, use the write endpoint. Each row must contain every column of the bucket.
- URL:
https://run.tavnit.io/api/buckets/write - Method: POST, Content-Type: application/json
- Header:
X-API-Key: YOUR_API_KEY
{
"bucket_id": "YOUR_BUCKET_ID",
"bucket_name": "YOUR_BUCKET_NAME",
"overwrite": false,
"rows": [
{ "column_one": "value", "column_two": 123 }
]
}Open the bucket's detail page and tap the info icon. Both values are copyable with a single tap.
Other features
Agents, matchers, inspectors, fillers, signals, subjects and Nets are started the same way: an HTTP request with the X-API-Key header. Switch to the Code tab for each endpoint's fields.
