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-Key header.
  • 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.
Beta features

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:

401
{
  "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:

Multipart file upload

Send the file as multipart/form-data in a field named file, with the other fields as form fields. Works on every file endpoint.

Base64 in a JSON body

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.

EndpointBase64Files accepted
POST /runs/processYesPDF, images (PNG, JPG, JPEG, JFIF, TIF, TIFF, WEBP, BMP, GIF), spreadsheets (XLSX, XLS, CSV)
POST /collections/processYesSame as runs
POST /splits/runYesSame as runs
POST /pipelines/{id}/executeYesPDF and images
POST /signals/{id}/runYesAudio: MP3, MP4, MPEG, MPGA, M4A, WAV, WEBM
POST /sweeps/runNoSpreadsheets: CSV, XLSX, XLS
Inspectors, fillers, subjects and casesNoThe 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:

409 example
{
  "success": false,
  "error": "Bucket mismatch",
  "message": "bucket_name does not match the target bucket. Verify both bucket_id and bucket_name before retrying."
}
StatusMeaning
200 / 201Done (reads, cancels, approvals) / created (cases, fills, inspections).
202Accepted: the work is queued. Keep the returned id.
400A required field is missing or invalid, the file type is not supported, or the resource is inactive or not set up.
401Missing or invalid X-API-Key.
402Your organization can't start new work right now; contact the Tavnit team.
403The resource belongs to another organization, your role is HITL Only, or you are not a reviewer of that item.
404The ID does not exist in your organization.
409Conflict with the current state: bucket name mismatch, already cancelled or finished, case closed, slot already filled, an agent name shared by several agents.
429Too many runs already waiting for an agent.
500Unexpected error. Safe to retry later.

Endpoint reference

FeatureMethod and pathWhat it does
FlowsPOST /runs/processExtract a document with a flow
FlowsGET /runs/{run_id}Run status and extracted data
FlowsGET /runs/{run_id}/source-fileThe original document
CollectionsPOST /collections/processAI routes a document to the right flow or splitter
CollectionsGET /collection-runs/{id}/source-fileThe original document
SplittersPOST /splits/runSplit a multi-document file
SplittersGET /splits/{split_id}/source-fileThe original bundle
CleanersPOST /sweeps/runSweep a spreadsheet
CleanersPOST /cleaners/{cleaner_id}/processSweep rows sent as JSON
BucketsPOST /buckets/writeAppend or replace rows
BucketsGET /buckets/readRead rows, paginated
AgentsPOST /bots/{agent_id}/runsStart an agent run
AgentsPOST /bots/runsStart an agent run by bot_id or bot_name in the body
AgentsGET /bot-runs/{bot_run_id}Poll one agent run
AgentsGET /bots/{agent_id}/runsList an agent's runs
AgentsPOST /bot-runs/{bot_run_id}/cancelCancel an agent run
AgentsPOST /bot-runs/{bot_run_id}/human/resolveTell a waiting agent the person is done
AgentsPOST /bot-runs/{bot_run_id}/resumeResume a run paused for a person
MatchersPOST /matchers/{matcher_id}/runMatch completed runs
InspectorsPOST /inspectors/{inspector_id}/processAdd a document to an inspection
FillersPOST /fillers/{filler_id}/fillsOpen a fill and add documents
PipelinesPOST /pipelines/{pipeline_id}/executeExecute a pipeline
SignalsPOST /signals/{signal_id}/runStructure an audio file (a Wave)
SubjectsPOST /subjects/{subject_id}/processRoute a document to a case
NetsPOST /nets/{net_id}/catchStart a Catch
Human reviewPOST /runs/{run_id}/hitl/approveApprove or reject paused items
API keyGET /me/api-keyRead 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.

POST/runs/process
* Send either file or file_base64 + filename.
FieldRequiredDescription
flow_idYesThe flow that extracts the document.
fileYes*The document (multipart).
file_base64 + filenameYes*Instead of file: base64 content and the filename with its extension.
content_typeNoMIME type for a base64 file.
sourceNoLabel stored on the run: api (default), email, manual_upload or collection.
Python
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())
202 response
{
  "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

GET/runs/{run_id}

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.

200 response
{
  "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
}
Python (submit and poll)
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

GET/runs/{run_id}/source-file

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.

200 response
{
  "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.

POST/collections/process

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.

Python (Collections)
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())
202 response
{
  "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.

POST/splits/run

Same body as /runs/process, with splitter_id.

Python (Splitters)
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())
202 response
{
  "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.

POST/sweeps/run
FieldRequiredDescription
cleaner_idYesThe cleaner to run.
fileYesCSV, XLSX or XLS, multipart only. Headers on the first row; every base field the cleaner requires must be a column, without duplicates.
sheet_nameNoWhich sheet of a workbook to read (default: the first).
POST/cleaners/{cleaner_id}/process

Send JSON with rows (an array of objects), or a multipart file as above.

JSON body
{
  "rows": [
    { "Vendor": "acme corp.", "Country": "usa" },
    { "Vendor": "GLOBEX INC", "Country": "Mexico" }
  ]
}
Python (Cleaners)
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())
202 response
{
  "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.

POST/buckets/write
FieldRequiredDescription
bucket_idYesThe bucket.
bucket_nameYesIts exact name.
overwriteYesBoolean. false appends the rows; true replaces all existing rows.
rowsYesArray 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.

Python (Buckets)
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())
202 response
{
  "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

GET/buckets/read

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.

Request
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"
200 response
{
  "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.

POST/bots/{agent_id}/runs

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.

Request
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

POST/bots/runs

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.

Request
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

EndpointWhat 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}/runsNewest 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}/cancelCancels 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.
GET /bot-runs/{bot_run_id}
{
  "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.

EndpointWhat it does
POST /bot-runs/{bot_run_id}/human/resolveThe 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}/resumeContinues 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.
Request
# 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.

POST/matchers/{matcher_id}/run
FieldRequiredDescription
run_idsYesTwo or more completed runs of the matcher's flow.
benchmark_run_idIn benchmark modeThe run the others are compared against.
Request
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.

POST/inspectors/{inspector_id}/process

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.

Request
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"
}
EndpointWhat it does
POST /inspectors/{inspector_id}/inspectionsCreates an empty inspection (201) to add documents to later.
POST /inspections/{inspection_id}/fireStops 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.

EndpointWhat it does
POST /fillers/{filler_id}/fillsOpens a fill (201, returns fill_id). The filler needs a template and field mappings.
POST /fills/{fill_id}/route-uploadMultipart file; AI routes it into an open slot (202).
POST /fills/{fill_id}/inputs/{input_id}/uploadMultipart file for a specific slot; starts that slot's run (202).
POST /fills/{fill_id}/inputs/{input_id}/attach-runJSON run_id: reuse a completed run for a slot (202).
POST /fills/{fill_id}/fireFills now instead of waiting for every slot. 400 with missing_inputs when a required slot is empty.
POST /fillers/{filler_id}/templatesAdds a template PDF (multipart file, optional name). Map its fields in the app.
Request
# 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.

POST/pipelines/{pipeline_id}/execute

Multipart file or file_base64 + filename; PDF or image. 400 when the pipeline is inactive or its graph cannot run (with validation_errors).

Request
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).

POST/signals/{signal_id}/run

Multipart file or base64. Audio up to 150 MB and 8 hours, with at most 2 hours of detected speech.

Request
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.

EndpointWhat it does
POST /subjects/{subject_id}/processMultipart file; Tavnit routes it to a case (202, returns subject_doc_id).
POST /cases/{case_id}/docsMultipart 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}/assignJSON case_id and doc_type_id: file a held document by hand (202).
POST /subjects/{subject_id}/casesJSON name (required, unique, up to 200 characters) and params (optional object). Creates a case (201). 409 when the name exists.
GET /subjects/{subject_id}/casesQuery: 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}/closeCloses the case (200). New documents for it are held instead of filed. 409 when already closed.
POST /cases/{case_id}/reopenReopens it (200). 409 when it is open.
POST /cases/{case_id}/inspectionsJSON subject_inspector_id: runs a bound inspector over the case's completed runs (202).
Request
# 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"
201 response
{
  "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.

POST/nets/{net_id}/catch

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.

Request
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.

EndpointBody
POST /runs/{run_id}/hitl/approveoutput_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/approvegroups (the final grouping), excluded, diff.
POST /inspections/{inspection_id}/hitl/approvewaivers (item_id and reason for each failed item to waive), diff.
POST /fills/{fill_id}/hitl/approvevalues (field values per template), diff, allow_missing_human_fields.
.../hitl/rejectThe same paths ending in /hitl/reject, with an optional reason.
Request
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

EndpointWhat it does
GET /me/api-keyReturns your key for the organization.
POST /me/api-key/regenerateIssues a new key and invalidates the old one at once. Update every integration.
200 response
{
  "success": true,
  "api_key": "tvnt_...",
  "user_id": "...",
  "org_id": "..."
}