priv/docs/rest-api.md

main at 58e6347eeb72 · 16 KB

REST API

OpenAgents serves a bounded GitHub-shaped API under /api/v1. The paths make familiar repository tooling easier to adapt, but OpenAgents does not implement the complete GitHub API.

Authenticate

API writes require an oa_pat_ bearer token with forge:write scope. Create and revoke tokens on API tokens.

curl -H "Authorization: Bearer $OPENAGENTS_TOKEN" \
  https://openagents.com/api/v1/repos/OpenAgentsInc/openagents.com/issues

Public repositories allow anonymous reads. The repository, issue, and project base read routes also accept an optional bearer token so a member can read a private repository. Ancillary comment, label, assignee, and milestone read routes remain public-repository reads in the current subset.

Use Call the API with the CLI when you want the CLI to select the API origin, load your stored credential, and return JSON.

Repositories

GET    /api/v1/user
GET    /api/v1/user/repos
POST   /api/v1/repos
POST   /api/v1/user/repos
POST   /api/v1/orgs/:org/repos
GET    /api/v1/repos/:owner/:repo
DELETE /api/v1/repos/:owner/:repo
POST   /api/v1/user/repos/imports
POST   /api/v1/orgs/:org/repos/imports
GET    /api/v1/repository-imports/:id

POST /api/v1/repos takes an owner field and resolves it here, so a caller that was handed OWNER/NAME does not have to decide whether OWNER is a person or an organization before it picks a route. Omit owner and the repository lands in your own namespace. POST /api/v1/user/repos and POST /api/v1/orgs/:org/repos stay as the GitHub-compatible spellings.

Repository writes require an Idempotency-Key header. The published openagents.repositories.v1 contract defines request authority, lifecycle states, pagination, and stable error codes. Only a repository owner can delete it. A successful deletion returns 204 No Content.

Issues and comments

GET    /api/v1/repos/:owner/:repo/issues
POST   /api/v1/repos/:owner/:repo/issues
GET    /api/v1/repos/:owner/:repo/issues/:issue_number
PUT    /api/v1/repos/:owner/:repo/issues/:issue_number
PATCH  /api/v1/repos/:owner/:repo/issues/:issue_number

GET    /api/v1/repos/:owner/:repo/issues/:issue_number/comments
POST   /api/v1/repos/:owner/:repo/issues/:issue_number/comments
GET    /api/v1/repos/:owner/:repo/issues/comments/:id
PUT    /api/v1/repos/:owner/:repo/issues/comments/:id
PATCH  /api/v1/repos/:owner/:repo/issues/comments/:id
DELETE /api/v1/repos/:owner/:repo/issues/comments/:id

List responses use named envelopes. For example, the issue list returns an object with an issues array.

Pull requests and stacks

GET    /api/v1/repos/:owner/:repo/pulls
POST   /api/v1/repos/:owner/:repo/pulls
GET    /api/v1/repos/:owner/:repo/pulls/:pull_number
PATCH  /api/v1/repos/:owner/:repo/pulls/:pull_number

GET    /api/v1/repos/:owner/:repo/stacks
POST   /api/v1/repos/:owner/:repo/stacks
GET    /api/v1/repos/:owner/:repo/stacks/:stack_number
POST   /api/v1/repos/:owner/:repo/stacks/:stack_number/append
POST   /api/v1/repos/:owner/:repo/stacks/:stack_number/rebase
POST   /api/v1/repos/:owner/:repo/stacks/:stack_number/merge
POST   /api/v1/repos/:owner/:repo/stacks/:stack_number/unstack
POST   /api/v1/repos/:owner/:repo/stacks/:stack_number/dissolve
PUT    /api/v1/repos/:owner/:repo/pulls/:pull_number/merge-async

See Pull requests for the pull request endpoints and the Stacks API for stacks, durable operations, idempotency, and optimistic concurrency.

Issue prerequisites

An issue can wait on other issues in the same repository.

GET    /api/v1/repos/:owner/:repo/issues/:issue_number/dependencies
POST   /api/v1/repos/:owner/:repo/issues/:issue_number/dependencies
DELETE /api/v1/repos/:owner/:repo/issues/:issue_number/dependencies/:blocked_by_number

POST takes the issue numbers this issue waits on and returns the resulting graph:

{ "blocked_by": [9, 12] }
{
  "blocked": true,
  "blocked_by": [{ "number": 9, "title": "Deliver the work system", "state": "open" }],
  "blocks": []
}

Every issue response carries the same object under openagents, and a response that carries it names the namespace in the x-openagents-extensions header. GET /api/v1 lists the extension fields this deployment serves.

blocked is derived from the prerequisites' own state, so closing the last open prerequisite unblocks the issue with no second write. The issue list filters on it, which answers "what can an agent start right now":

GET /api/v1/repos/:owner/:repo/issues?blocked=false
GET /api/v1/repos/:owner/:repo/issues?blocked=true

Prerequisites stay inside one repository. An unknown number, a self reference, and an edge that would close a cycle each return 422 Unprocessable Entity, and none of the batch is recorded. Reading the graph needs the same access as reading the issue. Recording or removing an edge needs repository write access.

The OpenAgents extension namespace

Every OpenAgents-specific field on a GitHub-shaped resource lives in one openagents object beside the GitHub keys, which stay exactly as GitHub shapes them. A GitHub client ignores the extra object; an OpenAgents-aware client reads issue.openagents.*.

Four rules govern every field in that namespace:

  1. It lives under openagents and never changes a GitHub-shaped key.
  2. GET /api/v1 enumerates it — type, enum values, owning version, and the endpoints it belongs to — before any client is expected to read it.
  3. A filter the root document lists is refused by the endpoint that names it when the value falls outside the published enum.
  4. A derived field states what it derives from, including whose visibility.

Rules 1 to 3 are enforced by a test that reads the root document and the live responses and fails on any disagreement, so a field that is served but not published cannot ship.

GET /api/v1

Responses that carry an extension name the namespace in the x-openagents-extensions header, so a client can branch without hardcoding.

Issue progress

issue.openagents.progress is one of to_do, in_progress, or done. It is derived, never stored:

  • A closed issue is done. Closing an issue is the act that finishes it.
  • An open issue is in_progress while any one of three records says work is under way on it:
    • An attempt holds it. An execution attempt against the issue is admitted or running and its deadline has not passed.
    • A session is bound to it. A coding session the reader may read is open on the issue and has recorded something in the last two hours. A session binds itself: a thread whose objective names an issue in its own repository carries that issue.
    • A board says so. A project board the reader can open places the issue in a started column — In Progress, In review, or Started, matched without regard to case or separators.
  • Every other open issue is to_do, including one whose only board column is Done, because the issue is still open.

Each input says when it stops counting, so a claim never reads as work forever: an attempt's deadline passes, a session goes quiet, or somebody moves the board column.

Visibility is the reader's own for every input. A column on a board in a private repository the reader is not a member of, an owner-only session, and an attempt withheld at its own transparency tier all leave the issue reading to_do. Nothing here says who is working, only that work is under way.

GET /api/v1/repos/:owner/:repo/issues?progress=in_progress

The filter reads the same derivation as the field, so a listed issue always reports the value it was listed under. It composes with state like every other filter, so progress=done needs state=all or state=closed. Any value outside the enum returns 422 Unprocessable Entity naming progress.

Reputation attestations

A reputation attestation is a signed claim that one subject completed, verified, reviewed, was paid for, or lost credit for one accepted outcome, in one repository, at one revision, under one verifier policy. Reads are public for public repositories; issuing and revoking stay behind verifier authority.

GET /api/v1/reputation/policy
GET /api/v1/reputation/keys
GET /api/v1/repos/:owner/:repo/issues/:issue_number/attestations
GET /api/v1/repos/:owner/:repo/attestations/:id
GET /api/v1/repos/:owner/:repo/attestations/:id/verification
GET /api/v1/repos/:owner/:repo/reputation/subjects/:subject_id

Every attestation response carries the signed claim verbatim next to its signature, so you can verify it without trusting this API:

{
  "claim": {
    "schema": "openagents.reputation.attestation.v1",
    "event_type": "completion",
    "issuer": { "key_id": "…", "algorithm": "ed25519", "public_key": "…" },
    "subject": { "actor_id": "…" },
    "outcome": { "kind": "compensation_outcome_decision", "state": "accepted" },
    "scope": { "repository": "…", "issue_number": 88, "revision": "…" },
    "verifier": { "policy_id": "…", "policy_version": 1, "policy_digest": "…" },
    "confidence_ppm": 900000,
    "evidence": [{ "kind": "outcome", "digest": "…", "disclosed": true }]
  },
  "claim_digest": "…",
  "signature": "…",
  "signature_algorithm": "ed25519"
}

To check one yourself, canonicalize the claim with sorted object keys and no insignificant whitespace, confirm its SHA-256 digest equals claim_digest, verify the Ed25519 signature against the public_key that /api/v1/reputation/keys publishes for issuer_key_id, and hash the policy rules from /api/v1/reputation/policy to reproduce policy_digest.

The verification endpoint reports the same checks, plus evidence availability, staleness, and revocation state. Pass what you expect — subject_id, revision, event_type, or policy_id — and a claim that binds to something else answers with verified: false and the mismatch:

curl "https://openagents.com/api/v1/repos/OpenAgentsInc/openagents.com/attestations/$ID/verification?subject_id=actor:builder"

An attestation is issued only after its accepted-outcome receipt reaches an admitted terminal state. Presence, token volume, online time, and narration are not attestable. A reversed or invalidated outcome gets a linked reversal or revocation attestation, and the original claim stays readable so a past decision remains auditable.

Subject evidence is scoped to one repository and reports counts per event type. It never returns a score or a ranking:

{ "subject_id": "…", "scope": "repository", "counts": { "completion": 3 }, "score": null }

An attestation on a private repository is disclosed to repository members only, and a private attestation withholds the outcome reference and every evidence reference from the signed claim while staying verifiable.

Labels

GET    /api/v1/repos/:owner/:repo/labels
POST   /api/v1/repos/:owner/:repo/labels
GET    /api/v1/repos/:owner/:repo/labels/:name
PUT    /api/v1/repos/:owner/:repo/labels/:name
PATCH  /api/v1/repos/:owner/:repo/labels/:name
DELETE /api/v1/repos/:owner/:repo/labels/:name

GET    /api/v1/repos/:owner/:repo/issues/:issue_number/labels
POST   /api/v1/repos/:owner/:repo/issues/:issue_number/labels
DELETE /api/v1/repos/:owner/:repo/issues/:issue_number/labels/:name

Adding a label through the issue-label endpoint creates the label when it does not exist. Creating an issue with an unknown label remains a validation error.

Assignees and milestones

GET    /api/v1/repos/:owner/:repo/assignees
GET    /api/v1/repos/:owner/:repo/assignees/:assignee
GET    /api/v1/repos/:owner/:repo/issues/:issue_number/assignees
POST   /api/v1/repos/:owner/:repo/issues/:issue_number/assignees
DELETE /api/v1/repos/:owner/:repo/issues/:issue_number/assignees

GET    /api/v1/repos/:owner/:repo/milestones
POST   /api/v1/repos/:owner/:repo/milestones
GET    /api/v1/repos/:owner/:repo/milestones/:milestone_number
PUT    /api/v1/repos/:owner/:repo/milestones/:milestone_number
PATCH  /api/v1/repos/:owner/:repo/milestones/:milestone_number
DELETE /api/v1/repos/:owner/:repo/milestones/:milestone_number

Projects

GET    /api/v1/repos/:owner/:repo/projectsV2
POST   /api/v1/repos/:owner/:repo/projectsV2
GET    /api/v1/repos/:owner/:repo/projectsV2/:project_number
PATCH  /api/v1/repos/:owner/:repo/projectsV2/:project_number
GET    /api/v1/repos/:owner/:repo/projectsV2/:project_number/notes
POST   /api/v1/repos/:owner/:repo/projectsV2/:project_number/notes
PATCH  /api/v1/repos/:owner/:repo/projectsV2/:project_number/notes/:note_id
DELETE /api/v1/repos/:owner/:repo/projectsV2/:project_number/notes/:note_id
GET    /api/v1/repos/:owner/:repo/projectsV2/:project_number/items
GET    /api/v1/repos/:owner/:repo/projectsV2/:project_number/items/:item_id/events
POST   /api/v1/repos/:owner/:repo/projectsV2/:project_number/items
PATCH  /api/v1/repos/:owner/:repo/projectsV2/:project_number/items/:item_id
GET    /api/v1/repos/:owner/:repo/projectsV2/:project_number/fields
POST   /api/v1/repos/:owner/:repo/projectsV2/:project_number/fields

Promise registry projects use a promise_state field with LIVE, GATED, and WITHDRAWN values. Their item responses include openagents.promise. Filter items with promise_state=LIVE|GATED|WITHDRAWN or bounty_candidate=true. Read an item's actor-attributed history with:

GET /api/v1/repos/{owner}/{repo}/projectsV2/{project_number}/items/{item_id}/events

Only an accepted_outcome evidence entry naming an existing accepted OpenAgents.Compensation.OutcomeDecision can certify a LIVE promise. Issue, changelog, and forge receipt entries remain visible supporting evidence, but they do not certify LIVE; links never certify it.

The events endpoint is paginated with the standard page parameter. Evidence that points to a repository or issue you cannot read is omitted from the item, promise, and event projections.

The repository in the path controls visibility and write authority. Project numbers are repository-local. Project creation through this REST path is an OpenAgents extension; GitHub Projects V2 creation is not part of the assessed GitHub REST surface.

A project update accepts title, description, and state, where description is Markdown and state is open or closed. Each accepted change appends one immutable activity entry to the project's notes.

The notes read is paginated and takes page and kind, where kind is note, activity, or all. The response carries notes, page, per_page, and total_count. Editing or deleting a note requires authorship: another member with write access receives 403 Forbidden, and an activity entry is never editable. See Projects.

Choose a client

The paths are GitHub-shaped so that you can adapt a client you already know. They are not a promise that GitHub's own tooling runs against this host.

  • The openagents CLI is the first-class client. It finds the API origin, loads your stored credential, and returns JSON. See Call the API with the CLI.
  • A client that takes a base URL works on shape alone. Point Octokit at https://openagents.com/api/v1 and the issue, comment, label, assignee, milestone, and project reads and writes described on this page behave as the paths suggest, within the limits below.
  • GitHub's gh CLI is not supported. Its ported commands do not use REST: gh issue list sends a GraphQL query to /api/graphql, and gh issue view first probes GET /api/v3/meta. OpenAgents serves neither, so those commands fail whatever the base path is. The gh api passthrough does work if you give it a full URL, as in gh api https://openagents.com/api/v1/repos/OWNER/REPO/issues, but curl and the openagents CLI do the same job without the confusion.

/api/v3 no longer answers. It was a migration alias for clients released before the API moved to /api/v1, and it was removed on 2026-08-25 once the last client using it was upgraded. Send everything to /api/v1.

Know the compatibility limits

  • List responses use named envelopes such as issues, comments, labels, milestones, assignees, projects, items, and fields. GitHub commonly returns a bare array.
  • Pagination, filters, link headers, and error envelopes form a bounded local contract. They do not provide complete Octokit compatibility, and gh is not a supported client at all.
  • Ancillary issue-resource reads do not yet use the optional-bearer pipeline for private repositories.
  • A nonnumeric issue or milestone number can produce 500 Internal Server Error instead of 404 Not Found. Project number parsing fails closed with 404 Not Found.
  • Project update, archive, delete, item removal and ordering, field mutation, views, draft items, and organization projects are not implemented.

Know what is not implemented

Pull request reviews, webhooks, releases, SSH Git transport, and Git LFS object storage are outside the current subset. Pull requests and stacked pull requests are implemented — see Pull requests and the Stacks API.