COG-21: REST API
Status: Draft
Version: 0.1
Created: 2026-10-31
Updated: 2026-10-31
Authors: Mike Anderson
This document specifies the HTTP binding of a venue's core interactions: the routes, what each answers, and what an HTTP status means. What operations, jobs and messages are is specified in COG-7: Operations, COG-8: Jobs and COG-9: Agent Messaging; how callers authenticate in COG-3: Authentication Mechanisms and COG-10: Venue Authentication & Access Control. Other bindings of the same interactions, such as COG-14: A2A Agent Interoperability, have their own specifications.
This standard specifies the REST API through which clients invoke operations on a venue, observe and control the resulting jobs, deliver messages to them, and read and store assets. Its central rules are that an HTTP status reports how the venue's API layer handled the request, never how an operation that the venue accepted turned out, and that each route's statuses are part of its contract, chosen by what the client can do next and not by how the venue fails internally.
Purpose
A client of a venue needs to tell apart three situations that a single error code would conflate:
- The request was refused. It was malformed, unauthenticated, not permitted, or the venue could not take it now. Nothing ran; the client may correct the request or retry.
- The request was accepted and the operation failed. A job exists and records why it failed. The failure belongs to the operation, not to the HTTP exchange.
- The venue failed. Something went wrong that neither the request nor the operation explains.
Status codes carry the first and third; job records carry the second. Keeping them apart lets clients, and the language models acting through them, react correctly: fix the request, read the job, or report a venue fault.
Terminology
See COG-1: Architecture for definitions of Grid terminology. Additional terms used in this specification:
- API layer: The venue's handling of a request before any operation runs: parsing the request, authenticating the caller, admission control, resolving the operation, and checking that the caller may make the request.
- Accepted invocation: An invocation the API layer has admitted, for which the venue has created a job (COG-8).
- Reason: A human-readable statement of what failed and why, complete on its own, suitable for showing to a person or a language model.
Specification
All routes in this specification are relative to the base path /api/v1. Request and response bodies are JSON (Content-Type: application/json) unless a route states otherwise.
Status Codes
Each route in this specification lists the situations it answers and the status for each. A venue MUST answer the status a route specifies when it is in the situation that row describes, and 500 in any situation the route does not describe. A situation is what the client can observe: its request, its own identity and authority, and the state of the resource the route addresses. How a venue represents a failure internally MUST NOT determine a status.
Statuses are deliberately coarse. The reason in the error body says what specifically happened, so a row describes a situation, not every cause that can produce it.
An HTTP status reports the API layer's handling of the request:
- A
2xxstatus means the API layer accepted and answered the request. - A
4xxstatus means the API layer refused the request for a reason the caller can address. - A
5xxstatus means the venue could not handle the request: it failed, or a remote party it relayed to failed, or it is temporarily unavailable.
Once an invocation is accepted, how its operation ends is the job's outcome (COG-8). A venue MUST NOT report the failure of an accepted invocation as an API-layer refusal. In particular, an authority check that an operation makes while it runs, and that fails, fails the job; it is not a 403 response. The /run route, which returns the operation's result in place of a job, answers a failed job as specified there.
The statuses have the following meanings in every route, each stated as what the client can do:
| Status | Meaning |
|---|---|
| 200 OK | The request was answered; the body is the route's result |
| 201 Created | The request created a resource, named by the Location header |
| 202 Accepted | The request was queued; its effect is observed through the job |
| 400 Bad Request | The request must change before it can succeed: it is malformed, or names something it cannot use, such as an operation that does not resolve |
| 401 Unauthorized | The caller must authenticate: the request carries no credential where one is required, or its credential is invalid |
| 403 Forbidden | The caller is identified and may not make this request; repeating it will not help |
| 404 Not Found | The resource the route addresses, such as a job or an asset, does not exist for this caller |
| 406 Not Acceptable | The route cannot produce a representation the Accept header allows |
| 409 Conflict | The resource the route addresses is in a state that does not allow the request, such as a finished job; repeating it will not help until that resource changes |
| 429 Too Many Requests | The caller must slow down; Retry-After gives the wait in seconds |
| 500 Internal Server Error | The venue failed; the request is not known to be at fault |
| 502 Bad Gateway | A remote venue or service that the venue relayed the request to failed or gave an unusable answer |
| 503 Service Unavailable | The venue cannot handle the request now and may later; the client may retry |
A 404 MUST NOT be used to report that an operation, asset or other reference named inside a request body does not resolve: the route itself exists, and the request is a 400.
Where a route allows both 409 and 503, the choice turns on whether the situation can clear while the addressed resource stays as it is: if it cannot, the answer is 409; if it can, 503.
Error Responses
Every 4xx and 5xx response carries a JSON error body:
{"error": "<reason>"}
The reason MUST say what failed and why. It MUST NOT expose implementation internals such as stack traces. A 500 reason MAY name the kind of failure that occurred, since it reports a venue fault rather than something the caller did.
A 401 response MUST carry a WWW-Authenticate: Bearer header. A 429 response MUST carry a Retry-After header.
Authentication and Admission
Callers present credentials as specified in COG-3. The API layer applies them before any route runs:
| Condition | Response |
|---|---|
| No credential, venue allows public access (COG-10) | The request proceeds as the venue's public principal |
| No credential, venue does not allow public access | 401 |
| Credential presented but invalid | 401; a venue MUST NOT fall back to the public principal |
| Credential valid, venue refuses to admit the caller | 403 |
| Caller exceeds the venue's request rate or credential-failure limits | 429 |
Admission control for invocations (COG-1, COG-8) runs before a job is created. A refused invocation creates no job:
| Condition | Response |
|---|---|
| Caller has reached the venue's limit on concurrent jobs | 429 |
| Venue is shutting down | 503 |
Invocation
Invoke
POST /api/v1/invoke
Invokes an operation and returns its job. The request body is the invoke request of COG-8: operation, input, wait and ucans. wait MAY also be given as a query parameter, which takes precedence over the body.
| Status | Condition | Body |
|---|---|---|
| 201 Created | Invocation accepted, no wait | Job record; Location: /api/v1/jobs/{id} |
| 200 OK | wait given and the job reached COMPLETE within the window | Job record |
| 201 Created | wait given and the job did not reach COMPLETE within the window, whether it is still running or has ended FAILED, CANCELLED or REJECTED | Current job record; Location as above |
| 400 | Body is not JSON; operation is missing or not a string; wait is malformed; private is true, since an invocation always records its job; operation does not resolve or is not an operation; input does not satisfy the operation's input schema | Error |
| 403 | The API layer refuses the caller this invocation | Error |
| 429 | Admission refused | Error |
| 502 | A remote operation definition could not be fetched | Error |
| 503 | Venue shutting down | Error |
An accepted invocation always answers 200 or 201 with the job record, whatever the job's status. A client reads a failed operation's reason from the job record's error field.
A venue MUST validate wait before creating a job, so that a malformed wait creates none.
Run
POST /api/v1/run
Runs an operation and returns only its result. The request body carries operation, input, ucans and an optional boolean private; private: true asks for an unrecorded job where the venue supports it (COG-8). Responses MUST carry Cache-Control: private, no-store, since a result may contain material the caller alone may see.
Because the response body is the operation's result, a failed job cannot be answered with a job record. A venue answers it with an error status whose reason is the job's error:
| Status | Condition | Body |
|---|---|---|
| 200 OK | The operation completed | The operation's output |
| 400 | The request is malformed, operation does not resolve or is not an operation, or private runs are requested and not supported | Error |
| 400 | The job failed with a reason, an authority check inside the operation included | Error; the reason is the job's error |
| 403 | The API layer refused the caller this run before the operation started | Error |
| 429 | Admission refused | Error |
| 500 | The job failed unexpectedly | Error |
| 502 | A remote operation definition could not be fetched | Error |
| 503 | Venue shutting down | Error |
A 403, 429, 502 or 503 from /run means no operation ran. A 400 or 500 says the job failed; it does not guarantee that the operation had no side effects before it failed.
Jobs
List Jobs
GET /api/v1/jobs?offset={n}&limit={n}
Answers 200 with the caller's jobs as a page: {"items": [<job id>...], "total": <n>, "offset": <n>, "limit": <n>}. A malformed offset or limit is a 400.
Job Status
GET /api/v1/jobs/{id}
| Status | Condition | Body |
|---|---|---|
| 200 OK | The caller may read the job | Current job record (COG-8) |
| 400 | {id} is not a job ID | Error |
| 403 | The caller may not read this job | Error |
| 404 | No such job for this caller | Error |
A caller MAY present delegation proofs for a job it does not own in the X-Covia-Ucans header, since the request has no body.
Job Events
GET /api/v1/jobs/{id}/sse
Answers 200 with a text/event-stream of the job's current records (COG-8); the stream closes after the record that reaches a terminal status. The statuses of Job Status apply before the stream opens, and 406 answers an Accept header that admits neither text/event-stream nor */*. If the job becomes unreadable after the stream opens, the venue sends an error event carrying an error body and closes the stream.
Message Delivery
POST /api/v1/jobs/{id}
Delivers a message to the job's queue (COG-9). A body that is not a JSON object is delivered as {"content": <body>}.
| Status | Condition | Body |
|---|---|---|
| 202 Accepted | The message was queued | {"status": "queued"} |
| 400 | {id} is not a job ID, or the body is not JSON | Error |
| 403 | The caller may not message this job | Error |
| 404 | No such job for this caller | Error |
| 409 | The job does not take messages in its current state, or at all; the reason says which | Error |
| 502 | The job mirrors a job on a remote venue, which did not acknowledge the message; whether it was delivered is unknown | Error |
| 503 | The venue cannot take messages for the job now, and may later | Error |
202 does not mean the message has been processed. Clients observe its effect through the job.
Job Control
PUT /api/v1/jobs/{id}/cancel
PUT /api/v1/jobs/{id}/pause
PUT /api/v1/jobs/{id}/resume
PUT /api/v1/jobs/{id}/delete
Control a job as specified in COG-8. cancel accepts an optional body {"reason": "<text>"}, which becomes the job's error.
| Status | Condition | Body |
|---|---|---|
| 200 OK | cancel, pause or resume applied, or cancel on a job already finished | Current job record |
| 200 OK | delete removed the job | Empty |
| 400 | {id} is not a job ID, or a cancel body is not JSON | Error |
| 403 | The caller may not control this job | Error |
| 404 | No such job for this caller | Error |
| 409 | The job's state does not allow the request: pause or resume where COG-8 refuses it, or any control of a job that has not finished but that this venue is no longer running | Error |
Assets and Content
Asset Metadata
GET /api/v1/assets/{ref}
POST /api/v1/assets
GET answers 200 with the asset's metadata (COG-5) and an ETag of its asset ID. {ref} is any asset reference: an asset ID, a catalog path or a DID URL. A bare asset ID names an asset in the caller's own namespace. An asset that does not exist is a 404; one the caller may not read is a 403.
POST stores the request body as metadata of an asset in the caller's namespace and answers 201 with the asset ID as a JSON string and Location: /api/v1/assets/{id}. A body that is not valid metadata is a 400; a caller who may not store assets, the unauthenticated caller included, receives 403.
Content
GET /api/v1/content/{ref}
PUT /api/v1/assets/{id}/content
GET answers 200 with the content of the asset that {ref} names, streamed, with the content type its metadata declares. An asset that does not exist, declares no content, or whose content is not held is a 404; a caller who may not read it receives 403.
PUT stores the request body as the content of asset {id} and answers 200 with the content's SHA-256 hash as a JSON string. The asset's metadata MUST declare the content's sha256, and the venue MUST refuse content that does not match it.
| Status | Condition |
|---|---|
| 400 | {id} is not an asset ID; the metadata declares no valid sha256; the content does not match it or exceeds the venue's size limit |
| 403 | The caller may not store content for this asset |
| 404 | No such asset, or its metadata declares no content |
Venue Status
GET /api/v1/status
Answers 200 with the venue's identity, version, statistics and access policy. The route is available to anonymous callers whether or not the venue allows public access; an invalid credential is still a 401.
Security Considerations
Reasons and Disclosure
Reasons are written for the caller. A venue MUST NOT place credentials, secret values, or another principal's data in a reason.
Refusal Before Execution
API-layer refusals happen before a job is created. Every 4xx answer to /invoke, and a 403, 429, 502 or 503 answer to /run, therefore means that no operation ran and no side effect occurred, and the client MAY retry without risk of duplicating an effect. A 400 or 500 answer to /run gives no such guarantee: it may report a job that failed after its operation had side effects.
Related Specifications
- COG-3: Authentication Mechanisms — credentials and the UCAN proof channel
- COG-7: Operations — what an invocation runs
- COG-8: Jobs — the job lifecycle and job record
- COG-9: Agent Messaging — message semantics
- COG-10: Venue Authentication & Access Control — public access and admission
- COG-13: Agent Capabilities — authority checks