Developer API - REST

The Developer API is the HTTP integration surface for managing PoQ projects and data.

The API is served at https://sapien-poq.up.railway.app/developer/v1.

The OpenAPI spec is machine-readable, and interactive docs are available in the browser.

Authentication

Every request needs a bearer token in the Authorization request header:

Authorization: Bearer poq_live_...

See API keys for key creation and lifecycle. Project and upload endpoints also require the key creator to be an org originator.

Conventions

JSON bodies and responses use camelCase keys.

Errors follow a Stripe-shaped envelope:

{
  "error": {
    "type": "invalid_request_error",
    "code": "project_not_found",
    "param": "projectId",
    "message": "Project not found."
  },
  "requestId": "0192f0a0-..."
}

requestId appears in error bodies and always in the X-Request-Id response header. Include it when reporting failures.

Pagination on GET /projects is keyset-based. Responses include { data, hasMore, nextCursor }. Pass nextCursor as ?cursor= on the next request. limit is 1–100 (default 20).

Endpoints

System

MethodPathSummary
GET/pingVerify the key is valid. Returns { "pong": true }.

Projects

MethodPathSummary
POST/projectsCreate an active project from a JSON spec and name. Returns projectId, specId, specVersion, status.
GET/projectsList projects in the key's org, newest first. Query: limit, cursor.
GET/projects/{projectId}Fetch one project.
PUT/projects/{projectId}Update name, description, and/or paused. At least one field required. Spec and status are immutable.

The spec object on create is a JSON mirror of the poq.toml specification. The project goes live immediately. The spec is compiled and locked at creation.

Upload sessions

MethodPathSummary
POST/projects/{projectId}/upload-sessionsOpen a session. Declare files (relPath, size). Returns presigned PUT URLs.
POST/projects/{projectId}/upload-sessions/{uploadId}/filesAdd files or refresh URLs for an open session. Manifest is merged, never replaced.
GET/projects/{projectId}/upload-sessions/{uploadId}Session status, declared files, verification counters.
PUT/projects/{projectId}/upload-sessions/{uploadId}/fileOne-shot upload: 307 redirect to a presigned URL. Query: path (required), size (optional).
POST/projects/{projectId}/upload-sessions/{uploadId}/processVerify uploads and start the ingest run. Returns runId (202). Optional: dryRun, skipExisting.
GET/projects/{projectId}/upload-sessions/{uploadId}/statusPoll session lifecycle and the latest ingest run.

Upload workflow

File bytes never pass through the Developer API. The API issues presigned PUT URLs; the client uploads directly to object storage.

StepAction
1POST .../upload-sessions with the file manifest (relPath, size in bytes).
2PUT each file's bytes to its uploadUrl (no Authorization header on these PUTs).
3Optionally POST .../files to declare more files or refresh expired URLs.
4POST .../process to verify uploads and start ingest.
5GET .../status until the run reaches complete or failed.

Limits: 5 GiB per file; up to 1000 files per declare call; paths must be relative (no leading /, no ..). Presigned URLs expire after one hour. Request fresh ones via declare-files.

One-shot redirect: PUT .../file?path=data/rows.csv responds with 307 to the presigned URL before reading the body. Works well with curl -T:

curl -T ./data/rows.csv \
  'https://sapien-poq.up.railway.app/developer/v1/projects/{projectId}/upload-sessions/{uploadId}/file?path=data/rows.csv' \
  -H 'Authorization: Bearer poq_live_...' \
  --location

For large files or unfamiliar HTTP clients, prefer the two-step declare + PUT flow.

Error codes

Stable codes are part of the public contract. Renaming or removing a code is a breaking change.

Authentication

CodeHTTPMessage
api_key_missing401Missing or invalid Authorization header.
api_key_malformed401Invalid API key format.
api_key_invalid401Invalid or expired API key.

Request validation

CodeHTTPMessage
parameter_invalid400Invalid request parameter.
request_body_invalid400Invalid request body.
request_body_malformed400Malformed request body.
unknown_endpoint404Unknown endpoint.

Projects

CodeHTTPMessage
api_key_actor_unavailable403This API key has no user authorized to create projects.
not_org_originator403The API key owner is not an originator for this organization.
project_not_found404Project not found.
project_already_active409Project is already active.
project_spec_invalid400The project spec is invalid. (Message carries the specific parse error.)
qualification_catalog_conflict409A qualification field conflicts with the existing catalog.

Upload sessions

CodeHTTPMessage
upload_session_not_found404Upload session not found.
upload_session_not_open409Session is no longer open for new files.
upload_session_not_ready409Cannot start a run (in flight or already complete).
upload_no_files400No files were declared.
upload_file_path_invalid400A declared path is absolute or escapes the session folder.
upload_id_required400An upload session ID is required.
upload_files_missing409Declared files not yet uploaded (message names paths).
project_spec_missing409Project has no compiled spec.

Server

CodeHTTPMessage
internal_error500Internal server error.

See also

PageDescription
MCP serverSame operations as MCP tools
API keysCreation and authorization
poq.toml specificationAuthoring the spec field
Quickstart - APIEnd-to-end walkthrough
Edit this page on GitHub Last updated Jul 14, 2026