PolderAPI

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, never 403.
  • 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

GET/api/v1/workspaces/{workspace}/asset-typesscope assets:read

Returns 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

GET/api/v1/workspaces/{workspace}/asset-types/{type}scope assets:read

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

POST/api/v1/workspaces/{workspace}/asset-typesscope assets:write

Administrators 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

POST/api/v1/workspaces/{workspace}/asset-types/{type}/fieldsscope assets:write

Administrators 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

PATCH/api/v1/workspaces/{workspace}/asset-types/{type}/fields/{field}scope assets:write

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

GET/api/v1/workspaces/{workspace}/asset-relation-typesscope assets:read

The 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

GET/api/v1/workspaces/{workspace}/assetsscope assets:read

Assets 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

GET/api/v1/workspaces/{workspace}/assets/{asset}scope assets:read

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

GET/api/v1/workspaces/{workspace}/assets/{asset}/historyscope assets:read

Everything 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

POST/api/v1/workspaces/{workspace}/assetsscope assets:writethrottle 60 per minute

Registers 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

PATCH/api/v1/workspaces/{workspace}/assets/{asset}scope assets:write

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

POST/api/v1/workspaces/{workspace}/assets/{asset}/archivescope assets:write

Assets 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

POST/api/v1/workspaces/{workspace}/assets/{asset}/issuescope assets:write

Hands 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

POST/api/v1/workspaces/{workspace}/assets/{asset}/returnscope assets:write

Takes 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

POST/api/v1/workspaces/{workspace}/assets/{asset}/renewscope assets:write

Moves 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

POST/api/v1/workspaces/{workspace}/assets/{asset}/relationsscope assets:write

Links 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 relations in Get an asset carry no id. To remove a relation you need the id from the answer here.

Remove a relation

DELETE/api/v1/workspaces/{workspace}/assets/{asset}/relations/{relation}scope assets:write

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