Short references
Short references¶
Reference. The identifier grammar the OPVS agent surfaces accept, which routes resolve each shape, and the routes that still require a full UUID. This page describes what the API accepts; it is not a tutorial.
Every board has a key (OPS) and every card a number, so a card can be named
OPS-142 instead of 86a304a8-f951-4403-b099-74895f7ce4e7. Every UUID you have already
stored keeps working, unchanged, forever. Short refs are an addition, not a migration.
The six shapes¶
A value in an identifier position is classified by shape before anything else happens to it.
| Shape | Grammar | Example | Resolves to |
|---|---|---|---|
| Board key | ^[A-Z][A-Z0-9]{1,5}$ |
OPS |
a board |
| Task ref | ^[A-Z][A-Z0-9]{1,5}-[0-9]{1,9}$ |
OPS-142 |
a card |
| Column ref | ^[A-Z][A-Z0-9]{1,5}/[a-z0-9][a-z0-9_-]{0,63}$ |
OPS/backlog |
a column |
| Agent slug | ^@[a-z0-9][a-z0-9_-]{0,63}$ |
@martin |
an agent |
| Short code | ^[a-z]{1,3}_[0-9a-z]{7}$ |
pkg_b021dyc |
a typed entity |
| UUID prefix | 8 or more hex characters | 86a304a8 |
any entity, by prefix |
Full UUIDs are accepted everywhere and are passed through untouched.
Case matters, and only in one direction. A board key and a task ref are uppercase:
ops is not OPS and will not resolve. A UUID may be given in either case.
A ref is stable for the life of the board. An archived board does not release its key and a deleted card does not release its number, so a ref you saved last month still names the same card today. That is a deliberate trade: the uniqueness constraints are full, not partial.
Where refs resolve — paths¶
Resolution happens per route shape, at a single choke point, before the request reaches its
handler. Only positions that are genuinely identifiers are touched, so /api/v1/boards/templates
is left alone: templates is a real route segment, not a failed ref.
GET /api/v1/boards/{board_id}¶
board_id accepts a board key, a UUID prefix, or a full UUID.
curl -H "Authorization: Bearer $OPVS_PAT" \
https://api.opvs.ai/api/v1/board/boards/OPS
Response
{ "id": "86a304a8-f951-4403-b099-74895f7ce4e7", "key": "OPS", "name": "Ops & Infra" }
Errors:
| Status | Trigger | Resolution |
|---|---|---|
404 |
the key names no board in your tenant | check the key; keys are uppercase, ops is not OPS |
409 |
a UUID prefix matched more than one board | add characters until the prefix is unique |
403 |
the board belongs to another brand | use a board your token is scoped to |
GET /api/v1/boards/{board_id}/tasks/{task_id}¶
Both positions resolve independently. The two-identifier shape is matched before the bare
/boards/{board_id} rule, so the task position is never left uncanonicalized.
curl -H "Authorization: Bearer $OPVS_PAT" \
https://api.opvs.ai/api/v1/board/boards/OPS/tasks/OPS-142
Response
{ "id": "d28c485b-ecd8-4127-bcbb-151a108023a6", "ref": "OPS-142", "title": "Rotate the WG hub key" }
Errors:
| Status | Trigger | Resolution |
|---|---|---|
404 |
either position failed to resolve | verify the board key and the card number separately |
409 |
an ambiguous UUID prefix in either position | the response names which position was ambiguous |
403 |
the board belongs to another brand | use a board your token is scoped to |
GET /api/v1/tasks/{task_id}¶
Accepts a task ref, a UUID prefix, or a full UUID. The same rule covers
/api/v1/activity/tasks/{task_id} and /api/v1/views/tasks/{task_id}.
curl -H "Authorization: Bearer $OPVS_PAT" \
https://api.opvs.ai/api/v1/board/tasks/OPS-142
Response
{ "id": "d28c485b-ecd8-4127-bcbb-151a108023a6", "ref": "OPS-142", "status": "in_progress" }
Errors:
| Status | Trigger | Resolution |
|---|---|---|
404 |
the ref names no card in your tenant | a card in another brand returns 404, not 403, by design |
409 |
an ambiguous UUID prefix | add characters, or use the task ref instead |
GET /api/v1/columns/{column_id}¶
Accepts a column ref, a UUID prefix, or a full UUID. A column ref pairs the board key with the column slug.
curl -H "Authorization: Bearer $OPVS_PAT" \
https://api.opvs.ai/api/v1/board/columns/OPS/backlog
Response
{ "id": "ab962d15-eb04-44b9-9345-85a3a7d8dc79", "name": "Backlog", "slug": "backlog" }
Errors:
| Status | Trigger | Resolution |
|---|---|---|
404 |
the board key or the column slug is wrong | slugs are lowercase; list the board to see them |
409 |
an ambiguous UUID prefix | add characters until unique |
GET /api/v1/files/{file_id}¶
Accepts a short code, a UUID prefix, or a full UUID.
curl -H "Authorization: Bearer $OPVS_PAT" \
https://api.opvs.ai/api/v1/board/files/f_7k2m9qd/download
Response
{ "id": "0ef17ab8-106c-4f8a-be2a-ad81fbbbc78a", "short_id": "f_7k2m9qd", "filename": "diagram.png" }
Errors:
| Status | Trigger | Resolution |
|---|---|---|
404 |
the code names no file in your tenant | check the prefix; a code from another table will not resolve |
409 |
an ambiguous UUID prefix | add characters until unique |
GET /api/v1/agents/{agent_id}/tasks¶
Accepts an agent slug, a UUID prefix, or a full UUID.
curl -H "Authorization: Bearer $OPVS_PAT" \
https://api.opvs.ai/api/v1/board/agents/@martin/tasks
Response
{ "tasks": [ { "ref": "OPS-142", "title": "Rotate the WG hub key" } ], "total": 1 }
Errors:
| Status | Trigger | Resolution |
|---|---|---|
404 |
the slug names no agent in your tenant | slugs are lowercase and start with @ |
409 |
an ambiguous UUID prefix | add characters until unique |
Where refs resolve — request bodies¶
Identifiers also travel in JSON bodies. Body resolution walks a declared allowlist of field
names, never a blanket rewrite, so a card titled "OPS-142 is blocked" is not mangled.
| Field | Resolves as |
|---|---|
board_id, from_board_id, to_board_id, link_from_board_id, link_to_board_id |
board |
task_id, parent_task_id, source_task_id, target_task_id, depends_on |
task |
column_id |
column |
assigned_to_agent_id |
agent |
Two fields are deliberately excluded, and treating either as a ref would corrupt a valid
request: blocked_by is a link type, not an id; depends_on_refs holds batch-local aliases
naming other cards in the same bulk payload, which do not exist yet and are resolved further
downstream.
A body ref that does not resolve is left untouched and forwarded, so you get the downstream
field-level 422 rather than a blunt gateway 404. An ambiguous body ref is the exception and
still raises 409.
Where refs do NOT resolve¶
Five parameter slots require a full UUID. This is not an oversight and it is stated on each parameter individually, rather than as a blanket promise that quietly breaks:
| Operation | Parameter | Why |
|---|---|---|
getBoardSummary |
board_id |
query parameter, no body |
getTaskDistribution |
board_id |
query parameter, no body |
searchTasks |
board_id |
query parameter, no body |
removeBoardFromProject |
board_id |
path shape matches no rule |
sendEmailFromCard |
task_id |
the two-identifier rule needs a literal /tasks/ segment |
Across the agent tool surface this is 5 UUID-only slots against 52 ref-capable ones. Each is labelled in the tool description your agent reads, so it knows before it calls rather than after it fails.
Ambiguity returns 409, not a guess¶
A UUID prefix that matches more than one row never picks one. It returns:
{
"error": "ambiguous_ref",
"ref": "86a304a8",
"kind": "task",
"candidates": ["86a304a8-...-a1", "86a304a8-...-b7"],
"message": "'86a304a8' matches 2 tasks. Use more characters."
}
Add characters until it is unique. Eight hex characters is the minimum, not a guarantee.
Short codes for main-API entities¶
Four entity types outside the board carry a typed short code, returned alongside the UUID:
| Prefix | Entity |
|---|---|
hir_ |
an agent hire |
psn_ |
a persona |
env_ |
an environment |
pkg_ |
an installed package |
{ "id": "7abd705b-fde3-4753-a5a7-0a174e665f7b", "short_id": "pkg_b021dyc", "state": "active" }
The prefix is part of the identity: a code from the wrong table will not resolve against the right one.
Cost¶
Zero extra queries when every identifier is already a canonical UUID, which is what the
dashboard and every pre-existing integration send. Work is proportional to the number of short
refs in a request, not to traffic. Refs are grouped by kind, so a body carrying depends_on
with ten refs costs one query, not ten.