Get Started

Quickstart

This walks through the whole loop — get a key, upload a document, start processing, get results back — using an invoice as the example. If you haven't installed curl or set up an environment variable for your key yet, see Environment Setup first.

Step 1 — Get your API key

Your API key lives on your account's profile settings page, under API Key. The v1 API requires a paid plan (Basic or above) — Free-plan keys are accepted by the auth check but every call after that returns a plan_required error. Keys generated going forward look like itt_live_<64 hex characters>; export yours as an environment variable so it doesn't end up hardcoded in a script:

export API_KEY="itt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Step 2 — Upload a document

POST /api/v1/documents takes either a file (multipart) or a url the server fetches for you — pass exactly one. batch_name is optional — omit it and the API generates one for you (returned in the response) and every document you want processed together should share the same batch_name. This example uses url against a real sample invoice hosted on our own site, so it's runnable exactly as written, with no local file needed:

curl -X POST https://imagetotable.ai/api/v1/documents \
  -H "Authorization: Bearer $API_KEY" \
  -F "url=https://imagetotable.ai/static/samples/invoice.webp"
import os
import requests

response = requests.post(
    "https://imagetotable.ai/api/v1/documents",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
    data={"url": "https://imagetotable.ai/static/samples/invoice.webp"},
)
print(response.json())
const formData = new FormData();
formData.append("url", "https://imagetotable.ai/static/samples/invoice.webp");

const response = await fetch("https://imagetotable.ai/api/v1/documents", {
  method: "POST",
  headers: { "Authorization": `Bearer ${process.env.API_KEY}` },
  body: formData,
});
console.log(await response.json());

Got a local file instead? Swap url for a multipart file field — see Upload a document in the Reference for the full parameter list including batch_name, template_id, and password.

The response returns the new document's ID and the batch it landed in — save batch_name, you'll need it in the next two steps:

{
  "document_id": "3f9c9e2a-1b7d-4e2b-9a3e-2f5b6c7d8e9f",
  "batch_name": "260716-4K9P",
  "remaining_batch_capacity": 199
}

Uploading a multi-page PDF instead of an image works the same way, except document_id comes back as an array — one document per page — since each page is processed independently.

Step 3 — Start processing

POST /api/v1/batches/{batch_name}/process kicks off extraction. You can point it at a saved Template via template_id, or — as here — declare the fields you want inline with fields for a one-off run:

curl -X POST https://imagetotable.ai/api/v1/batches/260716-4K9P/process \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "fields": [
          {"name": "invoice_number"},
          {"name": "invoice_date"},
          {"name": "vendor_name"},
          {"name": "total_amount"}
        ]
      }'
import os
import requests

response = requests.post(
    "https://imagetotable.ai/api/v1/batches/260716-4K9P/process",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
    json={
        "fields": [
            {"name": "invoice_number"},
            {"name": "invoice_date"},
            {"name": "vendor_name"},
            {"name": "total_amount"},
        ]
    },
)
print(response.json())
const response = await fetch("https://imagetotable.ai/api/v1/batches/260716-4K9P/process", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    fields: [
      { name: "invoice_number" },
      { name: "invoice_date" },
      { name: "vendor_name" },
      { name: "total_amount" },
    ],
  }),
});
console.log(await response.json());

This deducts credits immediately (one document deducted per call) and enqueues the document(s) for background processing:

{
  "batch_name": "260716-4K9P",
  "queued": 1,
  "quality": "fast",
  "webhook_registered": false
}

Leave fields and template_id out entirely and the API infers reasonable column names on its own instead. quality is optional too — omit it and processing falls back to your account's own speed/quality setting (see Account Settings and API Behavior). webhook_registered: false here just means this particular call didn't include webhook_url — it doesn't mean no webhook is registered for this batch; see the Batches reference if you're registering one separately via PUT .../webhook.

Step 4 — Poll and get your results

Processing is asynchronous — poll GET /api/v1/batches/{batch_name} until all_done is true (a few seconds for a single document), or register a webhook instead of polling. The full lifecycle is covered in the Async Task Model guide.

Once it's done, fetch the reshaped results:

curl https://imagetotable.ai/api/v1/batches/260716-4K9P/results \
  -H "Authorization: Bearer $API_KEY"
import os
import requests

response = requests.get(
    "https://imagetotable.ai/api/v1/batches/260716-4K9P/results",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
)
print(response.json())
const response = await fetch("https://imagetotable.ai/api/v1/batches/260716-4K9P/results", {
  headers: { "Authorization": `Bearer ${process.env.API_KEY}` },
});
console.log(await response.json());

Response JSON is never language-tabbed — the shape doesn't change based on which client sent the request:

{
  "batch_name": "260716-4K9P",
  "status": "succeeded",
  "created_at": "2026-07-16T09:12:03Z",
  "started_at": "2026-07-16T09:12:05Z",
  "completed_at": "2026-07-16T09:12:14Z",
  "documents": [
    {
      "document_id": "3f9c9e2a-1b7d-4e2b-9a3e-2f5b6c7d8e9f",
      "filename": "invoice.jpg",
      "status": "succeeded",
      "created_at": "2026-07-16T09:12:03Z",
      "started_at": "2026-07-16T09:12:05Z",
      "completed_at": "2026-07-16T09:12:14Z",
      "line_items": [
        {
          "invoice_number": "INV-1042",
          "invoice_date": "2026-06-30",
          "vendor_name": "Acme Supply Co.",
          "total_amount": "1,284.50"
        }
      ]
    }
  ]
}

completed_at is set once a document (or, at the batch level, every document in the batch) reaches a terminal state — succeeded, failed, or canceled. It stays null while still queued or processing, and at the batch level it also stays null until all documents in the batch have finished, even if some of them finished earlier.

Next steps

From here: read Async Task Model for the full status lifecycle, Webhooks to get notified instead of polling, and API Reference for every endpoint's full parameter list.

📮 contact email: [email protected]