CorebanqCorebanq Developer Docs
Itemsv1

Get item by ID

Returns one item, with title_loc/description_loc resolved from Accept-Language. A caller without the read permission on items is refused with 403 `common.rbac_no_rec_access`.

GET
/v1/items/{id}

Path Parameters

id*string

Item UUID

id*string

id path parameter

Header Parameters

Accept-Language?string

Locale for the localised title and description. Honoured on GET /v1/items and GET /v1/items/{id} only — those are the two handlers that read constants.AcceptLanguage. Resolution is four steps, not two: exact match on the stored locale map; then the base language, so en-US falls to en; then config.DefaultLanguage(), which is the app-config default_language and only equals en when that key is unset; and finally, when none of those is present in the map, the FIRST KEY IN ALPHABETICAL ORDER of whatever the item stores. So an item holding only de and fr, requested with Accept-Language: en or with no header at all, comes back with the German text in title_loc and no indication that the locale is not the one asked for. title_loc is never empty for a populated item.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/items/string"
{
  "type": "product",
  "name": "premium-savings-account",
  "title": {
    "en": "Premium Savings Account",
    "de": "Premium-Sparkonto"
  },
  "description": {
    "property1": "string",
    "property2": "string"
  },
  "title_loc": "string",
  "description_loc": "string",
  "sort_order": 0,
  "system": false,
  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  "created_at": "2019-08-24T14:15:22Z",
  "created_by": "ee824cad-d7a6-4f48-87dc-e8461a9201c4",
  "modified_at": "2019-08-24T14:15:22Z",
  "modified_by": "e8d4374d-93a1-4e98-a6c6-fdcf00c5059f",
  "active": true,
  "metadata": {}
}
{
  "status": 409,
  "message": "Item with this name already exists",
  "code": "items_m.item_name_exists",
  "class": "business",
  "retryable": false,
  "details": [
    {
      "field": "title",
      "rule": "required",
      "param": "string",
      "message": "title is required"
    }
  ]
}
{
  "status": 409,
  "message": "Item with this name already exists",
  "code": "items_m.item_name_exists",
  "class": "business",
  "retryable": false,
  "details": [
    {
      "field": "title",
      "rule": "required",
      "param": "string",
      "message": "title is required"
    }
  ]
}
{
  "status": 409,
  "message": "Item with this name already exists",
  "code": "items_m.item_name_exists",
  "class": "business",
  "retryable": false,
  "details": [
    {
      "field": "title",
      "rule": "required",
      "param": "string",
      "message": "title is required"
    }
  ]
}
{
  "status": 409,
  "message": "Item with this name already exists",
  "code": "items_m.item_name_exists",
  "class": "business",
  "retryable": false,
  "details": [
    {
      "field": "title",
      "rule": "required",
      "param": "string",
      "message": "title is required"
    }
  ]
}
{
  "status": 429,
  "message": "rate limit exceeded",
  "code": "rate_limits_m.exceeded",
  "class": "temporary",
  "retryable": true
}
{
  "status": 409,
  "message": "Item with this name already exists",
  "code": "items_m.item_name_exists",
  "class": "business",
  "retryable": false,
  "details": [
    {
      "field": "title",
      "rule": "required",
      "param": "string",
      "message": "title is required"
    }
  ]
}

{
  "overall_status": "unhealthy",
  "message": "Service is shutting down",
  "timestamp": "2026-08-27T15:04:05Z"
}

GETList items

Lists items in the standard envelope. Read scope is enforced in the query, not by an early check: the service passes RbacRecordType into GetAllTotal, which asks for the read-all (A) mask on the record type and, when the caller does not hold it, resolves the record ids they may read and merges them into the query as a scope predicate. So a caller without A sees only their permitted rows, and a caller with no permitted rows gets an empty list with total 0 — not a 403. WARNING: there is no working way to filter or sort the item list by modification time. validFields declares the field as updated_at mapped to column misc.items.updated_at, and misc.items has no such column — the migration and the production baseline both name it modified_at, and no modified_at entry exists in the map. So search.updated_at reaches Postgres and errors, answering 500; search.modified_at is an unknown field, which on THIS route is also a 500 (see the 500 response); and sort=modified_at is 400 query_m.invalid_sort_field, but only when the count found rows, because the count pass skips sort parsing entirely. created_at is the only usable time field. The set-item list carries the identical defect, resolved to different status codes — see GET /v1/items/set.

GETGet distinct item types with counts

Returns each distinct item type with how many items carry it. A bare array, not the list envelope. The count is over ACTIVE items only (WHERE active = true), so it does not match the totals from GET /v1/items unless that list is filtered the same way. This route performs no record-permission check and takes no query parameters.