CorebanqCorebanq Developer Docs
Itemsv1

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

GET
/v1/items/set

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. On this route that is a 500, not the 400 the item list gives: GetTotalSetItems catches the parser's query_m.invalid_sort_field and rewraps it as items_m.item_failed_to_list with no status code, which defaults to 500. With distinct=record_id the sort is not validated at all — applySorting builds the ORDER BY from the raw token. Omitted, the list is ordered by created_at descending.

filter?string

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

search_text?string

IGNORED on this route. Both spellings land in Parameters.SearchText, and only ExtractTextSearch reads that field — the set-item count and fetch call ParseSearchQuery on Parameters.Search directly and never invoke it. So ?search_text=... and ?search=... are dropped without an error and the response is the unfiltered page. Use search._text=... instead, which travels in the search map and is honoured; _text is the reserved field name.

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 the latest row per record_id. record_id is the only value the service acts on — GetSetItems compares the parameter against that one literal to choose the distinct count and fetch functions; any other value is neither honoured nor rejected, it falls through to the ordinary listing. Passing it also changes the status of an unknown search field on this route from 400 to 500, because the distinct count function does not carry the upgrade the ordinary one does.

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.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/items/set"
{
  "total": 0,
  "total_unfiltered": 0,
  "metadata": {},
  "keys": [
    "string"
  ],
  "has_more": true,
  "data": [
    {
      "record_id": "8bf519b6-a3e0-49d2-8e42-039542d9a489",
      "record_type": "string",
      "type": "string",
      "name": "string",
      "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"
}

POSTCreate new item

Creates an item. The name must be unique within the type. A caller without the create permission on items is refused with 403 `common.rbac_no_rec_access`. Accept-Language is not read on this route: only GetItem and ListItems pull constants.AcceptLanguage from the request, and this 201 body carries no localised fields.

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.