Backlog API
Assets
Asset types, custom fields, assets, their history, hand-overs, renewals and relations.
Base URLhttps://backlog.polder.work
Assets is the register of what a team owns and manages: laptops, domains, licences, servers. Every asset has a type, and a type defines its own custom fields and statuses. This page covers the types, the fields and the assets themselves.
Two scopes: assets:read reads, assets:write registers and changes. Like every scope, they only narrow what the token's owner may do. The owner's rights decide the rest:
- The workspace must have Assets switched on, and the owner must be a member who may use it. A guest (a client) gets
403, and so does a workspace without Assets. - Creating types and fields is for workspace administrators. Everything else (registering, changing, archiving, issuing, returning, renewing, relating) is for every member who can see the asset's type.
- A type can be visible to everybody, to the members of one team, or to administrators only. A type you cannot see is not in any list, and its assets and the type itself answer
404, never403. - A sensitive field is left out of every answer unless you are an administrator. When you write
values, the stored value of a sensitive field stays untouched, whatever you send.
Identifiers
An asset is addressed by its identifier: the prefix of its type, a dash and its number in that type, such as LAP-12. In this API the identifier goes where {asset} is. Matching ignores case (lap-12 works). Identifiers are unique per workspace. Types and fields are addressed by their numeric id.
An identifier that does not exist, or that belongs to a type you cannot see, is a 404. An archived asset still resolves.
The asset object
{
"id": 41,
"identifier": "LAP-12",
"name": "MacBook Pro 14",
"url": "https://assets.polder.work/assets/41",
"type": { "id": 3, "name": "Laptop", "prefix": "LAP", "icon": "laptop", "color": "#2563eb", "archived": false },
"status": { "id": 7, "name": "In use", "color": "#16a34a", "category": "active" },
"owner": { "key": "team:2", "type": "team", "id": 2, "name": "Engineering" },
"holder": { "key": "user:5", "type": "user", "id": 5, "name": "Sanne" },
"values": { "serial_number": "C02XL1ABC", "purchase_price": { "amount": 2199.0, "currency": "EUR" } },
"nextDeadline": "2027-03-01",
"archived": false,
"updatedAt": "2026-10-20T09:14:00+00:00"
}
| Field | Description |
|---|---|
identifier |
LAP-12. |
status |
null when the type has no status. category is planned, active or closed, the same across types that name their statuses differently. |
owner, holder |
A party, or null. Written as user:5 (a member), team:2 or customer:7 (a customer of the workspace) in the key field and in every request. The owner is who is responsible, the holder is who has the asset now. |
values |
Custom field values by field key. Empty values are left out. |
nextDeadline |
The earliest date among the asset's fields that run out (YYYY-MM-DD), or null. Present in lists and writes. |
archived |
Whether the asset is archived. |
updatedAt |
ISO 8601. |
Note that this object uses camelCase (nextDeadline, updatedAt), unlike most of the Backlog API.
Custom fields
Each type defines fields. A field has a stable key that is made from its label when you create it (Serial number becomes serial_number, a second one gets _2). Renaming a field later changes only its label, never the key. Values are stored and sent by key, in the shape the field type calls for:
Field type |
Value in values |
Checked as |
|---|---|---|
text |
string | max 500 characters |
longtext |
string | max 20 000 characters |
number |
number | numeric |
money |
{"amount": 2199.5, "currency": "EUR"} |
amount numeric, currency 3 letters, defaults to the field's options.currency (EUR) |
date |
"2026-12-31" |
Y-m-d |
datetime |
"2026-12-31T09:00:00+01:00" |
any date; stored as ISO 8601 |
select |
one choice value |
must be one of the field's options.choices[].value |
multiselect |
list of choice values |
every item must be a choice |
boolean |
true / false |
boolean |
member |
user id (integer) | a member of the workspace |
customer |
customer id (integer) | a customer of the workspace |
party |
"user:5", "team:2" or "customer:7" |
must exist in the workspace |
url |
string | http or https, max 2000 |
email |
string | valid address, max 255 |
asset |
asset id (integer) | an asset of the workspace, of the type in options.asset_type_id if the field names one |
A required field must be present. Sending null or an empty string clears a value. A date or datetime field can be marked expires: it then counts as a deadline, gets reminders and shows up in nextDeadline, expiring and renew.
Defaults apply when you create an asset: a field's default fills any value you leave out. A status can also require fields: moving an asset into that status (or creating it there) fails with 422 on status_id while a required field is empty. Adding a requirement does not lock assets already in the status.
List asset types
/api/v1/workspaces/{workspace}/asset-typesReturns the active (non-archived) types you may see, in their set order, each with its statuses and fields.
curl https://backlog.polder.work/api/v1/workspaces/7/asset-types \
-H "Authorization: Bearer $POLDER_TOKEN"
{
"data": [
{
"id": 3,
"name": "Laptop",
"prefix": "LAP",
"icon": "laptop",
"color": "#2563eb",
"archived": false,
"description": null,
"visibility": "workspace",
"statuses": [
{ "id": 6, "name": "In stock", "color": "#9ca3af", "category": "planned" },
{ "id": 7, "name": "In use", "color": "#16a34a", "category": "active" },
{ "id": 8, "name": "Retired", "color": "#6b7280", "category": "closed" }
],
"fields": [
{
"id": 11, "key": "serial_number", "label": "Serial number", "type": "text",
"required": false, "options": null, "default": null, "help": null, "section": null,
"showInList": true, "sensitive": false, "expires": false,
"reminderDays": [], "renewMonths": null, "archived": false
}
]
}
]
}
Archived fields are not listed. Sensitive fields are listed for administrators only.
Get an asset type
/api/v1/workspaces/{workspace}/asset-types/{type}One type in the same shape as above. 404 if the type does not exist in this workspace, is archived, or is one you may not see.
Create an asset type
/api/v1/workspaces/{workspace}/asset-typesAdministrators only (403 otherwise). A new type comes with three starting statuses (in stock, in use, retired). The first type of a workspace also brings the standard relation kinds. Answers 201 with the type.
Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Max 100 characters. |
prefix |
string | no | Letters and digits, max 8; stored upper case. Left out, it is derived from the name (Laptop becomes LAP; a taken one gets a number, LAP2). Must be unique in the workspace, else 422. |
icon |
string | no | Max 40. |
color |
string | no | #rrggbb. |
description |
string | no | Max 1000. |
visibility |
string | no | workspace (everybody, the default), team or admins. |
team_id |
integer | no | A team of this workspace. Only used with visibility: team; ignored (set to null) for the other two. |
curl https://backlog.polder.work/api/v1/workspaces/7/asset-types \
-H "Authorization: Bearer $POLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Laptop", "visibility": "team", "team_id": 3}'
Changing or archiving a type, and its statuses, are not part of the API; use the Assets screens.
Add a field to a type
/api/v1/workspaces/{workspace}/asset-types/{type}/fieldsAdministrators only. Answers 201 with the new field. The field is added at the end of the type.
Body
| Field | Type | Required | Description |
|---|---|---|---|
label |
string | yes | Max 100. The key is made from it and never changes afterwards. |
type |
string | yes | One of text, longtext, number, money, date, datetime, select, multiselect, boolean, member, customer, party, url, email, asset. |
required |
boolean | no | Default false. |
options |
object | no | Depends on type, see below. Ignored keys are dropped. |
default |
any | no | The value a new asset starts with. |
help |
string | no | Hint under the input, max 255. |
section |
string | no | Group heading, max 60. |
show_in_list |
boolean | no | Show as a column in lists. |
sensitive |
boolean | no | Administrators only, to read and to write. |
expires |
boolean | no | Only for date and datetime; ignored for other types. The field becomes a deadline. |
reminder_days |
integer[] | no | Max 10 items, each between -365 and 365: days before the date to remind (a negative number is after). Duplicates are removed. |
renew_months |
integer | no | 1 to 120. Only for date fields: the interval renew uses. |
options by type
| Type | Options |
|---|---|
select, multiselect |
{"choices": [{"label": "Dell", "value": "dell", "color": "#2563eb"}]}. A choice may also be a plain string. value defaults to the slugged label; color is optional. Empty labels are dropped. |
money |
{"currency": "EUR", "recurring": "month"}. currency defaults to EUR; recurring is month or year for a running cost, absent for a value. |
asset |
{"asset_type_id": 3} limits the field to assets of one type. |
date, datetime |
{"draft_to_customer": true} asks for a draft mailing to the customer when reminders start. |
curl https://backlog.polder.work/api/v1/workspaces/7/asset-types/3/fields \
-H "Authorization: Bearer $POLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "Warranty ends", "type": "date", "expires": true, "reminder_days": [30, 7, 0], "renew_months": 12}'
{ "data": { "id": 14, "key": "warranty_ends", "label": "Warranty ends", "type": "date", "required": false, "sensitive": false, "expires": true } }
Marking a field as expiring rebuilds the deadlines of every active asset of the type.
Change a field
/api/v1/workspaces/{workspace}/asset-types/{type}/fields/{field}Administrators only. Takes the same body as creating a field, with every field optional; only what you send changes. 404 if the field is not on this type.
The type of a field cannot change once any asset has a value for it (422 on type). Archive the field in the app and add a new one instead. The key never changes.
Answers 200 with the same short field shape as creating.
List relation kinds
/api/v1/workspaces/{workspace}/asset-relation-typesThe kinds of relation the workspace has, to use as relation_type_id when you relate two assets. Empty until the first asset type exists.
{ "data": [ { "id": 1, "key": "runs_on", "name": "runs on", "inverse_name": "hosts", "hierarchical": true } ] }
hierarchical kinds refuse a loop (A part of B part of A).
List assets
/api/v1/workspaces/{workspace}/assetsAssets of the types you may see, newest change first. Archived assets are left out unless you ask for them.
Query
| Parameter | Description |
|---|---|
q |
Text search over the name, identifier and all text fields (max 200 characters). |
type |
Asset type id. |
status |
Status id. |
category |
planned, active or closed. |
owner, holder |
A party such as user:5, team:2, customer:7, or none for assets without one. |
expiring |
Integer 0 to 3650: assets with a deadline within that many days. 0 is already past or due today. |
archived |
1 lists only archived assets. |
f[key] |
Filter on a custom field by its key, for example f[serial_number]=C02. Text, long text, URL and email match as a substring; number and date fields take f[key][from] and f[key][to]; a multiselect matches when it contains the choice; booleans take true/false; other types match exactly. A key that does not exist is ignored. Pass type as well when several types use the same key. |
page, per_page |
Paging. per_page defaults to 50, at most 200. |
curl "https://backlog.polder.work/api/v1/workspaces/7/assets?type=3&expiring=30&per_page=20" \
-H "Authorization: Bearer $POLDER_TOKEN"
{
"data": [ { "identifier": "LAP-12", "name": "MacBook Pro 14", "nextDeadline": "2026-11-14", "…": "…" } ],
"meta": { "page": 1, "per_page": 20, "total": 1, "last_page": 1 }
}
The order is fixed. There is no sort parameter in the API.
Get an asset
/api/v1/workspaces/{workspace}/assets/{asset}One asset: everything in the asset object plus notes, deadlines and relations. {asset} is the identifier.
{
"data": {
"identifier": "LAP-12",
"notes": "Bought with the Q3 batch.",
"deadlines": [
{ "key": "warranty_ends", "label": "Warranty ends", "dueOn": "2026-11-14", "daysLeft": 17, "renewMonths": 12, "reminderDays": [30, 7, 0] }
],
"relations": [
{ "label": "runs on", "asset": { "identifier": "SRV-1", "name": "Host 1", "url": "https://assets.polder.work/assets/9", "status": "In use" } }
],
"…": "…"
}
}
relations lists both directions; label is already worded from this asset's side ("runs on" or "hosts"). Related assets you may not see are left out.
Get the history of an asset
/api/v1/workspaces/{workspace}/assets/{asset}/historyEverything that happened to the asset, newest first, paged like a list (page, per_page).
{
"data": [
{ "id": 310, "kind": "issued", "by": 5, "data": { "to": "user:5", "name": "Sanne", "condition": "good" }, "at": "2026-10-20T09:14:00+00:00" }
],
"meta": { "page": 1, "per_page": 50, "total": 12, "last_page": 1 }
}
by is the user id (or null for the system). kind includes created, updated (with data.changes, field by field as [old, new]), status, owner, issued, returned, renewed, related, unrelated, archived, restored, reminded, holder_left, commented, linked and unlinked. data depends on the kind.
Create an asset
/api/v1/workspaces/{workspace}/assetsRegisters an asset. It takes the next number of its type, so the identifier is in the answer. Goes through the same action as the app: values are validated, the history gets a created event, Flows and notifications hear about it. Answers 201 with the asset.
Body
| Field | Type | Required | Description |
|---|---|---|---|
asset_type_id |
integer | yes | An active type you can see, else 404. |
name |
string | yes | Max 200. |
status_id |
integer | no | A status of that type. Defaults to the type's default status. |
notes |
string | no | Max 50 000. |
values |
object | no | Custom field values by key, see Custom fields. Unknown keys are ignored. |
owner |
string | no | A party, user:5. 422 if it does not exist in the workspace. |
holder |
string | no | A party. Creates the hand-over as well, as if you had issued it. |
curl https://backlog.polder.work/api/v1/workspaces/7/assets \
-H "Authorization: Bearer $POLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"asset_type_id": 3, "name": "MacBook Pro 14", "owner": "team:2", "holder": "user:5",
"values": {"serial_number": "C02XL1ABC", "warranty_ends": "2027-03-01"}}'
Validation errors are 422 and name the field: values.warranty_ends for a field value, status_id for a missing required field.
Change an asset
/api/v1/workspaces/{workspace}/assets/{asset}Only what you send changes.
| Field | Type | Description |
|---|---|---|
name |
string | Max 200, cannot be empty. |
status_id |
integer or null | A status of the asset's type. null clears it. Fails with 422 if the new status requires fields that are empty. |
notes |
string or null | Max 50 000. |
values |
object | Merged into the stored values: send only the keys you change, null clears one. |
owner |
string or null | A party, or null/empty to clear it. |
The holder is not changed here; use issue and return. The history records what changed, with a separate status and owner event when those moved. Answers 200 with the asset.
Archive or restore an asset
/api/v1/workspaces/{workspace}/assets/{asset}/archiveAssets are never deleted: the number stays taken and the history stays readable. Send {"restore": true} to bring an archived asset back. Both directions are idempotent.
{ "data": { "archived": true } }
Issue an asset
/api/v1/workspaces/{workspace}/assets/{asset}/issueHands the asset to a member, a team or a customer. Whoever has it now gets it taken back first (without a returned event), the hand-over is written down with its condition, and the new party becomes the holder. Answers 200 with the asset.
| Field | Type | Required | Description |
|---|---|---|---|
to |
string | yes | A party: user:5, team:2, customer:7. 422 if it is unknown or not in this workspace. |
condition |
string | no | The state it is handed over in, max 200. |
note |
string | no | Max 2000. |
curl https://backlog.polder.work/api/v1/workspaces/7/assets/LAP-12/issue \
-H "Authorization: Bearer $POLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"to": "user:5", "condition": "good, with charger"}'
Return an asset
/api/v1/workspaces/{workspace}/assets/{asset}/returnTakes the asset back from its holder. Body is optional: condition (max 200) and note (max 2000). An asset nobody holds is not an error: nothing changes. Answers 200 with the asset.
Renew a deadline
/api/v1/workspaces/{workspace}/assets/{asset}/renewMoves a date field that runs out to its next date: the certificate was renewed, the warranty extended.
| Field | Type | Required | Description |
|---|---|---|---|
field |
string | yes | The key of a date field of the type that has expires set. Otherwise 422 on field. |
to |
date | no | The new date. Without it, the date moves by the field's renew_months, or by a year. It counts from the old date, or from today if that has passed. |
Reminders start over by themselves, and the history records renewed as well as the change. Answers 200 with the asset.
curl https://backlog.polder.work/api/v1/workspaces/7/assets/CRT-3/renew \
-H "Authorization: Bearer $POLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"field": "expires_on", "to": "2027-01-01"}'
Relate two assets
/api/v1/workspaces/{workspace}/assets/{asset}/relationsLinks this asset to another one. Reading the relation as "asset kind with" gives the default direction: LAP-12 runs on SRV-1 is {asset} = LAP-12, with = SRV-1.
| Field | Type | Required | Description |
|---|---|---|---|
relation_type_id |
integer | yes | From relation kinds; 404 if it is not in this workspace. |
with |
string | yes | The identifier of the other asset (404 if you cannot see it). |
direction |
string | no | forward (default) or inverse, which swaps the two assets. |
It is 422 to relate an asset to itself, to assets of different workspaces, or to loop a hierarchical kind. Relating the same pair twice is not an error and creates nothing new. Both assets get a related history event.
Answers 201 with the asset's relations, including the id you need to remove one:
{ "data": { "relations": [ { "id": 5, "label": "runs on", "asset": { "identifier": "SRV-1", "name": "Host 1", "url": "…", "status": "In use" } } ] } }
Note The
relationsin Get an asset carry noid. To remove a relation you need the id from the answer here.
Remove a relation
/api/v1/workspaces/{workspace}/assets/{asset}/relations/{relation}{relation} is the relation id. 404 if it does not involve this asset, or if you may not see the asset on its other end. Both assets get an unrelated event.
{ "data": { "removed": true } }
Errors
| Status | Meaning |
|---|---|
401 |
No or invalid token. |
403 |
The token lacks the scope, Assets is off for the workspace or for you, you are a guest, or you are not an administrator on a type or field route. |
404 |
Unknown id or identifier, or a type you may not see. |
422 |
Validation: the body names the failing fields. |
429 |
Too many requests. |
The same actions are available to assistants as the MCP tools list_asset_types, list_assets, get_asset, create_asset and update_asset.