CorebanqCorebanq Developer Docs
Itemsv1

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

GET
/v1/items

Query Parameters

limit?integer

Maximum records to return. parseLimitOffset does not reject out-of-range values, it rewrites them: anything above 100 (constants.MaxLimit) is clamped to 100 with no signal to the caller, 0 and any negative other than -1 fall back to the default 10, and -1 (constants.LimitSkipDataFetching) is a counts-only mode — ShouldSkipDataFetching short-circuits the fetch and the envelope comes back with total and total_unfiltered filled in and data empty. A non-numeric value is 400 common.invalid_input.

offset?integer

Records to skip.

sort?string

Sort expression. The parser splits on commas and understands ONLY an optional leading minus for descending — sort=-created_at,name. There is no :asc / :desc suffix: ParseSortFields looks the whole token up in the field map, so sort=created_at:asc finds no such field and answers 400 query_m.invalid_sort_field. Omitted, the list is ordered by created_at descending. Note that sort is parsed on the fetch pass only: GetTotal runs with isCount true and skips it, and the fetch is not reached when the count is 0 — so an unknown sort field is 400 on a populated tenant and 200 on an empty one.

filter?string

JSON-encoded filter criteria. A value that is not valid JSON is rejected with 400.

search_text?string

Free-text search. Also accepted as search — parseStandardQueryParameter routes both spellings to the same slot. It is honoured on this route because the generic count and fetch both run ExtractTextSearch, which reads it. The equivalent inside the search map is search._text; the reserved field name is _text, not search_text.

stack?string

Group results by this field; data then comes back as an object keyed by the field value instead of an array.

distinct?string

Return one row per distinct value of this field. The field must be in validFields — anything else is 400 common.invalid_input, on the count pass as well as the fetch. The count then runs COUNT(DISTINCT ) and the fetch runs DISTINCT ON () with a forced descending sort on that same column, which is applied before your own sort.

search.<field>?string

Per-field filter, optionally with an operator suffix (.eq, .ne, .gte, .lte, .gt, .lt, ...). An unrecognised field is rejected by the shared parser.

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

curl -X GET "https://example.com/v1/items"
{
  "total": 0,
  "total_unfiltered": 0,
  "metadata": {},
  "keys": [
    "string"
  ],
  "has_more": true,
  "data": [
    {
      "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": 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"
}

GETGet set items

Lists set 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. Accept-Language is NOT read on this route. Only GetItem and ListItems pull constants.AcceptLanguage from the request; set items carry no localised fields, so the header is ignored here exactly as it is on the two POSTs. WARNING: there is no working way to filter set items by their modification time. validSetItemFields declares the field as updated_at mapped to column misc.set_items.updated_at, but the table names it modified_at — so search.updated_at reaches Postgres and errors, which is a 500, while search.modified_at is rejected by validateFieldName as an unknown field, which on this route is a 400 (see the responses). Neither spelling returns rows, and there is no third one. sort=modified_at is a 500 here as well, for the reason given on the sort parameter. Operators are eq, ne, gt, gte, lt, lte, in, nin, like, start_with, end_with — not neq, and not ilike or contains, which belong to the reserved text-search set and are rejected with 400 query_m.invalid_operator.

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