API Documentation

Integrate Extraly's AI-powered document processing into your applications.

Quick Start

Get up and running with the Extraly API in four steps:

1

Create an account

Sign up at extraly.ai/register and choose a plan that includes API access.

2

Generate an API key

Navigate to Settings → API Keys in your dashboard and create a new key. Copy it — it will only be shown once.

3

Submit a document

Send a POST request to https://extraly.ai/api/v1/documents with your file attached.

4

Retrieve results

Poll the document status endpoint or configure a webhook to receive results automatically when processing completes.

curl -X POST https://extraly.ai/api/v1/documents \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "document=@bank_statement.pdf" \
  -F "detection_type=bank_statement"

Authentication

All API requests require two authentication headers:

Header Description
Authorization: Bearer <token> Your account access token, generated when you log in or via the API.
X-API-Key: <key> Your API key, created from the dashboard under Settings → API Keys.

Both headers must be present on every request. Requests with missing or invalid credentials will receive a 401 Unauthorized response.

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
X-API-Key: xtr_live_a1b2c3d4e5f6...

Endpoints

Base URL: https://extraly.ai/api/v1

POST /api/v1/documents

Submit a document for AI processing. Send as multipart/form-data.

Parameters

Field Type Required Description
document file Yes* The document file to process. Supported formats: PDF, PNG, JPG, JPEG, TIFF, WEBP. Max 20 MB.
file file Yes* Alias for document. Either document or file must be provided.
detection_type string No Hint for the AI model. Options: bank_statement, invoice, receipt, auto. Defaults to auto.
custom_fields JSON string No A JSON array of custom field names to extract, e.g. ["vat_number","po_number"].
webhook_url string (URL) No A URL to receive a POST callback when processing completes or fails.
callback_url string (URL) No Alias for webhook_url.
metadata JSON string No Arbitrary key-value pairs to attach to the document for your reference.

Response 201 Created

{
  "data": {
    "id": "doc_8f3a1b2c4d5e",
    "status": "processing",
    "detection_type": "bank_statement",
    "original_filename": "bank_statement.pdf",
    "pages": 3,
    "webhook_url": "https://yourapp.com/webhook",
    "metadata": {},
    "created_at": "2026-03-21T10:30:00Z",
    "updated_at": "2026-03-21T10:30:00Z"
  }
}
GET /api/v1/documents/{id}

Retrieve the status and results of a specific document. When processing is complete, the results field contains the extracted data.

Path Parameters

Parameter Description
id The unique document identifier returned when the document was submitted.

Response 200 OK

{
  "data": {
    "id": "doc_8f3a1b2c4d5e",
    "status": "completed",
    "detection_type": "bank_statement",
    "original_filename": "bank_statement.pdf",
    "pages": 3,
    "webhook_url": "https://yourapp.com/webhook",
    "metadata": {},
    "results": {
      "bank_name": "PrivatBank",
      "account_number": "UA21 3223 1300 0002 6007 2335 6600 1",
      "currency": "UAH",
      "statement_period": {
        "from": "2026-01-01",
        "to": "2026-01-31"
      },
      "opening_balance": 15230.50,
      "closing_balance": 18445.75,
      "transactions": [
        {
          "date": "2026-01-03",
          "description": "Payment from Client ABC",
          "amount": 5200.00,
          "type": "credit",
          "balance": 20430.50
        }
      ],
      "total_credits": 42500.00,
      "total_debits": 39284.75,
      "transaction_count": 47
    },
    "created_at": "2026-03-21T10:30:00Z",
    "updated_at": "2026-03-21T10:31:15Z"
  }
}

Status Values

Status Description
pending Document uploaded, waiting in queue.
processing AI is currently analyzing the document.
completed Processing finished successfully. Results are available.
failed Processing encountered an error. Check the error field.
GET /api/v1/documents

List all documents for the authenticated user. Results are paginated.

Query Parameters

Parameter Type Default Description
page integer 1 Page number.
per_page integer 20 Results per page. Maximum 100.
status string Filter by status: pending, processing, completed, failed.
detection_type string Filter by detection type.
sort string created_at Sort field. Options: created_at, updated_at.
order string desc Sort order: asc or desc.

Response 200 OK

{
  "data": [
    {
      "id": "doc_8f3a1b2c4d5e",
      "status": "completed",
      "detection_type": "bank_statement",
      "original_filename": "bank_statement.pdf",
      "pages": 3,
      "created_at": "2026-03-21T10:30:00Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 5,
    "per_page": 20,
    "total": 94
  }
}
DELETE /api/v1/documents/{id}

Permanently delete a document and all associated data (original file, extracted results, metadata). This action cannot be undone.

Response 200 OK

{
  "message": "Document deleted successfully."
}
GET /api/v1/documents/{id}/export/{format}

Export the extracted results in a specific format. The document must have a completed status.

Path Parameters

Parameter Description
id The unique document identifier.
format Export format: csv, xlsx, json, or xml.

Response

Returns a file download with the appropriate Content-Type and Content-Disposition headers. For json format, the response is a JSON object identical to the results field from the document detail endpoint.

Webhooks

If you provide a webhook_url when submitting a document, Extraly will send a POST request to that URL when processing completes or fails. Your endpoint must respond with a 2xx status code within 10 seconds. Failed deliveries are retried up to 3 times with exponential backoff.

Completion Payload

{
  "event": "document.completed",
  "document_id": "doc_8f3a1b2c4d5e",
  "status": "completed",
  "results": {
    "bank_name": "PrivatBank",
    "transaction_count": 47,
    "opening_balance": 15230.50,
    "closing_balance": 18445.75
  },
  "metadata": {},
  "timestamp": "2026-03-21T10:31:15Z"
}

Failure Payload

{
  "event": "document.failed",
  "document_id": "doc_8f3a1b2c4d5e",
  "status": "failed",
  "error": {
    "code": "PROCESSING_ERROR",
    "message": "Unable to extract data from the provided document. The file may be corrupted or in an unsupported layout."
  },
  "metadata": {},
  "timestamp": "2026-03-21T10:31:15Z"
}

Verifying Webhook Signatures

Every webhook request includes an X-Extraly-Signature header containing an HMAC-SHA256 signature of the request body. Verify this signature using your API key as the secret to ensure the request originated from Extraly.

X-Extraly-Signature: sha256=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

# Verification pseudocode:
expected = HMAC-SHA256(request_body, your_api_key)
is_valid = secure_compare(expected, signature_from_header)

Rate Limits

API requests are rate-limited to 1,000 requests per hour per API key. Rate limit information is included in every response via headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 984
X-RateLimit-Reset: 1711015200

When the rate limit is exceeded, the API responds with 429 Too Many Requests. The Retry-After header indicates how many seconds to wait before making another request. Implement exponential backoff in your integration for best results.

Error Codes

All errors follow a consistent format:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The document field is required.",
    "details": {
      "document": ["The document field is required."]
    }
  }
}
HTTP Status Code Description
400 VALIDATION_ERROR Request body failed validation. Check the details field for specific errors.
401 UNAUTHORIZED Missing or invalid authentication credentials.
403 FORBIDDEN You do not have permission to access this resource.
404 NOT_FOUND The requested document or resource does not exist.
409 CONFLICT The document is still processing and cannot be modified.
413 FILE_TOO_LARGE The uploaded file exceeds the 20 MB size limit.
415 UNSUPPORTED_FORMAT The uploaded file format is not supported.
422 PROCESSING_ERROR The document could not be processed by the AI model.
429 RATE_LIMITED Rate limit exceeded. Retry after the time indicated in the Retry-After header.
500 INTERNAL_ERROR An unexpected server error occurred. Contact support if the issue persists.
503 SERVICE_UNAVAILABLE The service is temporarily unavailable due to maintenance or high load.

Code Examples

Below are examples for submitting a document and retrieving results in popular languages.

cURL

# Submit a document
curl -X POST https://extraly.ai/api/v1/documents \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "[email protected]" \
  -F "detection_type=invoice" \
  -F "webhook_url=https://yourapp.com/webhook"

# Get document status and results
curl https://extraly.ai/api/v1/documents/doc_8f3a1b2c4d5e \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY"

# List all documents
curl "https://extraly.ai/api/v1/documents?page=1&per_page=20&status=completed" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY"

# Export results as CSV
curl -OJ https://extraly.ai/api/v1/documents/doc_8f3a1b2c4d5e/export/csv \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY"

# Delete a document
curl -X DELETE https://extraly.ai/api/v1/documents/doc_8f3a1b2c4d5e \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY"

Python

import requests
import time

BASE_URL = "https://extraly.ai/api/v1"
HEADERS = {
    "Authorization": "Bearer YOUR_ACCESS_TOKEN",
    "X-API-Key": "YOUR_API_KEY",
}

# Submit a document
with open("bank_statement.pdf", "rb") as f:
    response = requests.post(
        f"{BASE_URL}/documents",
        headers=HEADERS,
        files={"document": f},
        data={
            "detection_type": "bank_statement",
            "webhook_url": "https://yourapp.com/webhook",
        },
    )

doc = response.json()["data"]
doc_id = doc["id"]
print(f"Document submitted: {doc_id} (status: {doc['status']})")

# Poll for results
while True:
    result = requests.get(
        f"{BASE_URL}/documents/{doc_id}",
        headers=HEADERS,
    ).json()["data"]

    if result["status"] in ("completed", "failed"):
        break

    time.sleep(2)

if result["status"] == "completed":
    print(f"Transactions found: {result['results']['transaction_count']}")

    # Export as XLSX
    export = requests.get(
        f"{BASE_URL}/documents/{doc_id}/export/xlsx",
        headers=HEADERS,
    )
    with open("results.xlsx", "wb") as f:
        f.write(export.content)
else:
    print(f"Processing failed: {result['error']}")

JavaScript (Node.js)

const fs = require("fs");
const FormData = require("form-data");

const BASE_URL = "https://extraly.ai/api/v1";
const headers = {
  Authorization: "Bearer YOUR_ACCESS_TOKEN",
  "X-API-Key": "YOUR_API_KEY",
};

async function processDocument(filePath) {
  // Submit document
  const form = new FormData();
  form.append("document", fs.createReadStream(filePath));
  form.append("detection_type", "bank_statement");

  const submitRes = await fetch(`${BASE_URL}/documents`, {
    method: "POST",
    headers: { ...headers, ...form.getHeaders() },
    body: form,
  });
  const { data: doc } = await submitRes.json();
  console.log(`Submitted: ${doc.id}`);

  // Poll for results
  let result;
  while (true) {
    const res = await fetch(`${BASE_URL}/documents/${doc.id}`, { headers });
    result = (await res.json()).data;

    if (result.status === "completed" || result.status === "failed") break;
    await new Promise((r) => setTimeout(r, 2000));
  }

  if (result.status === "completed") {
    console.log(`Transactions: ${result.results.transaction_count}`);

    // Export as JSON
    const exportRes = await fetch(
      `${BASE_URL}/documents/${doc.id}/export/json`,
      { headers }
    );
    const exportData = await exportRes.json();
    console.log(exportData);
  }
}

processDocument("./bank_statement.pdf");

PHP

<?php

$baseUrl = 'https://extraly.ai/api/v1';
$headers = [
    'Authorization: Bearer YOUR_ACCESS_TOKEN',
    'X-API-Key: YOUR_API_KEY',
];

// Submit a document
$ch = curl_init("$baseUrl/documents");
$postFields = [
    'document' => new CURLFile('bank_statement.pdf', 'application/pdf'),
    'detection_type' => 'bank_statement',
    'webhook_url' => 'https://yourapp.com/webhook',
];
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $postFields,
    CURLOPT_HTTPHEADER => $headers,
    CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

$docId = $response['data']['id'];
echo "Submitted: $docId\n";

// Poll for results
while (true) {
    $ch = curl_init("$baseUrl/documents/$docId");
    curl_setopt_array($ch, [
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $result = json_decode(curl_exec($ch), true)['data'];
    curl_close($ch);

    if (in_array($result['status'], ['completed', 'failed'])) {
        break;
    }
    sleep(2);
}

if ($result['status'] === 'completed') {
    echo "Transactions: " . $result['results']['transaction_count'] . "\n";

    // Export as CSV
    $ch = curl_init("$baseUrl/documents/$docId/export/csv");
    curl_setopt_array($ch, [
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $csv = curl_exec($ch);
    curl_close($ch);
    file_put_contents('results.csv', $csv);
} else {
    echo "Failed: " . $result['error']['message'] . "\n";
}

Webhook Signature Verification (PHP)

<?php

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_EXTRALY_SIGNATURE'] ?? '';
$apiKey = 'YOUR_API_KEY';

$expected = 'sha256=' . hash_hmac('sha256', $payload, $apiKey);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($payload, true);

if ($event['event'] === 'document.completed') {
    // Process the completed document
    $docId = $event['document_id'];
    $results = $event['results'];
    // ... your logic here
}

http_response_code(200);
echo 'OK';

Webhook Signature Verification (Python)

import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
API_KEY = "YOUR_API_KEY"

@app.route("/webhook", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("X-Extraly-Signature", "")
    expected = "sha256=" + hmac.new(
        API_KEY.encode(), request.data, hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        abort(401)

    event = request.json

    if event["event"] == "document.completed":
        doc_id = event["document_id"]
        results = event["results"]
        # ... your logic here

    return "OK", 200