Skip to content
OpenComputer
Build an application

Management API

Create sessions, send turns, read the event log and manage projects over HTTP

The management API is the HTTP surface behind the CLI, the dashboard, the SDK helpers and the React hook’s proxy routes. Use it from trusted server code to run agents from your own application.

Base URL and authentication

https://app.opencomputer.dev/api/managed-agents

Every request carries an API key in the x-api-key header. A key belongs to an organization and reaches every project, agent and session in it. Keep it on the server; the React integration shows how a browser attaches through your own routes without seeing the key.

Your server must authenticate callers and enforce which projects, environments, agents and sessions they may access. A list filter or session label is not an authorization boundary.

Request and response bodies are JSON. Paths below are relative to the base URL; <p> is a project ID and <r> a memory resource ID. The TypeScript client on @opencomputer/sdk/agents mirrors these routes one to one.

Errors

Error bodies are { error: { code, message } }. The code is stable and is what to branch on; the message is short and safe to show.

StatusMeaning
400Invalid body, query or header
401Missing or invalid API key
402insufficient_credits: the organization is out of minutes or credits; error.haltReason says why and error.actionUrl links Billing
403The key may not perform this operation
404The route or the target does not exist
409The request conflicts with current state, for example a reused Idempotency-Key
429Too many requests; retry after Retry-After
5xxTemporary failure; retry with the same idempotency key

A missing key and an unknown route return { "error": "<text>" } without a code.

Sessions

A session is a durable conversation with one deployed agent. It keeps the deployment it was created on; see Sessions and turns.

OperationMethod and pathSuccess
CreatePOST /sessions201 with the session, 200 when the key had already created it
GetGET /sessions/<id>200 with the session
ListGET /sessions200 with { sessions, nextCursor }: one page of rows
LabelsPATCH /sessions/<id>/labels200 with the session
EndPOST /sessions/<id>/end200 with the ended session
InterruptPOST /sessions/<id>/interrupt200 with the session
Dismiss a questionPOST /sessions/<id>/questions/<questionId>/dismiss200; see Questions

Create

curl -X POST 'https://app.opencomputer.dev/api/managed-agents/sessions' \
  -H 'x-api-key: <api-key>' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: topic/workshop/1' \
  -d '{
    "agentId": "<agent-id>@development",
    "labels": { "topic": "workshop", "requested_by": "u-42" },
    "memory": {
      "notes": { "scope": "document", "id": "workshop", "access": "read-write" }
    }
  }'
FieldMeaning
agentId<agent-id>@development or <agent-id>@production. The alias selects the environment and the deployment active in it. A bare agent ID means production. This is how applications address an agent; which deployment runs the session is the platform’s choice, recorded on the session.
deploymentIdAdvanced: pin one deployment instead of resolving an alias, for reproducibility or testing. Send environment with it; it may be omitted only when the deployment is promoted to exactly one environment.
environmentdevelopment or production for legacy projects; default for single-environment projects. Optional with agentId; it must agree with the alias.
memoryBindings keyed by resource ID, at most eight. Needs a deployed agent and an environment. Shapes on Document memory.
sourceapi (default), playground or webhook. The dashboard groups sessions by it.
labelsYour own metadata on the session, an object of strings; see Labels. Applied at creation and ignored on a replay of the same Idempotency-Key.
externalReferenceYour own reference for the session, such as the id of the record it works for: a string of 1 to 256 bytes of UTF-8 without control characters. Stored as given and never interpreted; it is not shown to the agent, grants nothing and does not replace the session id or the Idempotency-Key. Returned on the session, its list row and every session.* event; see External references.

The response is { session: { id, executionMode, status, createdAt, externalReference? }, deployment }. The session starts without a turn; send one with the turns route.

Idempotency-Key (at most 256 characters) makes a retry safe. The key identifies the session within your organization:

  • The same key with the same request, meaning the same agentId (or the same pinned deploymentId), environment and memory bindings, returns the existing session with 200, together with the deployment it pinned when it was first created. That holds after a redeploy or a rollback: the session keeps the deployment it started with, and your retry does not need to know which one that was. A new session under a new key gets the deployment active at that time.
  • Anything else under that key is 409 idempotency_conflict: another agent, environment or pinned deployment, different memory bindings, or a different externalReference (including adding or dropping one). Pinning the deployment the session already holds counts as the same request.
  • Two identical requests under one key that arrive together create one session; both answers name it, one with 201 and the other with 200.
  • Sessions created before this rule was recorded keep the earlier one: a replay through an alias succeeds only while the alias still resolves to the deployment the session pinned.
  • 503 memory_admission_unconfirmed means the memory grants were not confirmed in time. Retry with the same key; the retry resumes the wait.
  • 503 session_publication_unconfirmed means the session exists but its row in the list was not confirmed in time; error.sessionId names it. Retry with the same key and the same body until the answer is 201 or 200. A success means GET /sessions reflects the session; the platform keeps publishing the row in the background either way.

Other codes: 400 invalid_environment, 400 invalid_memory_binding, 400 invalid_labels, 400 invalid_external_reference, 404 deployment_not_found, 409 deployment_not_promoted (a pinned deployment is not active in the named environment), 402 insufficient_credits.

Get and list

GET /sessions/<id> returns the session:

FieldMeaning
id, agentId, deploymentIdThe session and the deployment it runs on, chosen when it was created
projectIdThe project the agent belongs to
environmentdevelopment or production, when the request named one
statusnew, connecting, idle, running, stopping, waiting_runtime, suspending, suspended, resuming, failed or ended
sourceThe source given at creation
labels, labelsUpdatedAtYour metadata, and when it last changed; see Labels
externalReferenceThe reference given at creation; absent when none was given
memoryBindings with resource, scope, id, access and writable; present when the session was created with bindings
turnsEvery turn: id, input, mode, status, createdAt, updatedAt; outcome: "question" on a completed turn that ended by asking; and deliveries when an event subscription selected its outcome
resultThe latest committed output of the agent’s result tool, or null: { turnId, callId, reportedAt, data }. turnId and callId name the call that reported data, so a reader can tell which turn it came from and whether a later turn changed things without reporting. A failed or cancelled turn never clears it.
questionThe open question the agent asked with ask, or null: { id, text, options, askedAt }, options an array of { label, value }, empty for free text. Independent of result.
revisionA counter that increases with every change to the session’s status, turns, labels or result
createdAt, updatedAtTimestamps

GET /sessions returns one page of rows, { sessions, nextCursor }, filtered by the query:

curl 'https://app.opencomputer.dev/api/managed-agents/sessions?project=<project-id>&environment=development&label.topic=workshop&limit=50' \
  -H 'x-api-key: <api-key>'
ParameterMeaning
project or projectIdA project id or slug
environmentdevelopment or production
agent or agentIdAn agent id
statusOne session status
deploymentIdA deployment id
externalReferenceSessions created with exactly this external reference
createdAfter, createdBeforeRFC 3339 timestamps: sessions with createdAt >= createdAfter and createdAt < createdBefore
updatedAfterAn RFC 3339 timestamp: sessions with updatedAt >= updatedAfter
label.<key>=<value>Sessions whose label <key> equals <value> exactly; at most three
limitRows per page, 1 to 100; default 50
cursorThe nextCursor of the previous page

Every filter is an exact match; there is no prefix, substring or wildcard search. Filters combine with AND. Naming both spellings of a parameter (project and projectId, agent and agentId), any other parameter or a malformed value is 400 invalid_query.

Filters select among the sessions the API key can already see: an organization key sees the organization’s sessions, a project-scoped key only its project’s, and a filter naming another project is 403 project_scope_violation. A development filter never returns Production sessions, and a reference created in one project is not found from another.

A row is id, projectId, agentId, deploymentId, environment (null when the session has none), source, status, labels, externalReference (when one was given), createdAt, updatedAt, revision, activity and result. activity is { activeTurnId, queued, lastSettledTurn }: the running turn’s id or null, how many turns wait behind it, and the most recently finished turn as { id, status, at } or null. Rows carry no turns, no memory and no prompt or message text; read GET /sessions/<id> for those.

Rows are ordered by createdAt, newest first, then by id, so sessions created in the same millisecond have a fixed order and none is skipped. Updates do not move rows. nextCursor is an opaque string, valid only with the filters of the page that returned it: sending it with different filters is 400 invalid_cursor, and so is a cursor that cannot be read. limit may change between pages. null means there are no more matches after that cursor.

Pagination is not a snapshot: each request applies the filters to the current rows. New sessions and sessions that newly match a status or label filter can fall before your cursor. Refresh from the first page to see them. Sorting fetched rows by updatedAt orders only those rows, not all matching sessions. A successful create response confirms the session is listed.

External references

externalReference is one opaque string your application chooses when it creates a session, for example the id of the order, ticket or job the session works for. It is 1 to 256 bytes of UTF-8 with no control characters; anything else is 400 invalid_external_reference. Omitting it, or sending null, creates a session without one; it cannot be changed afterwards, and it is not required to be unique.

The platform stores the reference as given. It is not sent to the agent or the model, does not grant access, and does not replace the session id or the Idempotency-Key. Keep your own mapping from reference to session id as the authoritative record; the reference is there for recovery and audit when that record is incomplete:

  • If a create response was lost, retry with the same Idempotency-Key first; it returns the same session with 200. The reference makes a second recovery path available: GET /sessions?externalReference=<ref> finds the session however many unrelated sessions were created since.
  • A retry of the key with a different reference is 409 idempotency_conflict, so two records can never share one session by accident.
  • The reference is returned on the session, on its list row and in the data of every session.* event, so a consumer of the event log can route each event to the record it belongs to without another lookup.

A reference is metadata, not a credential: do not put secrets, tokens or signed URLs in it. It is visible to every API key that can read the session.

Labels

Labels are your own metadata on a session: at most 16 keys, each matching ^[a-z][a-z0-9_.-]{0,63}$, each value at most 256 characters, 4 KB in all. They filter the list and appear on the session and its row. They are not permissions, are not searched beyond equality, and are never shown to the agent; give the agent its context in the turn input.

PATCH /sessions/<id>/labels changes them and returns the session:

curl -X PATCH 'https://app.opencomputer.dev/api/managed-agents/sessions/<session-id>/labels' \
  -H 'x-api-key: <api-key>' \
  -H 'Content-Type: application/json' \
  -d '{ "set": { "outcome": "merged" }, "unset": ["requested_by"] }'

unset removes keys, then set writes keys; each key takes the last write. The session’s labelsUpdatedAt records the change. A patch that would exceed the bounds, or a key or value that does not fit them, is 400 invalid_labels and changes nothing. Labels can be changed on an ended session.

A 200 means the list reflects the change: the session’s row carries the new labels and the filters see them. 503 session_publication_unconfirmed (with error.sessionId) means the labels are recorded on the session but the row was not confirmed in time; retry the same patch until it answers 200. The platform keeps publishing the row in the background either way, and repeating the patch is safe because each key takes the last write.

End and interrupt

POST /sessions/<id>/end ends the session. Queued and running turns are marked cancelled with reason session_ended, and no further turns are accepted. A 200 confirms memory write access is revoked. 503 session_end_unconfirmed means the session ended but memory revocation was not acknowledged; retry the same end request, or freeze the document. Repeating end confirms any pending memory revocation without ending the session again.

The session.ended event is recorded before memory revocation is confirmed. Neither that event nor the end response waits for commands to stop; remote cleanup continues in the background. Interrupt provides command-settlement confirmation for the current turn, subject to the compatibility note below; it leaves queued turns available to run.

POST /sessions/<id>/interrupt stops the running turn without spending a model turn. The response acknowledges the stop request; it does not wait for completion. While stopping, the session is stopping and the turn remains running. Once the model loop and its commands have stopped, the turn becomes cancelled with reason interrupted, the session becomes idle, and the next queued turn can start.

If a command cannot be confirmed stopped, OpenComputer terminates its computer before settling the turn. The next command uses a fresh computer with the session’s workspace. The cancellation event records the settlement details. An idle session is returned unchanged. Stopping does not undo files already written or external effects.

Sessions created with executionMode: "microvm" cancel immediately on interrupt, without waiting for their commands to stop. They do not enter stopping, and their cancellation events have no settlement fields. Check the creation response when this guarantee matters to your app.

Turns

curl -X POST 'https://app.opencomputer.dev/api/managed-agents/sessions/<session-id>/turns' \
  -H 'x-api-key: <api-key>' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: message-42' \
  -d '{
    "input": "Plan the workshop.",
    "mode": "queue",
    "payload": { "workshopId": "ws_812", "audience": "backend" }
  }'

Idempotency-Key is the one rule on both routes: it identifies this request, a repeat returns the existing turn, and a different request under the same key is refused (below). Without one every request starts a turn. The body field idempotencyKey carries the same key for a send that a browser makes through your own server, where the header is the server’s to set; when both are present they must be equal, else 400 invalid_turn.

FieldMeaning
inputThe user text for this turn. Required, not empty.
idempotencyKeyOptional. The key in body form, for a proxied browser send; equal to the header when both are present.
modequeue (default): run after earlier turns. steer: deliver into the running turn when the runtime supports it, otherwise queue. interrupt: interrupt running turns and run next, with the same settlement behavior as the interrupt route.
payloadOptional. A JSON value for the agent, at most 32 KB encoded. It reaches the agent as useInput().payload with source: "user", next to the text, and is recorded on the turn’s message.received event. The text stays the message; the payload is structured context the agent reads.
answersOptional. The id of the session’s open question. The input answers it, and the agent reads it as useInput().answer.

The response is 202 { turnId, status, duplicate } for a new turn and 200 with duplicate: true when the key had already created one. status is the turn’s status: queued or running for a new turn, and for a repeated key whatever the existing turn has reached, completed, failed or cancelled once it has settled. Follow the turn in the event log; the response does not wait for it.

A turn sent to a suspended session resumes it: the turn is queued while the session is resuming, and runs once the runtime reconnects. There is no need to resume the session first.

The key identifies the request, not only the call. A repeat with the same input, payload and mode is the existing turn: key order inside the payload does not matter, an omitted mode means queue, and whitespace in the text counts. The same key with anything else is 409 idempotency_conflict and admits nothing. A retry after a lost reply is therefore safe with the same body, and a different request never lands on another request’s key.

Codes: 400 invalid_turn, 400 invalid_payload (over 32 KB), 402 insufficient_credits, 409 idempotency_conflict, 409 question_stale, 409 memory_admission_pending (retry the session creation with its key first), 409 memory_admission_rejected (the session is ended; create a new one).

Questions

While the session has an open question, a turn sent without answers is held rather than run: the response is 202 { status: "held", questionId, duplicate } with no turnId, and 200 for a repeated key. The held input is delivered with the answer, in order, as the answering turn’s steering, up to a bounded number and 64 KiB; the rest run as ordinary turns after it. A repeat of its key after delivery returns the receipt of the turn that carried it; after a Linear stop, it returns status: "discarded". A turn already queued when the question was asked keeps its turnId and settles as cancelled with reason held and the questionId.

A turn with answers resolves the question and runs as the answer, in one step, idempotent on its key; the same key with a different answers is 409 idempotency_conflict. Naming a question that is not the open one, or answering when none is open, is 409 question_stale; read the session and answer its current question.

POST /sessions/<id>/questions/<questionId>/dismiss closes the open question with reason dismissed; its held inputs run as ordinary turns, in order. Ending the session closes it with ended. Interrupt does not close it.

Start work from an application

An application that turns a form submission into agent work makes two calls, each with its own key, and keeps what it sent until the second one is acknowledged:

  1. POST /sessions with Idempotency-Key: <submission-id> and agentId: <agent-id>@<environment>. The platform chooses the deployment active in that environment and records it on the session; a retry under the same key returns that session, even if the alias has moved since. The application never discovers, verifies or stores a deployment id.
  2. POST /sessions/<id>/turns with Idempotency-Key: <submission-id>/start, the text and the payload.

Keep the submission (its id, the text and the payload) until the turns call answers, with duplicate: false or true. A lost reply at either step is retried with the same body and the same key: the first step returns the same session, the second the same turn. A session that exists without a turn is work that was never admitted; only the holder of the submission can retry it. A 409 idempotency_conflict from either call means a different request already used that id; show it, keep the draft, and do not append the submission to a session it did not create. Each later message is its own submission with its own key.

Events

curl 'https://app.opencomputer.dev/api/managed-agents/sessions/<session-id>/events?after=0' \
  -H 'x-api-key: <api-key>'

GET /sessions/<id>/events?after=<seq> returns { events }: up to 500 events with seq greater than after, in ascending order. Each event is { id, seq, timestamp, sessionId, turnId, type, data }; turnId is absent on session-level events.

To read a whole log, start at after=0 and repeat with the last seq you received until a page is empty. A full page means more may follow; read on without waiting. To follow a live session, keep polling from the last seq; the CLI’s sessions tail and useAgent do exactly this. The log is durable, so a consumer that stops can resume from its cursor and miss nothing. Event types and their data are listed on Session events.

Memory

Document memory belongs to a project and an environment. Every route takes ?environment=development or ?environment=production. Bodies, conditional headers, the document object and the error codes are on Document memory.

OperationMethod and pathSuccess
Resource inventoryGET /projects/<p>/memory200 with { resources }
List documentsGET /projects/<p>/memory/<r>/documents200 with { documents, nextCursor }
ReadGET /projects/<p>/memory/<r>/documents/<id>200 with the document and ETag
CreatePUT /projects/<p>/memory/<r>/documents/<id> with If-None-Match: *201
Replace text or summaryPUT /projects/<p>/memory/<r>/documents/<id> with If-Match200
Change title or policyPATCH /projects/<p>/memory/<r>/documents/<id> with If-Match200
DeleteDELETE /projects/<p>/memory/<r>/documents/<id> with If-Match204
curl -X PUT 'https://app.opencomputer.dev/api/managed-agents/projects/<project-id>/memory/notes/documents/workshop?environment=development' \
  -H 'x-api-key: <api-key>' \
  -H 'Content-Type: application/json' \
  -H 'If-None-Match: *' \
  -d '{ "title": "Workshop notes" }'

oc.sessions.startOnDocument on the TypeScript client does this create and the session create in one call.

Webhooks

Webhook configuration lives under the project; invocation uses the separate /api/agent-webhooks/<id>/<token> URL described on Agent webhooks.

OperationMethod and pathBody or querySuccess
ListGET /projects/<p>/webhooksOptional ?environment= and ?agentId=200 with { webhooks }, URLs without tokens
CreatePOST /projects/<p>/webhooks{ name, agentId, environment, identity? }201 with { webhook }; token and the full invocationUrl appear once
UpdatePATCH /projects/<p>/webhooks/<id>Any of name, enabled, identity (null removes it)200 with { webhook }
Rotate tokenPOST /projects/<p>/webhooks/<id>/rotate-tokenNone200 with { webhook } carrying the new token
DeleteDELETE /projects/<p>/webhooks/<id>None204
Request ledgerGET /projects/<p>/webhooks/<id>/requestsNone200 with { requests }

identity is header:<name> or body:<json-pointer>. Creating a webhook for an agent that is not deployed in the environment is 409 webhook_target_unavailable.

curl 'https://app.opencomputer.dev/api/managed-agents/projects/<project-id>/webhooks/<webhook-id>/requests' \
  -H 'x-api-key: <api-key>'

Event subscriptions

An event subscription delivers the recorded outcome of turns run by agents in a project to a session in the same project, as a new turn of that session. It is how one agent learns that another finished: a coordinator subscribes to its workers’ outcomes and reasons about them when they arrive. Destinations are sessions only; there is no public HTTPS destination, and this is not the outbound webhooks feature.

OperationMethod and pathSuccess
CreatePOST /projects/<p>/event-subscriptions201 with { subscription }
ListGET /projects/<p>/event-subscriptions200 with { subscriptions }
GetGET /projects/<p>/event-subscriptions/<id>200 with { subscription }
DeleteDELETE /projects/<p>/event-subscriptions/<id>204; pending deliveries stop
curl -X POST 'https://app.opencomputer.dev/api/managed-agents/projects/<project-id>/event-subscriptions' \
  -H 'x-api-key: <api-key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "agentId": "worker",
    "events": ["turn.completed", "turn.failed"],
    "destination": { "type": "session", "sessionId": "<coordinator-session-id>" },
    "environment": "development"
  }'
FieldMeaning
agentIdOptional. Outcomes of this agent only; omitted, every agent in the project.
eventsOne or more of turn.completed, turn.failed, turn.cancelled. Turn outcomes, not session ends.
destination{ type: "session", sessionId }: a session of an agent in this project that can still take turns.
environmentdevelopment or production. A subscription is scoped to one environment: it selects outcomes recorded there and its destination session runs there.

A subscription is immutable; { subscription } carries id, projectId, the fields above and createdAt. Codes: 400 invalid_event_subscription, 403 project_scope_violation (the agent is outside the project), 404 destination_session_not_found, 409 destination_session_ended, 404 event_subscription_not_found.

Delivery and receipts

When a selected turn settles, the source session records one delivery per matching subscription and starts a turn on the destination session with source: "event" input; the receiving agent reads it with useInput() (Event input). The destination turn’s idempotency key is <subscription-id>:<event-id>, so a retried delivery never starts a second turn. Deliveries are retried with backoff and stop when the subscription is deleted or the destination has ended.

GET /sessions/<id> on the source session lists each turn’s deliveries:

FieldMeaning
id<subscription-id>:<event-id>
subscriptionId, eventId, eventTypeWhat was delivered, to which subscription
destinationThe subscription’s destination
statuspending, delivered or failed
attemptDelivery attempts so far
receipt{ sessionId, turnId }: the turn the destination admitted, once delivered
nextAttemptAtWhen a pending delivery is retried
errorsubscription_unavailable, target_missing, target_ended, or delivery_failed

The delivered event names identifiers and the outcome; a completed turn’s final message is included, bounded to 16 KB. The receiving agent should treat that text as data about another agent’s work, not as instructions.

Linear connections

A Linear connection binds a Linear app in your workspace to one agent in one environment. The dashboard drives these routes. A project-scoped key may list its own project’s connections; every other route needs an organization API key and is 403 forbidden otherwise.

OperationMethod and pathBody or querySuccess
CreatePOST /projects/<p>/linear/connections{ name, environment, agentId }201 with { connectionId, webhookUrl, createAppUrl, connection }; 200 when a pending connection for that binding already exists
ListGET /projects/<p>/linear/connectionsOptional ?environment=200 with { connections }
Set credentialsPUT /linear/connections/<id>/credentials{ clientId, clientSecret, signingSecret }, as copied from the Linear app200 with { connection }
AuthorizePOST /linear/connections/<id>/authorizeNone200 with { authorizeUrl, expiresAt }
DisconnectDELETE /linear/connections/<id>None200 with { connection, revoked }

createAppUrl opens Linear’s create-app page prefilled with the name, the callback URL and webhookUrl. Creating again for a pending binding resets it with the new name and a new webhookUrl, so an earlier createAppUrl stops working. webhookUrl contains the connection’s secret; treat it as a credential. authorizeUrl is Linear’s consent page for the app; a workspace admin completes it, and Linear returns to OpenComputer’s callback, which stores the tokens and redirects to the project’s Connections tab. revoked on a disconnect says whether Linear confirmed the token revocation.

A connection is { id, projectId, environment, agentId, name, status, clientId, appUserId, organizationId, webhookUrl, createAppUrl, verifiedAt, verificationError, lastEventAt, teams, health, revision, createdAt, updatedAt }. It never carries a secret or a token. status is pending, connected, disconnected or revoked. health is { state, message, lastEventAt? }, with state one of awaiting_credentials, awaiting_authorization, waiting_for_first_delegation, receiving or revoked. teams is { allPublic, teamIds }, the teams the app can see.

CodeMeaning
400 invalid_environment, 400 invalid_agentenvironment must be development or production; agentId must name an agent of the project
400 invalid_linear_app_nameThe name must be 1 to 64 characters
400 linear_app_name_reservedLinear does not allow app names that contain “Linear”
400 invalid_linear_credentialsclientId, clientSecret and signingSecret are all required
403 forbiddenA project-scoped key on a route other than List
404 project_not_foundNo such project for this key
404 linear_connection_not_foundNo such connection, or it was disconnected
409 project_archivedRestore the project before creating a connection
409 linear_already_connectedThe agent already has a connection in this environment; error.connectionId names it
409 linear_app_already_connectedThat Linear app is connected to another agent or environment; create a separate app
409 linear_connection_connectedThe connection is authorized with a different Linear app; disconnect it first
409 linear_credentials_requiredSet the credentials before authorizing
409 linear_connection_changedAnother request changed the connection; list the connections and continue; error.connectionId names it
503 linear_connector_unavailableLinear connections are not available right now

Projects, agents and deployments

OperationMethod and pathSuccess
List projectsGET /projects200 with { projects }
Create projectPOST /projects with { name, slug?, environmentMode? }201 with the project. environmentMode is single (one default environment) or legacy (Development and Production)
Get projectGET /projects/<p>200 with { project, deployments, sessions, connections, schedules }
List agentsGET /agents200 with { agents }: id, name, activeAlias, activeDeploymentId, deploymentCount; activeAlias and activeDeploymentId are null while the agent has no active deployment
List deploymentsGET /deployments?agentId=<agent-id>200 with { deployments }
Get deploymentGET /deployments/<id>200 with id, agentId, alias, memory declarations, createdAt
List GitHub repositoriesGET /projects/<p>/github/repositories?environment=<env>200 with { repositories, nextCursor }; see GitHub App connections

A project is { id, slug, name, environmentMode, environments, agents, createdAt, updatedAt }; agents carries each agent’s id and name. Single-environment projects have one environment named default; requests that name development resolve to it, and requests that name production fail with 409 single_environment_project. See Projects and agents for how projects are created and linked from the CLI.

curl 'https://app.opencomputer.dev/api/managed-agents/projects' \
  -H 'x-api-key: <api-key>'

Secrets, runtime variables, schedules and logs are managed with the CLI and the dashboard; see their pages under Concepts.