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.
| Endpoint | What to cache | Refresh guidance |
|---|---|---|
| GET /api/external | Token queue identity, workspace metadata, endpoint map, capability flags | Per token workspace session |
| GET /api/external/teams | teamId and teamKey pairs | Refresh on team-management changes |
| GET /api/external/users | ownerId, reporterId, assigneeId, automationAgents | Refresh on membership or token-name changes |
| GET /api/external/statuses | Valid statuses for a team | Refresh when moving across teams or after status configuration changes |
| GET /api/external/tags | Managed tags plus issue and project-linked agenda usage | Refresh after tag catalog or meeting agenda tag changes |
Practical caching rule
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.
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
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.
| Route | Supports | Typical use |
|---|---|---|
| GET /api/external/projects | limit, cursor | Workspace and team-scoped project inventory |
| GET /api/external/issues | limit, cursor | Issue triage, backlog review, automation pickup |
| GET /api/external/meetings | limit, cursor | Meeting history and workflow state review |
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": "Human-readable message",
"code": "MACHINE_CODE",
"details": {
"field": "value"
},
"suggestion": "Recovery guidance for a client or agent."
}| Code | Meaning | Recommended recovery |
|---|---|---|
| UNAUTHORIZED | The 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_FOUND | The 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_AVAILABLE | A 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_AMBIGUOUS | A projectKey was supplied without enough team context to resolve it uniquely. | Add teamKey or switch to projectId. |
| PROJECT_NOT_FOUND | The 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_MISMATCH | The 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_CONFLICT | The requested project key is already used in the target team. | Choose a unique project key or update the existing project instead. |
| NAME_REQUIRED | A 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_BLOCKED | A 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_FOUND | The 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_DISABLED | The 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_FOUND | The 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_REQUIRED | A 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_FOUND | The 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_INVALID | A 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_REQUIRED | Sub-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_REQUIRED | An issue relationship request did not identify the other issue. | Provide targetIssueId or a resolvable target issue key. |
| TARGET_ISSUE_NOT_FOUND | The 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_TYPE | An 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_REFERENCE | An issue or sub-task relationship attempted to link a record to itself. | Choose a different target record before retrying. |
| ISSUE_LINK_ID_REQUIRED | A relationship delete request omitted the link id. | Read the current relationships and send the exact linkId to remove. |
| ISSUE_LINK_NOT_FOUND | The 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_CONFLICT | A request supplied conflicting compatibility fields for the issue container/epic. | Send one consistent epicId or issueContainerId value. |
| ISSUE_CONTAINER_DISABLED | The target project currently hides or disables issue containers. | Enable containers in Workflow Management or omit the container assignment. |
| ISSUE_CONTAINER_NOT_FOUND | The 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_FOUND | The 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_MILESTONE | A 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_CONFLICT | A 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_REQUIRED | An issue or agenda action requires a target project but none was supplied or resolved. | Provide projectId or projectKey with team context when needed. |
| TITLE_REQUIRED | An issue create request was sent without a non-empty title. | Validate issue payloads before write calls and retry with title. |
| STATUS_INVALID_FOR_PROJECT | The 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_WORKFLOW | The 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_FOUND | The supplied statusId or statusKey could not be resolved. | Refresh statuses for the target team and retry with a current status reference. |
| WORKFLOW_TRANSITION_BLOCKED | The requested status move is blocked by project workflow transition rules. | Move through an allowed transition path or adjust the project workflow first. |
| MEETING_NOT_FOUND | The 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_REQUIRED | A meeting create or update request was sent without a non-empty title. | Provide title before retrying the meeting write. |
| INVALID_MEETING_TIME | Meeting 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_SCHEDULE | Start 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_REQUIRED | A recurring meeting was requested without a usable start date. | Provide startsAt when recurrenceType is not none. |
| AGENDA_ITEM_NOT_FOUND | The requested agenda item could not be found inside the supplied meeting. | Reload the meeting agenda and retry with a current agendaItemId. |
| AGENDA_ITEM_TITLE_REQUIRED | An agenda item create request was sent without a non-empty title. | Provide title before retrying the agenda item write. |
| AGENDA_STATUS_NOT_FOUND | The 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_BLOCKED | The agenda status cannot be deleted because agenda items still reference it. | Move agenda items to another status, then retry deletion. |
| OWNER_NOT_IN_WORKSPACE | An 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_FOUND | An agenda owner or external owner preset could not be found. | Reload agenda owners or meeting settings before retrying. |
| OWNER_REQUIRED | An agenda owner write did not include an internal user or external owner value. | Provide userId, presetId, externalName, or externalEmail. |
| ASSIGNEE_NOT_IN_WORKSPACE | The 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_PROJECT | The 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_TEAM | The 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_CONFLICT | The request combined assignedToMe with an explicit assigneeId. | Use either assignedToMe=true or assigneeId, not both. |
| REPORTER_NOT_IN_WORKSPACE | The requested reporter is not a workspace member. | Refresh users and retry with a workspace member user id. |
| COMMENT_BODY_REQUIRED | A 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_FOUND | A threaded reply referenced a parent comment that does not belong to the issue. | Reload issue comments and retry with a current parentCommentId. |
| ATTACHMENT_URL_REQUIRED | A link-backed attachment request omitted a valid http or https URL. | Provide an accessible http/https URL and optional display name. |
| ATTACHMENT_LIMIT_EXCEEDED | One request attempted to add more attachment links than the route permits. | Split the attachment list into batches of 20 or fewer links. |
| INVALID_TARGET_TYPE | A meeting work-link request used an unsupported targetType. | Use issue, project, discussion_topic, or agenda_item. |
| TARGET_REQUIRED | A meeting work-link request omitted both targetId and targetKey. | Provide targetId, or targetKey for supported issue/project targets. |
| DISCUSSION_TOPIC_NOT_FOUND | The 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_ACTION | The 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.
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.
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-orchestratedexport 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-orchestratedMultiple local bindings
~/.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
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
Recipes
Workflow recipes
These patterns are the shortest path to reliable real-world Aurora integrations.
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/issues1. 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 assignee1. 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
Know the boundaries
Current constraints
These are the constraints clients should plan around today instead of discovering them after they ship.
| Constraint | What it means |
|---|---|
| One workspace per token | A token cannot traverse workspaces. Resolve everything within its workspace boundary. |
| No token scopes yet | A valid token can use all supported external routes for its workspace. |
| External routes only | Tokens and connector access work on /api/external/* and /api/mcp, not internal app pages. |
| OAuth connector callers differ from PAT callers | Connector calls run as the approving human user; PAT calls run as the token queue identity. |
| External attachments are link-backed | Issue 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 principal | PAT 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 sandboxing | If a hidden Codex child must call Aurora or other external services, workspace-write may not be enough. |