Work items can carry a caller-supplied external ID — an opaque string that lets API consumers reference a work item by a familiar identifier from another system (JIRA, GitHub Issues, an internal tracker, etc.) without storing a separate UUID mapping.
Two problems external IDs solve:
-
Integration with project trackers. Every consumer that calls the CodeyBox API today must maintain its own
external_id → internal_uuidmapping so it can correlate CodeyBox work items with tickets in JIRA, GitHub Issues, or a custom dashboard. Setting anexternalIdat creation time lets the orchestrator carry that correlation, so webhook receivers and admin users can see the origin ticket directly. -
Dependency batching. The standard workflow to express "B depends on A" is: POST A → wait for 201 response → capture A's UUID → POST B with
dependsOn=[uuid]. Every round-trip is a ~100 ms RTT. Queuing 50 dependent items takes 50 serialised RTTs.With external IDs, the caller generates identifiers locally (sequential numbers, ULIDs, anything) and POSTs all 50 in parallel. The
dependsOnarray accepts external IDs alongside UUIDs; the orchestrator resolves the cross-references at create time — no round-trips needed.
| Rule | Detail |
|---|---|
| Length | 1–256 characters |
| Characters | ASCII printable, no whitespace, no /, no ?, no ; < = > |
| Reserved prefix | Must not start with wi- |
| UUID collision | Must not be parseable as a UUID (would be ambiguous with internal IDs) |
The safe shape is: lowercase letters, digits, and the separators _-:. — for example, JIRA-1234, gh-456, sprint-7:ticket-99, internal_tracker_id.
Violating any rule returns 400 Bad Request.
externalId is unique per project, not globally. Two different projects may legitimately use the same scheme (JIRA-1234 in project my-app and JIRA-1234 in project other-app are independent).
Null is allowed for items without an external ID; multiple null items in the same project coexist freely.
Attempting to create a second work item in the same project with a duplicate externalId returns:
{
"error": "externalId 'JIRA-1234' already exists in project 'my-app' for work item <uuid> (state: Queued)"
}POST /workitems
{
"projectId": "my-app",
"externalId": "JIRA-1234",
"title": "Add dark-mode support",
"prompt": "...",
"dependsOn": []
}The externalId is optional. Omit it (or pass null) for items that don't need one.
All endpoints that accept {id} now also accept a composite <projectId>:<externalId> path segment:
GET /workitems/my-app:JIRA-1234
DELETE /workitems/my-app:JIRA-1234
PATCH /workitems/my-app:JIRA-1234
The project part is required to avoid ambiguity between projects. If either part is empty the request is rejected with 400 Bad Request. If the externalId isn't found in that project the request returns 404 Not Found.
POST /workitems
{
"projectId": "my-app",
"externalId": "BATCH-1",
"title": "Step 1",
"prompt": "..."
}
POST /workitems
{
"projectId": "my-app",
"externalId": "BATCH-2",
"title": "Step 2",
"prompt": "...",
"dependsOn": ["BATCH-1"] ← externalId, resolved at create time
}
POST /workitems
{
"projectId": "my-app",
"externalId": "BATCH-3",
"title": "Step 3",
"prompt": "...",
"dependsOn": ["BATCH-2"] ← also an externalId
}All three can be POSTed in parallel (in any order that keeps the dep chain intact — or sequentially but without waiting for UUID responses). The dependsOn array also accepts a mix of external IDs and UUIDs:
"dependsOn": ["BATCH-1", "abcd-1234-..."]- Resolution happens once, at create time. The stored
dependsOncontains only internal UUIDs. - Deleting and re-creating a dependency under the same
externalIddoes not re-link existing dependents — they remain pointed at the old UUID. Operators who care should re-create dependent items too. - If any
dependsOnentry (UUID or externalId) cannot be resolved, the request returns400 Bad Requestwith the unresolved ID enumerated.
Every webhook event that carries a work item payload gains externalId alongside the existing id:
{
"event": "work_item.done",
"workItem": {
"id": "abcd-1234-...",
"externalId": "JIRA-1234",
...
}
}When the work item has no external ID, externalId is null (or omitted if the receiver uses JsonIgnoreCondition.WhenWritingNull).
# Create a CodeyBox work item linked to a JIRA ticket
resp = codeybox.create_work_item(
project_id="my-app",
external_id=jira_ticket.key, # e.g. "JIRA-4567"
title=jira_ticket.summary,
prompt=build_prompt(jira_ticket),
)
# No need to store resp["id"] — look up later via:
# GET /workitems/my-app:JIRA-4567external_id = f"GH-{issue.number}"
# Check if a work item already exists
r = requests.get(f"/workitems/my-app:{external_id}")
if r.status_code == 404:
codeybox.create_work_item(project_id="my-app", external_id=external_id, ...)@app.route("/webhook", methods=["POST"])
def on_webhook():
payload = request.json
if payload["event"] == "work_item.done":
external_id = payload["workItem"]["externalId"]
if external_id and external_id.startswith("JIRA-"):
jira.transition(external_id, status="Done")