v1 · REST · JSON

API Documentation

Integrate Kadeon with your ERP, CRM or back-office system via REST API.

Overview

All APIs are available at https://<your-instance>, with the prefix /api/v1/. The exceptions are /webhook/mailgun and /health, which don't use the prefix.

All responses are in JSON format.

Content-Type: application/json
Accept: application/json

Authentication methods

MethodHeaderUsed for
JWT BearerAuthorization: Bearer <token>User sessions (dashboard/app login)
API KeyX-API-Key: <key>Server-to-server integrations (document ingestion)

Authentication

POST /api/v1/auth/login

Authenticates a user with email and password, returns a JWT.

POST /api/v1/auth/login
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "secret"
}

// 200 OK
{
  "access_token": "eyJ...",
  "token_type": "bearer",
  "user": {
    "id": "...",
    "email": "user@example.com",
    "role": "tenant_admin",
    "tenant_id": "..."
  }
}

The token must be included in the Authorization header of all subsequent requests.

GET /api/v1/auth/me

Returns the authenticated user's data.

GET /api/v1/auth/me
Authorization: Bearer <token>

Machine token (M2M) tenant_admin+

Issues a scope-limited token, designed for integration with external systems (ERP).

POST /api/v1/auth/token
Authorization: Bearer <tenant_admin_jwt>
Content-Type: application/json

{
  "scopes": ["records:read"],
  "ttl_seconds": 3600
}

// 200 OK
{
  "access_token": "eyJ...",
  "token_type": "bearer",
  "expires_at": "2026-07-06T15:00:00Z",
  "scopes": ["records:read"],
  "tenant_id": "..."
}

Use this token to poll records from your ERP, without exposing user credentials.

Document ingestion

Send documents to the platform via email API or direct upload.

API key

Generate an API key from Settings, or via POST /api/v1/tenants/{id}/api-keys. Keep it safe: it is shown only once and must be included in every request in the X-API-Key header.

POST/api/v1/ingest/emailSubmit an email with attachments to process
POST /api/v1/ingest/email
X-API-Key: kd_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "message_id": "unique-id-001",
  "sender": "supplier@example.com",
  "subject": "Ordine #42",
  "body_text": "In allegato l'ordine.",
  "attachments": [
    {
      "filename": "ordine_42.pdf",
      "content_b64": "JVBERi0xLjQ...",
      "mime": "application/pdf"
    }
  ]
}

// 202 Accepted
{
  "status": "accepted",
  "email_raw_id": "...",
  "source": "API",
  "attachments": 1
}

The endpoint is idempotent on message_id: duplicate submissions are ignored.

Direct upload any role

POST/api/v1/ingest/uploadUpload one or more files directly (requires user session)
POST /api/v1/ingest/upload
Authorization: Bearer <token>
Content-Type: multipart/form-data

files=@document.pdf
files=@image.jpg

// 202 Accepted
{
  "status": "accepted",
  "email_raw_id": "...",
  "source": "upload",
  "attachments": 2
}

Record

Records are the result of AI processing: each email/attachment becomes a record with extracted data, status and metadata.

Lifecycle

NORMALIZING → NEEDS_REVIEW → APPROVED | REJECTED → EXPORTED

List records

GET/api/v1/recordsPaginated list with filters
GET /api/v1/records?status_filter=NEEDS_REVIEW&limit=50&offset=0
Authorization: Bearer <token>

// 200 OK
{
  "items": [
    {
      "id": "rec_...",
      "identity_code": "ORDER",
      "identity_name": "Ordine di acquisto",
      "status": "NEEDS_REVIEW",
      "confidence": 0.94,
      "email_subject": "Ordine #42",
      "email_sender": "supplier@example.com",
      "data": { "numero_ordine": "42", "importo": "1250.00" },
      "created_at": "2026-07-06T10:00:00Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

Available query parameters

ParameterDescription
status_filterSingle status (NEEDS_REVIEW, APPROVED, REJECTED, EXPORTED)
status_listMultiple statuses, comma-separated
identityIdentity code (e.g. ORDER)
qFree-text search on sender, subject, data
mailboxMailbox UUID
todaytrue → today's records only
limit / offsetPagination (max 200)
sort_by / sort_dirField and direction (asc/desc)

Record detail

GET/api/v1/records/{record_id}Full detail

Record actions

POST/api/v1/records/{id}/approveApprove the record
POST/api/v1/records/{id}/rejectReject the record
POST/api/v1/records/{id}/correctCorrect extracted data
POST/api/v1/records/{id}/reprocessReprocess with AI
DELETE/api/v1/records/{id}Delete record

Data correction

POST /api/v1/records/{record_id}/correct
Authorization: Bearer <token>
Content-Type: application/json

{
  "data": { "numero_ordine": "42-B", "importo": "1300.00" },
  "note": "Importo corretto"
}

// 200 OK — record with status NEEDS_REVIEW

Download PDF

GET/api/v1/records/{id}/document.pdfGenerated PDF with extracted data

Bulk operations

POST /api/v1/records/bulk
Authorization: Bearer <token>
Content-Type: application/json

{
  "action": "approve",
  "record_ids": ["rec_aaa", "rec_bbb"]
}

// Oppure — tutti i record che matchano filtri:
{
  "action": "approve",
  "all_matching": true,
  "filters": { "status": "NEEDS_REVIEW", "identity": "ORDER" }
}

// 200 OK
{ "action": "approve", "affected": 12 }

File export (CSV / Excel)

GET/api/v1/records/export-fileDownload up to 10,000 records
GET /api/v1/records/export-file?format=xlsx&identity=ORDER&status_filter=APPROVED
Authorization: Bearer <token>

// Response: binary Excel file

ERP Export (M2M pull)

Recommended flow for ERP/back-office systems: issue a scope-limited token and use the export endpoint to periodically poll approved records.

Full flow

// 1. Login con credenziali tenant_admin
POST /api/v1/auth/login
→ { access_token: "<admin_jwt>" }

// 2. Emetti un token M2M con scadenza e scope limitato
POST /api/v1/auth/token
Authorization: Bearer <admin_jwt>
{ "scopes": ["records:read"], "ttl_seconds": 86400 }
→ { access_token: "<m2m_token>", expires_at: "..." }

// 3. Poll dei record approvati (usa il token M2M)
GET /api/v1/export/records?status_filter=APPROVED&since=2026-07-01T00:00:00Z
Authorization: Bearer <m2m_token>
→ { items: [...], total: 8 }

Export parameters

ParameterDefaultDescription
status_filterAPPROVED,EXPORTEDStatus/statuses to export
identityFilter by identity code
sinceISO-8601 — only records created after this date
limit200 (max 1000)Maximum number of records

Response structure

{
  "items": [
    {
      "id": "rec_...",
      "identity_code": "ORDER",
      "schema_version": 1,
      "status": "APPROVED",
      "exported": false,
      "confidence": 0.97,
      "data": {
        "numero_ordine": "42",
        "fornitore": "Acme Srl",
        "importo": "1250.00",
        "data_consegna": "2026-07-15"
      },
      "created_at": "2026-07-06T10:00:00Z"
    }
  ],
  "total": 1
}

Mailgun Webhook

Configure Mailgun to push inbound emails directly to Kadeon via webhook. Authenticity is verified via HMAC-SHA256.

POST/webhook/mailgunMailgun inbound email

The webhook accepts both multipart payloads (standard Mailgun) and JSON. The HMAC key is configured via the MAILGUN_SIGNING_KEY environment variable on the server.

// Mailgun inbound route URL:
https://<your-instance>/webhook/mailgun

// 202 Accepted
{
  "status": "accepted",
  "email_raw_id": "...",
  "source": "MAILGUN",
  "attachments": 2
}

// 401 Unauthorized — firma HMAC non valida

Usage & quota

GET/api/v1/usage/meQuota and plan for the current user
GET /api/v1/usage/me
Authorization: Bearer <token>

// 200 OK
{
  "plan": { "name": "professional", "period": "monthly" },
  "cycle": { "period": "2026-07", "start": "2026-07-01", "end": "2026-07-31", "tokens": 50000 },
  "quota_remaining": 34210,
  "storage_used_mb": 142,
  "storage_quota_mb": 5120,
  "entitlement": { "ok": true, "tokens_ok": true, "storage_ok": true }
}
GET/api/v1/usageDetailed tenant usage (tenant_admin+)

Includes daily breakdown, by AI provider and by record status. Optional parameter: ?period=2026-07

Error codes

HTTPMeaningTypical cause
400Bad requestMissing parameters or wrong format
401UnauthenticatedMissing/expired token or invalid HMAC signature
402Quota exceededPlan tokens or storage exhausted
403ForbiddenInsufficient role for the operation
404Not foundResource does not exist or is out of tenant scope
409ConflictEmail already imported (duplicate message_id), record already exported
422Validation failedJSON body does not match the expected schema
429Too many requestsRate limit exceeded (login: 10 attempts / 15 min)
500Internal errorUnexpected server-side error

Error format

// Errore 422 — esempio
{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    }
  ]
}

// Errore generico
{
  "detail": "Record not found"
}