Skip to content

Error Handling

This guide covers how to handle errors when integrating with the XLSX API.

Error Response Format

All errors return a JSON object with a detail field:

{
  "detail": "Human-readable error message"
}

HTTP Status Codes

Code Meaning Action
400 Bad request Fix the request (validation error, missing fields)
401 Unauthorized Check your API key
403 Forbidden Check permissions or IP whitelist with your admin
404 Not found Verify tenant slug, engine slug, or job ID
429 Rate limited Back off and retry after a delay
500 Server error Retry with backoff; report if persistent

Handling by Endpoint

Execute (POST /jobs/execute/...)

Error Cause Fix
400 validation failed Inputs don't match the schema Check input_schema via the about endpoint
400 no active version Engine has no activated version Contact your tenant admin
403 no permission Your key lacks can_execute Contact your tenant admin
403 IP not whitelisted Your IP is blocked Contact your tenant admin
429 rate limited Too many requests Implement backoff

Job Status (GET /jobs/{job_id})

A completed job with status: "failed" is not an HTTP error — it's a normal response indicating the Excel processing failed (e.g. formula error, SharePoint issue).

{
  "status": "failed",
  "error_message": "Could not process at the moment"
}

Info

Error messages shown to consumers are intentionally generic. Detailed errors (like which Excel range caused #DIV/0!) are visible to tenant admins in the dashboard.

Download (GET /jobs/{job_id}/download)

The most common error is requesting a download before the job completes:

{ "detail": "Job not completed yet. Current status: processing" }

Always check status == "completed" before requesting a download.

Rate Limiting

When you hit a rate limit, the server returns 429. Implement exponential backoff:

import time

def call_with_retry(func, max_retries=3):
    delay = 1
    for attempt in range(max_retries):
        resp = func()
        if resp.status_code == 429:
            time.sleep(delay)
            delay *= 2
            continue
        return resp
    raise Exception("Rate limited after max retries")

Rate Limit Tiers

Scope Limit
Execution endpoints (per API key) 30 req/min
Job endpoints (per IP) 30 req/min
General API (per IP) 100 req/min
import requests

API_BASE = "https://api.xlsxapi.eu"
HEADERS = {"X-API-Key": "YOUR_API_KEY"}

def execute_and_wait(tenant, engine, inputs):
    # Execute
    resp = requests.post(
        f"{API_BASE}/jobs/execute/{tenant}/{engine}",
        json={"inputs": inputs},
        headers=HEADERS,
    )

    if resp.status_code == 400:
        raise ValueError(f"Invalid input: {resp.json()['detail']}")
    elif resp.status_code == 401:
        raise PermissionError("Invalid API key")
    elif resp.status_code == 403:
        raise PermissionError(resp.json()["detail"])
    elif resp.status_code == 429:
        raise RuntimeError("Rate limited — back off and retry")
    resp.raise_for_status()

    job_id = resp.json()["job_id"]

    # Poll
    import time
    delay = 1
    while True:
        time.sleep(delay)
        status_resp = requests.get(
            f"{API_BASE}/jobs/{job_id}",
            headers=HEADERS,
        )
        status_resp.raise_for_status()
        result = status_resp.json()

        if result["status"] == "completed":
            return result["outputs"]
        elif result["status"] == "failed":
            raise RuntimeError(
                f"Job failed: {result.get('error_message', 'Unknown error')}"
            )
        delay = min(delay * 2, 15)