# Short references

Source: https://opvs.ai/docs/concepts/short-references

> 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.

**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.

```bash
curl -H "Authorization: Bearer $OPVS_PAT" \
     https://api.opvs.ai/api/v1/board/boards/OPS
```

**Response**

```json
{ "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.

```bash
curl -H "Authorization: Bearer $OPVS_PAT" \
     https://api.opvs.ai/api/v1/board/boards/OPS/tasks/OPS-142
```

**Response**

```json
{ "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}`.

```bash
curl -H "Authorization: Bearer $OPVS_PAT" \
     https://api.opvs.ai/api/v1/board/tasks/OPS-142
```

**Response**

```json
{ "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.

```bash
curl -H "Authorization: Bearer $OPVS_PAT" \
     https://api.opvs.ai/api/v1/board/columns/OPS/backlog
```

**Response**

```json
{ "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.

```bash
curl -H "Authorization: Bearer $OPVS_PAT" \
     https://api.opvs.ai/api/v1/board/files/f_7k2m9qd/download
```

**Response**

```json
{ "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.

```bash
curl -H "Authorization: Bearer $OPVS_PAT" \
     https://api.opvs.ai/api/v1/board/agents/@martin/tasks
```

**Response**

```json
{ "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:

```json
{
  "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

```json
{ "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.
