API

The xplo REST API lets you upload Excel files, run analysis pipelines, and download compiled artifacts programmatically.

Base URL

https://xplo.pythia.software/api/v2

For self-hosted instances, the API is served at /api/v2/ under whatever host you've configured.

All requests and responses are JSON (snake_case), except file uploads and compiled artifacts. CORS is enabled for all origins.

Authentication

Every endpoint except /health requires an Authorization: Bearer <token> header. Two token types are supported:

API tokens (recommended for scripts and servers)

API tokens start with xplo_ and have no expiry unless you set one. Create one via the web app at Settings → API Tokens, or through the API once you're signed in:

curl -X POST https://xplo.pythia.software/api/v2/tokens \
  -H "Authorization: Bearer <firebase-id-token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-cli-token"}'

The response contains the plaintext token once — store it securely, it can't be retrieved later.

{
  "token": "xplo_a1b2c3...",
  "api_token": {
    "id": "tok_...",
    "name": "my-cli-token",
    "created_at": "2026-04-17T10:00:00Z"
  }
}

Use the token on subsequent requests:

curl https://xplo.pythia.software/api/v2/files \
  -H "Authorization: Bearer xplo_a1b2c3..."

Firebase ID tokens (used by the web app)

Users signed in through the web app authenticate with Firebase ID tokens obtained via the Firebase JS SDK. The backend verifies tokens and, on first login, auto-creates a user + organization.

Errors

Errors return a consistent JSON body with an HTTP status code:

{ "error": "file not found", "code": "NOT_FOUND" }
Code HTTP Meaning
VALIDATION_ERROR 400 Malformed request or invalid fields
UNAUTHORIZED 401 Missing or invalid credentials
FORBIDDEN 403 Authenticated, but not allowed
NOT_FOUND 404 Resource doesn't exist
CONFLICT 409 Resource state blocks the operation
INTERNAL 500 Unexpected server error

Pagination

List endpoints accept limit (default 20, max 100) and offset (default 0) query parameters and return:

{ "items": [ ... ], "total": 42 }

Resources

Organizations own all data. Every File, FileGroup, and Action belongs to exactly one organization, and access is scoped to organizations the caller is a member of. Most users have a single organization created automatically on first login.

  • File — one uploaded spreadsheet, with a version number. Re-uploading a file with the same name bumps the version.
  • FileGroup — a named bag of files you want to analyze together. An analysis runs against a file group.
  • Action — one run of the analysis pipeline on a file group. Actions are asynchronous and composed of many backing Tasks.
  • ApiToken — a long-lived bearer token scoped to your user + organization.

Quickstart: upload and analyze

The simplest path is the CLI compile endpoint, which bundles upload + file group + action creation into one call.

curl -X POST https://xplo.pythia.software/api/v2/cli/compile \
  -H "Authorization: Bearer xplo_..." \
  -F "file=@budget.xlsx"
{
  "action_id": "act_...",
  "file_id": "file_...",
  "file_group_id": "fg_..."
}

Poll for completion:

curl https://xplo.pythia.software/api/v2/cli/status/act_... \
  -H "Authorization: Bearer xplo_..."
{
  "action_id": "act_...",
  "status": "RUNNING",
  "percent_complete": 0.42,
  "total_tasks": 50,
  "completed_tasks": 21,
  "failed_tasks": 0
}

Maximum request size for /cli/compile is 100MB. For larger files, use the presigned upload flow below.

Presigned upload flow

For larger files or more control, upload goes directly to Google Cloud Storage via a presigned URL — the file never transits the xplo API server.

1. Request an upload URL.

curl -X POST https://xplo.pythia.software/api/v2/files/request-upload \
  -H "Authorization: Bearer xplo_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "budget.xlsx", "size_bytes": 82451}'
{
  "file": { "id": "file_...", "name": "budget.xlsx", "version": 1, ... },
  "upload_url": "https://storage.googleapis.com/...",
  "upload_headers": { "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }
}

The upload_url is valid for 15 minutes.

2. PUT the file bytes directly to upload_url, applying every header from upload_headers.

curl -X PUT "<upload_url>" \
  -H "Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" \
  --data-binary @budget.xlsx

3. Confirm the upload to mark the file as ready and record the upload timestamp.

curl -X POST https://xplo.pythia.software/api/v2/files/file_.../confirm-upload \
  -H "Authorization: Bearer xplo_..."

4. Attach it to a file group and start an action (see endpoint reference below).

Downloading a file

curl -X POST https://xplo.pythia.software/api/v2/files/file_.../download \
  -H "Authorization: Bearer xplo_..."

Returns a 15-minute presigned download_url pointing at the original uploaded bytes.

Endpoint reference

Paths are relative to /api/v2. All endpoints require Authorization: Bearer <token>.

Auth

Method Path Description
POST /auth/login Exchange a Firebase ID token for the current user + session info.
GET /auth/me Return the authenticated user.

Users

Method Path Description
PATCH /users/me Update the current user. Body: { "name": "..." }

Organizations

Method Path Description
GET /organizations/{id} Get an organization and its members.
PATCH /organizations/{id} Rename. Body: { "name": "..." }
POST /organizations/{id}/reset Destructive. Deletes all files, file groups, and actions for the org.

Files

Method Path Description
GET /files List files. Query: limit, offset, name_contains.
GET /files/{id} Get a single file's metadata.
DELETE /files/{id} Delete a file. Returns 409 CONFLICT if the file is in any file group.
POST /files/request-upload Create a file record and presigned upload URL. Body: { name, size_bytes, extension? }.
POST /files/{id}/confirm-upload Confirm that bytes have been uploaded to the presigned URL.
POST /files/{id}/download Get a 15-minute presigned download URL.

File groups

Method Path Description
GET /file-groups List file groups. Query: limit, offset.
POST /file-groups Create. Body: { name, file_ids?: [] }.
GET /file-groups/{id} Get, with nested files array.
PATCH /file-groups/{id} Rename. Body: { "name": "..." }
DELETE /file-groups/{id} Delete.
POST /file-groups/{id}/files Add files. Body: { "file_ids": [ ... ] }
DELETE /file-groups/{id}/files Remove files. Body: { "file_ids": [ ... ] }

Actions

Method Path Description
GET /actions List actions. Query: limit, offset, status, file_group_id.
POST /actions Start an analysis. Body: { file_group_id, enable_trace? }. Returns 201 with the new action.
GET /actions/{id} Get an enriched action — includes file group, user, tasks, progress, ETA, and WASM artifact URLs.
POST /actions/{id}/cancel Cancel a running or pending action.
POST /actions/{id}/retry Retry a failed action.
GET /actions/{id}/simulation-config Return the simulation-input configuration for a completed action.

Action status cycles through PENDING → RUNNING → COMPLETED or FAILED. The progress object reports task counts; eta gives a confidence interval for remaining time once enough history is available.

API tokens

Method Path Description
GET /tokens List your tokens (plaintext values are never returned here).
POST /tokens Create. Body: { name, expires_at? }. Plaintext token is returned once.
DELETE /tokens/{id} Revoke.

CLI convenience endpoints

Method Path Description
POST /cli/compile Multipart file= upload that creates file + file group + action in one call (≤100MB).
GET /cli/status/{action_id} Simplified progress poll suitable for scripts.
GET /cli/binary/{action_id} Beta. Download the compiled native binary for an action. Artifacts are not yet persisted across server restarts.
GET /cli/info/{action_id} Beta. Return the cell manifest (input/output metadata) for a compiled action.

Health

Method Path Description
GET /health Returns 200 OK with body OK. No auth.

Object shapes

File

{
  "id": "file_...",
  "organization_id": "org_...",
  "name": "budget.xlsx",
  "extension": "xlsx",
  "version": 1,
  "size_bytes": 82451,
  "uploaded_by": { "id": "usr_...", "name": "Ada Lovelace" },
  "uploaded_at": "2026-04-17T10:00:00Z",
  "created_at": "2026-04-17T09:59:55Z",
  "updated_at": "2026-04-17T10:00:00Z"
}

FileGroup

{
  "id": "fg_...",
  "organization_id": "org_...",
  "name": "Q4 Budget",
  "version": 1,
  "files": [ /* File objects */ ],
  "created_at": "...",
  "updated_at": "..."
}

Action

{
  "id": "act_...",
  "file_group_id": "fg_...",
  "user_id": "usr_...",
  "action_type": "ANALYZE_GRAPH",
  "status": "RUNNING",
  "requested_at": "2026-04-17T10:00:00Z",
  "started_at": "2026-04-17T10:00:01Z",
  "completed_at": null,
  "error_message": null,
  "progress": {
    "total_tasks": 50,
    "completed_tasks": 21,
    "failed_tasks": 0,
    "running_tasks": 3,
    "percent_complete": 0.42
  },
  "eta": {
    "stage": "POST_PARSER",
    "estimated_remaining_ms": 15000,
    "lower_bound_ms": 10000,
    "upper_bound_ms": 25000,
    "confidence": 0.85,
    "estimated_completion_at": "2026-04-17T10:01:30Z",
    "using_historical_data": true
  },
  "wasm": {
    "status": "RUNNING",
    "manifest_ready": false,
    "cell_manifest_url": null,
    "wasm_url": null,
    "js_glue_url": null
  }
}

Limits and notes

  • Uploads to /cli/compile are capped at 100MB. Use the presigned flow for larger files.
  • Presigned upload and download URLs expire after 15 minutes.
  • No per-tenant rate limits are enforced today; treat the API as best-effort and add client-side backoff on 5xx responses.
  • API versioning is in the path (/api/v2). Legacy /api/v1 endpoints are still served but should not be used for new integrations.

Getting help

File bugs and feature requests through the bug form or email contact@pythia.software.