REST API reference
The documented api.plori.ai/v1 surface: create agents, submit and track runs, schedules, credits, and the public rate cards. OpenAPI 3.1 spec included.
Everything the dashboard and the MCP tools do runs over one REST API at
https://api.plori.ai/v1. If you are writing code rather than operating an MCP
client, call it directly. The machine-readable contract is the OpenAPI 3.1
document at https://plori.ai/openapi.json; this page is the human version of
the same surface.
Authentication
Account endpoints take a bearer credential:
Authorization: Bearer plori_sk_...
Either a plori API key (plori_sk_…, minted in the dashboard under Settings →
API keys, or by plori login for the CLI) or an OAuth 2.1 access token from
the same flow MCP clients use. The full credential walkthrough, including how an
agent can obtain a key non-interactively, is at
plori.ai/auth.md.
GitHub Actions use a separate workload-identity path: a job mints a GitHub OIDC
JWT for audience plori.ai, exchanges it at POST /v1/github/oidc/exchange,
and receives a short-lived capability for one repository agent run. No Plori
secret or customer account is created by that exchange. The claim URL combines
the verified repository capability with a normal signed-in payer when the free
quota ends or the repository is private.
The main path: create, invoke, read
Create an agent (get-or-create by name, so reruns are safe):
curl -s https://api.plori.ai/v1/agents \
-H "Authorization: Bearer $PLORI_KEY" \
-d '{"name": "my-agent", "if_exists": "return"}'
Send it work. The response is always 202 with the run's ids; the endpoint
submits and returns, it does not block:
curl -s https://api.plori.ai/v1/agents/$AGENT_ID/runs \
-H "Authorization: Bearer $PLORI_KEY" \
-H "Idempotency-Key: hello-1" \
-d '{"message": "Say hello and tell me what tools you have."}'
# -> {"run_id": "...", "session_id": "..."}
Poll the run until it finishes:
curl -s https://api.plori.ai/v1/agents/$AGENT_ID/runs/$RUN_ID \
-H "Authorization: Bearer $PLORI_KEY"
The run object carries status and accounting (credits, tokens, stop reason),
not the reply text. Read the reply where the live surfaces read it: the
session WebSocket (GET /v1/ws, the browser's transport), the CLI
(plori result), or the MCP invoke_agent / get_run_result tools, which can
wait for the answer and return it in one call. If you want request/response
simplicity end to end, MCP is the better integration surface; the REST API is
the submit-and-track layer underneath.
Retries, idempotency, and concurrency
A run is nondeterministic and billable, so never retry blindly. Send an
Idempotency-Key header (or idempotency_key in the body): a retry with the
same key within 24 hours returns the original run (X-Plori-Idempotent-Replay: true) instead of starting a second one. The same key with a different payload
is a 422; a retry while the first request is still in flight is a 409 with
Retry-After.
How many runs an account may have in flight at once is set by its plan (1 Free,
2 Pro, 5 Power, counted across all its agents); past that a run is a 429 with
Retry-After. Running out of credits is a 402 whose body carries
"code": "insufficient_credits".
Endpoints
| Method and path | What it does |
|---|---|
POST /v1/agents |
Create an agent; if_exists: "return" makes it get-or-create by name |
GET /v1/agents |
List your agents |
GET /v1/agents/{agentID} |
One agent, including its warm/sleeping status |
POST /v1/agents/{agentID}/runs |
Submit a run (202 {run_id, session_id}; Idempotency-Key, max_turn_tokens) |
GET /v1/agents/{agentID}/runs |
Run history |
GET /v1/agents/{agentID}/runs/{runID} |
One run's status and cost |
POST /v1/agents/{agentID}/runs/{runID}/cancel |
Cancel an in-flight run (cascades to delegated child runs) |
POST /v1/agents/{agentID}/schedules |
Defer a one-shot run (fire_at RFC3339 or delay_seconds) |
GET /v1/agents/{agentID}/schedules |
List schedules |
GET /v1/agents/{agentID}/schedules/{scheduleID} |
One schedule |
DELETE /v1/agents/{agentID}/schedules/{scheduleID} |
Cancel a pending schedule (returns the updated schedule) |
GET /v1/users/{userID}/credits |
Balance, plan, and purchase options (your own account only) |
POST /v1/github/oidc/exchange |
Exchange a GitHub Actions OIDC JWT for one repository-scoped run capability |
POST /v1/github/claim |
Attach a verified repository and its existing agent workspace to the signed-in account |
GET /v1/github/runs/{runID} |
Poll one GitHub-triggered run's durable AG-UI events (narrow capability or owning API key) |
GET /v1/pricing/models |
Public model rate card (no auth) |
GET /v1/pricing/tools |
Public tool and workflow rate card (no auth) |
Errors share one shape, {"error": "<message>"}, with the two exceptions noted
above (402 adds code and audience). A 500 always answers
{"error": "internal error", "request_id": "<id>"}: the message is fixed (never
parse it), and request_id is the handle to quote at [email protected] so we can
find the failure in our logs.
Endpoints not in this table (files, memory, workflows, billing checkout, …) exist but are internal: the dashboard's private surface, subject to change without notice. If you need one of them in the documented API, tell us: [email protected].
How this page stays accurate
The OpenAPI document is drift-checked in CI against the live route table, so every path and method above is served exactly as written. Pricing numbers live on the rate cards and in the two pricing endpoints, never hand-copied here.