REST API reference
Endpoints, credential classes, scopes, errors, pagination and idempotency for the CrossUpload REST API.
Base URL: https://app.crossupload.com. JSON in, JSON out. Machine-readable spec: /openapi.yaml.
Credential classes
| Credential | Prefix | Who holds it | Can |
|---|---|---|---|
| Server API key | sk_ | Your backend only | Create sessions; read, URL-sign and delete files of its project; read usage. |
| Desktop capability | cu_d_ | Visitor’s browser (one session) | session:read, session:upload, session:manage, events:read |
| Pairing secret | (QR fragment) | QR code | Exchanged once at POST /v1/pair. Not a bearer token. |
| Phone capability | cu_p_ | Paired phone (one session) | session:upload only; sees only its own uploads, never URLs. |
| Project public key | pk_ | Anyone | Identifies a project. Cannot authorize anything. |
Send credentials as Authorization: Bearer ….
Endpoints
| Endpoint | Auth | Purpose |
|---|---|---|
POST /v1/sessions | Server key | Create a constrained session. Supports Idempotency-Key. |
GET /v1/sessions/{id} | Desktop / phone / server key | Current state, files and event cursor. |
DELETE /v1/sessions/{id} | Desktop (manage) | Cancel the session. |
POST /v1/sessions/{id}/pairing | Desktop (manage) | Issue a fresh QR (only before a phone pairs). |
POST /v1/pair | Pairing secret in body | Return an upload-only phone capability. |
POST /v1/sessions/{id}/uploads | Desktop / phone | Reserve slots and get signed POST data. |
POST /v1/uploads/{id}/complete | Matching capability | Hint that bytes were sent; triggers verification. |
POST /v1/sessions/{id}/finish | Desktop / phone | Finalize the batch once files are resolved. |
GET /v1/sessions/{id}/events | Desktop | Authenticated SSE stream. |
GET /v1/files?sessionId= | Server key | Files of one of your sessions. |
GET /v1/files/{id} | Server key | Metadata and status. |
POST /v1/files/{id}/url | Server key | Issue a temporary delivery URL. |
DELETE /v1/files/{id} | Server key | Permanent deletion (?invalidateCdn=true optional). |
GET /v1/usage | Server key | Account and project usage. |
GET /health/live, /health/ready | None | Operational status. |
Headless upload flow
# 1. Reserve a slot (desktop capability)
POST /v1/sessions/ses_123/uploads
{ "files": [{ "clientKey": "a1", "name": "id.jpg", "size": 482113, "type": "image/jpeg" }] }
→ { "uploads": [{ "file": { "id": "file_9", "status": "uploading", … },
"upload": { "method": "POST", "url": "https://…/quarantine", "fields": [["key","q/…"], …], "expiresAt": "…" } }] }
# 2. multipart POST to upload.url with every field in order, then the file as "file".
# 3. Tell us you are done (a hint — we verify independently)
POST /v1/uploads/file_9/complete → 202 { "status": "processing", … }
# 4. Wait for "file.ready" on the SSE stream, or poll GET /v1/sessions/ses_123
Re-sending the same clientKey for a file that hasn’t uploaded yet re-issues credentials without consuming another slot. To retry a rejected file, send retryOf with its ID.
Server-sent events
Native EventSource cannot send an Authorization header, so use fetch() and read the stream. Event types: session.created, file.created, file.uploaded, file.ready, file.rejected, file.failed, phone.paired, phone.batch_sent, pairing.refreshed, session.completed, session.expired, session.cancelled. File events include the current file object. Reconnect with Last-Event-ID (or ?cursor=) to resume.
Conventions
- Errors:
{ "error": { "code", "message", "requestId" } }with a stablecode. Every response has anX-Request-Idheader. - IDs are opaque strings with prefixes (
ses_,file_, …). They identify; they never authorize. - Timestamps are ISO-8601 UTC. Sizes are bytes.
- Dashboard lists use cursor pagination.
- JSON request bodies are limited to 64 KB.
Something unclear or wrong? Email master@crossupload.com.