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/compileare 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/v1endpoints 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.