Developer guide

Build Aurora clients that stay correct under retries, ambiguity, queue handoffs, and agent loops.

Guidance for retries, workspace changes, queue handoffs, durable comments, and long-lived Aurora clients.

Covers

Retries, caching, structured errors, queue handoffs, and long-lived client behavior.

Operating model

Aurora's contract model

Aurora's external API is not just a set of CRUD routes. It is a workspace-scoped execution surface for people and automation, which means identity resolution, queue semantics, and comment trails matter just as much as request syntax.

Discovery before mutation

Read workspace structure first so later writes can use valid teams, users, statuses, and queue identities.

Use stable natural keys

Prefer teamKey, projectKey, issueKey, and statusKey whenever your client can avoid opaque ids.

Make create flows idempotent

Use Idempotency-Key headers so retries do not fork records when workers, webhooks, or agents repeat work.

Recover from machine codes

Branch on code and details instead of parsing human text when references are stale or invalid.

Client architecture

Discovery and caching

Treat discovery as a lightweight schema sync for the current token workspace. It should happen before mutation and then refresh only when the surrounding workspace configuration actually changes.

EndpointWhat to cacheRefresh guidance
GET /api/externalToken queue identity, workspace metadata, endpoint map, capability flagsPer token workspace session
GET /api/external/teamsteamId and teamKey pairsRefresh on team-management changes
GET /api/external/usersownerId, reporterId, assigneeId, automationAgentsRefresh on membership or token-name changes
GET /api/external/statusesValid statuses for a teamRefresh when moving across teams or after status configuration changes
GET /api/external/tagsManaged tags plus issue and project-linked agenda usageRefresh after tag catalog or meeting agenda tag changes

Practical caching rule

Cache by token workspace, not globally. Two Aurora tokens from the same user can still see different workspaces, teams, statuses, and automation queues.

Identity and lookup

Prefer keys first, ids second

The docs, the API, and the plugin all work best when clients speak in team keys, project keys, and issue keys wherever they can.

Prefer these inputs

These are easier for operators to reason about and easier for agent prompts to generate correctly.

  • teamKey over teamId when humans already know the team key
  • projectKey plus teamKey over projectId when building prompts or task plans
  • issueKey over issueId when the issue was referenced in chat or copied from the UI
  • statusKey over statusId when a team's workflow keys are already known

Use ids when

Opaque ids are still correct when they come from a trusted previous read or when disambiguation is required.

  • The client already persisted the id from a previous Aurora response
  • A project key is ambiguous without a team id or team key
  • You are patching the exact record you just fetched by id
  • Your workflow stores canonical Aurora ids internally for subsequent calls

Create flows

Retry safety with Idempotency-Key

The fastest way to create duplicate projects or issues is to let a worker or agent retry a create call without a stable key. Aurora's create routes are designed so you do not have to.

Recommended idempotent project create
curl -X POST https://www.auroraworkos.com/api/external/projects \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: source-system:project:weekly-scorecard" \
  -d '{
    "name": "Weekly Scorecard"
  }'

Key design rule

Use a key shape derived from your upstream system and entity id, such as source-system:entity-type:external-id. That makes retries deterministic across restarts, not just within one process lifetime.

Large collections

Cursor pagination

When you are reading lists for sync, triage, or queue pickup, do not assume one page is enough. Aurora's list routes expose a cursor model that is intentionally simple to integrate.

RouteSupportsTypical use
GET /api/external/projectslimit, cursorWorkspace and team-scoped project inventory
GET /api/external/issueslimit, cursorIssue triage, backlog review, automation pickup
GET /api/external/meetingslimit, cursorMeeting history and workflow state review
Issue pagination pattern
GET /api/external/issues?limit=50

{
  "issues": [...],
  "pageInfo": {
    "hasMore": true,
    "nextCursor": "opaque-cursor",
    "limit": 50
  }
}

Error recovery

Structured errors are part of the contract

The Aurora API returns machine-usable errors so clients and agents can choose a recovery path without scraping human prose.

Error response shape
{
  "error": "Human-readable message",
  "code": "MACHINE_CODE",
  "details": {
    "field": "value"
  },
  "suggestion": "Recovery guidance for a client or agent."
}
CodeMeaningRecommended recovery
UNAUTHORIZEDThe request did not include a valid, active API token or OAuth access token.Refresh credentials and retry after confirming you are calling /api/external or /api/mcp with the right auth method.
TEAM_NOT_FOUNDThe supplied teamId or teamKey could not be resolved inside the token workspace.Refresh team discovery and retry with a valid teamId or teamKey.
NO_TEAM_AVAILABLEA create request needed a default team but the workspace has no usable team.Create or restore a workspace team, then retry the request with teamId or teamKey.
PROJECT_KEY_AMBIGUOUSA projectKey was supplied without enough team context to resolve it uniquely.Add teamKey or switch to projectId.
PROJECT_NOT_FOUNDThe requested project could not be found or is outside the caller's workspace/project access boundary.Refresh project discovery, confirm project access, and retry with a valid projectId or teamKey/projectKey pair.
PROJECT_TEAM_MISMATCHThe resolved project does not belong to the supplied team context.Use the project's actual teamKey/teamId or switch to projectId-only lookup.
PROJECT_KEY_CONFLICTThe requested project key is already used in the target team.Choose a unique project key or update the existing project instead.
NAME_REQUIREDA project, external owner preset, or agenda status write was sent without a required name.Validate payloads before write calls and retry with a non-empty name.
TEAM_CHANGE_BLOCKEDA project team move was requested while the project still has issues.Move or resolve project issues first, or leave the project on its current team.
ISSUE_NOT_FOUNDThe requested issue could not be found or is outside the caller's workspace/project access boundary.Refresh issue lookup with GET /api/external/issues or GET /api/external/issues/by-key/{issueKey}.
SUBTASK_DISABLEDThe parent project or issue does not currently allow sub-task writes.Enable sub-tasks in Workflow Management and on the parent issue before retrying.
SUBTASK_NOT_FOUNDThe requested sub-task could not be found under the parent issue.Reload the parent issue sub-task list and retry with a current subtaskId.
SUBTASK_TITLE_REQUIREDA sub-task create or update request did not include a usable title.Provide a non-empty title and retry the sub-task write.
SUBTASK_LINK_TARGET_NOT_FOUNDThe requested sibling sub-task relationship target could not be found.Reload sibling sub-tasks under the parent and retry with a current targetSubtaskId.
SUBTASK_LINK_SCOPE_INVALIDA sub-task relationship targeted a record outside the parent issue's sibling sub-task set.Use sub-task links only for siblings; use issue links for cross-issue dependencies.
SUBTASK_STATUS_MAPPING_REQUIREDSub-task promotion could not map its lightweight status into the target project workflow.Resolve a visible project status and provide the required target status mapping.
TARGET_ISSUE_REQUIREDAn issue relationship request did not identify the other issue.Provide targetIssueId or a resolvable target issue key.
TARGET_ISSUE_NOT_FOUNDThe issue relationship target could not be found inside the caller's accessible projects.Refresh issue discovery and retry with a visible target issue.
INVALID_ISSUE_LINK_TYPEAn issue relationship used an unsupported link direction or type.Use blocks, blocked_by, or related as documented by the issue-link route.
ISSUE_LINK_SELF_REFERENCEAn issue or sub-task relationship attempted to link a record to itself.Choose a different target record before retrying.
ISSUE_LINK_ID_REQUIREDA relationship delete request omitted the link id.Read the current relationships and send the exact linkId to remove.
ISSUE_LINK_NOT_FOUNDThe relationship id could not be found for the supplied issue or sub-task.Refresh current relationships and retry only if the link still exists.
ISSUE_CONTAINER_FIELD_CONFLICTA request supplied conflicting compatibility fields for the issue container/epic.Send one consistent epicId or issueContainerId value.
ISSUE_CONTAINER_DISABLEDThe target project currently hides or disables issue containers.Enable containers in Workflow Management or omit the container assignment.
ISSUE_CONTAINER_NOT_FOUNDThe requested issue container/epic could not be resolved in the target project.Reload project issue containers and retry with a current id.
ISSUE_CONTAINER_MILESTONE_NOT_FOUNDThe requested container milestone could not be found or is outside the caller's project-access boundary.Reload /api/external/issue-container-milestones and retry with a visible milestone id.
ISSUE_CONTAINER_REQUIRED_FOR_MILESTONEA milestone write or issue milestone assignment did not include a usable container/epic context.Provide issueContainerId/epicId, or resolve an active container before retrying.
TAG_FIELD_CONFLICTA request supplied incompatible values through preferred tags and legacy labels fields.Use tags as the preferred field, or make tags and labels equivalent for compatibility.
PROJECT_REQUIREDAn issue or agenda action requires a target project but none was supplied or resolved.Provide projectId or projectKey with team context when needed.
TITLE_REQUIREDAn issue create request was sent without a non-empty title.Validate issue payloads before write calls and retry with title.
STATUS_INVALID_FOR_PROJECTThe chosen status does not belong to the issue's project team.Reload statuses for the target team and retry with a valid statusId or statusKey.
STATUS_INVALID_FOR_PROJECT_WORKFLOWThe chosen status is hidden or disallowed by the project's workflow settings.Reload project workflow or board data and choose a visible, allowed status.
STATUS_NOT_FOUNDThe supplied statusId or statusKey could not be resolved.Refresh statuses for the target team and retry with a current status reference.
WORKFLOW_TRANSITION_BLOCKEDThe requested status move is blocked by project workflow transition rules.Move through an allowed transition path or adjust the project workflow first.
MEETING_NOT_FOUNDThe requested meeting could not be found in the caller's accessible workspace/projects.Refresh meeting discovery and confirm the token has project access when the meeting is project-linked.
MEETING_TITLE_REQUIREDA meeting create or update request was sent without a non-empty title.Provide title before retrying the meeting write.
INVALID_MEETING_TIMEMeeting or agenda dates/times were malformed or inconsistent.Use ISO datetimes for startsAt/endsAt and YYYY-MM-DD for agenda dates, with end after start.
INVALID_SCHEDULEStart date, due date, or duration values form an invalid or contradictory schedule.Use valid ISO dates and non-negative duration values, then retry with two-of-three schedule inputs.
RECURRENCE_START_REQUIREDA recurring meeting was requested without a usable start date.Provide startsAt when recurrenceType is not none.
AGENDA_ITEM_NOT_FOUNDThe requested agenda item could not be found inside the supplied meeting.Reload the meeting agenda and retry with a current agendaItemId.
AGENDA_ITEM_TITLE_REQUIREDAn agenda item create request was sent without a non-empty title.Provide title before retrying the agenda item write.
AGENDA_STATUS_NOT_FOUNDThe supplied agenda status id could not be resolved in the workspace.Reload meeting settings and retry with a current agenda status id.
AGENDA_STATUS_DELETE_BLOCKEDThe agenda status cannot be deleted because agenda items still reference it.Move agenda items to another status, then retry deletion.
OWNER_NOT_IN_WORKSPACEAn owner, reporter, actor, or token-backed user identity could not be resolved in the workspace.Refresh users, confirm the user is a workspace member, or recreate the API token from a current workspace member account.
OWNER_NOT_FOUNDAn agenda owner or external owner preset could not be found.Reload agenda owners or meeting settings before retrying.
OWNER_REQUIREDAn agenda owner write did not include an internal user or external owner value.Provide userId, presetId, externalName, or externalEmail.
ASSIGNEE_NOT_IN_WORKSPACEThe requested assignee is not a workspace member or active automation-agent id.Refresh users and retry with a valid human user id or api-token:<name> identity.
ASSIGNEE_NOT_IN_PROJECTThe requested assignee does not have access to the target project.Grant project access to the user or automation agent, or choose a different assignee.
ASSIGNEE_NOT_IN_PROJECT_TEAMThe requested human assignee is not a member of the target project team.Add the user to the project team or use an allowed automation-agent assignee.
ASSIGNEE_FILTER_CONFLICTThe request combined assignedToMe with an explicit assigneeId.Use either assignedToMe=true or assigneeId, not both.
REPORTER_NOT_IN_WORKSPACEThe requested reporter is not a workspace member.Refresh users and retry with a workspace member user id.
COMMENT_BODY_REQUIREDA comment create request was sent without a non-empty body.Validate comment payloads before write calls and retry with a non-empty string body.
COMMENT_PARENT_NOT_FOUNDA threaded reply referenced a parent comment that does not belong to the issue.Reload issue comments and retry with a current parentCommentId.
ATTACHMENT_URL_REQUIREDA link-backed attachment request omitted a valid http or https URL.Provide an accessible http/https URL and optional display name.
ATTACHMENT_LIMIT_EXCEEDEDOne request attempted to add more attachment links than the route permits.Split the attachment list into batches of 20 or fewer links.
INVALID_TARGET_TYPEA meeting work-link request used an unsupported targetType.Use issue, project, discussion_topic, or agenda_item.
TARGET_REQUIREDA meeting work-link request omitted both targetId and targetKey.Provide targetId, or targetKey for supported issue/project targets.
DISCUSSION_TOPIC_NOT_FOUNDThe requested discussion topic is absent, private from API, or outside the effective caller's visibility.Refresh visible discussion topics and confirm the owning group remains API-enabled.
UNSUPPORTED_ACTIONThe request attempted a guarded operation such as converting without an issue action or sending an empty update.Read the response suggestion/details, adjust the payload, and retry through the supported route.

Queues and orchestration

Automation queues and visible execution

Aurora treats API token names as first-class queue identities. That makes assignment, polling, and durable comment trails predictable for both background workers and visible chat-driven flows.

Recommended automation pattern

Use tokens as queue identities, not as a hidden replacement for human ownership.

  • Use GET /api/external first to discover token.assigneeId and capability flags before queue polling.
  • Treat the API token name as the queue identity for automation assignment and assignedToMe filtering.
  • Prefer visible chat-orchestrated execution for ambiguous work and reserve background workers for deterministic tasks.
  • Leave humans as reporterId, move issues through todo -> in-progress -> in-review, and always post a summary comment on completion.
Queue pickup query
curl "https://www.auroraworkos.com/api/external/issues?assignedToMe=true&statusKey=todo" \
  -H "Authorization: Bearer $TOKEN"

Agent setup

Bind Codex to an Aurora workspace

Use workspace settings to generate a secure connect command for the repo or worktree Codex should operate from. The command opens Aurora in the browser, creates a dedicated token, stores it in macOS Keychain or Windows DPAPI, and writes the local project binding.

Secure connect command shape
sh plugins/aurora-api/scripts/bind-project.sh connect \
  --base-url https://www.auroraworkos.com \
  --workspace-id <workspace-id> \
  --token-name "Codex automation" \
  --execution-mode chat-orchestrated
Manual token fallback
export AURORA_PAT_WORKSPACE=aurora_pat_...

sh plugins/aurora-api/scripts/bind-project.sh bind-token \
  --base-url https://www.auroraworkos.com \
  --token-env-var AURORA_PAT_WORKSPACE \
  --project <binding-id> \
  --execution-mode chat-orchestrated

Multiple local bindings

Aurora stores repo and worktree bindings in ~/.codex/aurora-projects.json. Cwd-based resolution picks the most specific matching projectPath, and --project can select a binding explicitly for polling or staged handoff commands.

Native Linux boundary

The bundled binder does not provide a native Linux secure store. Use remote MCP OAuth when possible, or an env-backed manual bind only on a trusted Linux host. WSL2 is supported through Windows DPAPI and is not the same as native Linux.

Event delivery

Use webhooks as signed triggers, not authorization

Webhooks notify your service about supported project, issue, sub-task, and container milestone activity. The receiver still needs its own OAuth or PAT credential before it reads or changes Aurora data.

Current delivery model

Aurora sends one signed POST immediately. Delivery is best effort today, with no durable retry queue or delivery history.

Receiver responsibilities

Verify the raw-body HMAC, reject stale timestamps, return quickly, deduplicate by domain identity, and process asynchronously.

Read the complete contract

See the webhook guide for headers, signature verification, the event catalog, payload envelope, and current limitations.

Recipes

Workflow recipes

These patterns are the shortest path to reliable real-world Aurora integrations.

Fresh workspace bootstrap
1. GET /api/external
2. GET /api/external/teams
3. GET /api/external/users
4. GET /api/external/statuses
5. POST /api/external/projects
6. POST /api/external/issues
Comment and status update loop
1. GET /api/external/issues/by-key/PE-001
2. POST /api/external/issues/{issueId}/comments
3. PATCH /api/external/issues/{issueId}
4. Re-read the issue if the next step depends on the new status or assignee
Meeting agenda to issue
1. GET /api/external/meetings/settings
2. GET /api/external/meetings/{meetingId}/agenda-items
3. POST /api/external/meetings/{meetingId}/agenda-items/{agendaItemId}/convert
4. POST /api/external/meetings/{meetingId}/agenda-items/{agendaItemId}/notes/publish
5. POST /api/external/issues/{issueId}/comments (optional summary or handoff note)

Human-friendly companion pages

If you need the endpoint list while following one of these recipes, keep the API reference open beside this guide.

Know the boundaries

Current constraints

These are the constraints clients should plan around today instead of discovering them after they ship.

ConstraintWhat it means
One workspace per tokenA token cannot traverse workspaces. Resolve everything within its workspace boundary.
No token scopes yetA valid token can use all supported external routes for its workspace.
External routes onlyTokens and connector access work on /api/external/* and /api/mcp, not internal app pages.
OAuth connector callers differ from PAT callersConnector calls run as the approving human user; PAT calls run as the token queue identity.
External attachments are link-backedIssue and agenda-item routes can list or add http/https attachment links. Binary transfer and the workspace file repository still require an app session.
Project access differs by principalPAT automation agents require explicit project access. Human MCP OAuth calls are currently workspace-scoped and do not mirror browser project filters.
Child background workers may need broader sandboxingIf a hidden Codex child must call Aurora or other external services, workspace-write may not be enough.