You are viewing a non-production environment — the API surface may differ from production.

Objects and fields

Attach typed, versioned fields to items, guests, reservations, tables, locations, tabs, orders and purchases, and read or write them through the API.

Merchants can extend Karma's objects with their own fields: a producer and vintage on a wine, a purchase-order reference on a tab, a delivery zone on a table. Fields are typed, inherit down the location tree, carry a version for safe concurrent edits, and appear on the object's normal API responses.

Use fields when the data has a type and a meaning. For opaque integration identifiers such as your own SKU or shop ID, the free-form metadata object on items and locations is still the right place.

For group attributes, linked-item values on variants, and customer checkout input, see Variant custom fields.

Objects

Fields attach to one of ten object types. The objectType path segment and the record identifier are:

objectTypeRecord id
itemsinventory item id
guestsuser id, scoped to the location
reservationsbooking id
tablestable UUID
locationsthe location id itself
tabstab UUID
orderskitchen order UUID
purchasespurchase id
retailer_usersstaff user id, scoped to the location
cost_centerscost center id
variant_groupsvariant group id
loyalty_membersloyalty membership account id

Every route lives under /api/v1/locations/{locationId}/object-types/{objectType} and uses the standard envelope. Reading and writing values takes the object's own read and update permissions; managing definitions takes the settings permission.

Definitions

A definition has an immutable id, an immutable key, a type, a label, a group, validation config and channel policies. Keys are namespaced by owner: merchant fields are loc<ownerLocationId>.<key>, Karma-provided fields are karma.*.

TypeValue on the wire
text, long_textstring
numberdecimal as a string, e.g. "12.5"
currency{ "amount": "80", "currency": "SEK" }
booleantrue / false
date"2026-09-24"
select, taxonomyan option id, never its label
multiselectarray of option ids
formula, rollupcomputed, read-only
relationid of a record of config.relationObject

Definitions inherit: a field created at a parent location applies to every descendant. A descendant may customise the label, group and placement and add its own options through the extensions route, but cannot change the type, validation or access policy.

Fetch the effective schema for a location and object:

curl "$COMMON_API/api/v1/locations/1234/object-types/items/fields" \
  -H "X-API-Key: $KARMA_API_KEY"

The response lists fields with their config, channels, revision, inherited and canWrite, plus capabilities telling you whether this location may customise fields (custom_fields, part of Intelligence) and use Kaia suggestions.

Create a field:

curl -X POST "$COMMON_API/api/v1/locations/1234/object-types/tabs/fields" \
  -H "X-API-Key: $KARMA_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "key": "po_reference",
    "label": "PO reference",
    "group": "Accounting",
    "type": "text",
    "config": { "maxLength": 40 },
    "channels": {
      "staff": true, "table": true, "tableDefault": false,
      "guestDisplay": false, "guestInput": false,
      "apiRead": true, "apiWrite": true,
      "analytics": true, "aiSource": false
    }
  }'

Returns 201 with a Location header. Updating goes through PATCH .../fields/{id} with the revision you last read; a stale revision is rejected with 409. Fields are archived, never deleted, and a key stays reserved after archival.

Channels are the access policy

An API key only sees fields with apiRead and can only write fields with apiWrite. A field that is staff-only is simply absent from API responses. Guest surfaces see only guestDisplay fields, and never schema or configuration.

Values

Read a record's values:

curl "$COMMON_API/api/v1/locations/1234/object-types/tabs/records/59e4ce7d-b801-4323-af9d-ba0ca4bd757a/values" \
  -H "X-API-Key: $KARMA_API_KEY"
{
  "status": "success",
  "data": {
    "values": [
      {
        "fieldId": "2c6f…",
        "value": "PO-42",
        "version": 3,
        "source": "api",
        "status": "fresh",
        "sourceLocationId": 1234,
        "updatedAt": "2026-09-24T09:12:40.000Z"
      }
    ]
  }
}

source is who last set it: manual, api, ai, formula or rollup. status matters for computed fields: fresh, stale (an input changed and the recompute is pending), error (see error) or frozen (the plan no longer covers calculations, the last value is kept). Unset fields are omitted; an explicitly cleared field is present with value: null.

Write values atomically, one call per record, up to 100 changes. Use the expectedVersion you last read to reject stale edits; a mismatch fails the whole request with 409 VERSION_CONFLICT and nothing is written. It is optional: omitting it intentionally updates the latest value. Identify each change by fieldId or a stable key.

curl -X PATCH "$COMMON_API/api/v1/locations/1234/object-types/tabs/records/59e4ce7d-…/values" \
  -H "X-API-Key: $KARMA_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "changes": [
      { "fieldId": "2c6f…", "expectedVersion": 3, "value": "PO-43" },
      { "fieldId": "9a10…", "expectedVersion": 0, "value": { "amount": "1250", "currency": "SEK" } }
    ]
  }'

A never-set field has version 0. Send "value": null to clear a field, or "reset": true on a shared item at a child location to drop the local override and inherit the parent's value again. Validation failures return 422 FIELD_VALIDATION with the offending field in the message.

Read many records at once with POST .../values/batch and up to 200 ids.

Values on normal create and update calls

Authenticated staff and API integrations can include optional customFields operations in the normal record request. They are saved in the same transaction as the record, so invalid values or a stale version roll back the whole edit.

For example, update a booking's guest count and meal abbreviation in one call:

PUT /api/v1/bookings/{bookingId}
Content-Type: application/json
{
  "guestCount": 4,
  "customFields": [
    { "fieldId": "<MA-field-UUID>", "expectedVersion": 1, "value": "LU" }
  ]
}

The same array is accepted on booking creation, with expectedVersion: 0 for each initial value. Definitions must already exist; inline operations set values, they do not create field definitions.

ObjectNormal calls accepting inline operations
ReservationsPOST /bookings, PUT /bookings/{id}
ItemsPOST /inventory, PUT /inventory/{id}; bulk-upsert item rows and the bulk update patch
GuestsPOST /locations/{locationId}/customers, PATCH /locations/{locationId}/customers/{userId}
TablesPOST /tables, PUT /tables/{id}, PUT /tables/bulk
LocationsPOST /locations, PATCH /locations/{locationId}/settings
TabsPOST /tabs/resolve, PATCH /tabs/{id}, PATCH /tabs/{id}/metadata
OrdersPOST /orders/create, POST /orders/append-to-dispatch, PATCH /orders/{id}
PurchasesPATCH /purchases/{id}, POST /purchases/{purchaseIntentId}/connect-customer
Staff usersPUT /retailer/users/{id}, PUT /retailer/users/me
Cost centersPOST /cost-centers, PUT /cost-centers/{id}
Variant groupsPOST /variant-groups, PUT /variant-groups/{id}; top-level customFields edits group values
Variant linked itemsPATCH /variants/{id}?locationId={locationId}, or variants[].customFields in group writes

All paths above start with /api/v1. The general PATCH calls on tabs, orders and purchases edit custom fields only; their lifecycle and payment actions keep their dedicated APIs. Staff-user creation creates an invitation, so values attach to the actual user after acceptance. Purchases are created by payment flows, rather than a general record-create endpoint.

  • Omit customFields to preserve values on updates. An empty array makes no field changes.
  • You do not need to include unset optional fields. On creation, writable required fields must have a value; conditional required fields apply when their condition matches. A submitted update checks the resulting required values, preserving existing values for omitted fields.
  • Send value: null to clear an optional field. Required fields cannot be cleared. Use reset: true without value to drop an inherited-item override; a required field must still have an inherited value.
  • Send the last-read expectedVersion for updates. A conflict returns 409 VERSION_CONFLICT; invalid values return 422 FIELD_VALIDATION. Permissions, API channel policies and plan requirements are identical to the values endpoint.
  • Guest checkout continues to use the guest form's schema token and answers, rather than staff field operations.

The response customFields array contains labels and display values as well as raw values and versions. Do not copy that entire read projection into a write: send only fieldId, expectedVersion and value (or reset).

Values on normal responses

You do not need the fields API to read values. Native detail and list responses for supported object types carries a read-only projection of the fields your key may read:

{
  "id": 456,
  "title": "Chianti Classico",
  "customFields": [
    { "fieldId": "…", "key": "loc1234.producer", "label": "Producer", "group": "Wine",
      "type": "text", "value": "Antinori", "displayValue": "Antinori", "version": 2, "status": "fresh" }
  ],
  "metadata": {
    "externalId": "sku-3732185",
    "customFields": { "loc1234.producer": "Antinori" }
  }
}

metadata.customFields is the same data keyed by field key, convenient for lookups. Both are current values, not a snapshot from the time of a sale. Writing metadata.customFields through a generic metadata update is rejected; values change through inline write operations or the values route above. Webhook payloads carry the same projection, described in Receiving webhooks.

Searching by field

POST .../search filters and sorts records by typed values with cursor pagination:

curl -X POST "$COMMON_API/api/v1/locations/1234/object-types/items/search" \
  -H "X-API-Key: $KARMA_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "filters": [
      { "field": "7b3e…", "operator": "eq", "value": "opt_red" },
      { "field": "c412…", "operator": "gte", "value": "2018" }
    ],
    "sort": { "field": "c412…", "descending": true },
    "limit": 50
  }'

Operators are eq, gte, lte, contains (multiselect membership or text) and unset. Up to five filters; query searches titles; ids restricts to a set. Follow nextCursor from the response for the next page. Filtering by generic metadata keys still goes through the object's own list endpoint; this route is for typed field values.

Errors

StatusCodeMeaning
400INVALID_INPUT, INVALID_FILTERMalformed body, or a filter names a field you cannot read
403FORBIDDEN, PLAN_REQUIREDMissing permission, channel, or the custom_fields entitlement
404NOT_FOUNDRecord or field does not exist at this location
409VERSION_CONFLICT, CONFLICTStale version or revision, or the key already exists
422FIELD_VALIDATION, FIELD_LIMIT, SCHEMA_LIMITValue rejected, or a bound reached

Bounds: 100 merchant fields per location, 500 effective fields per object type, 1,000 options per field, 200 records per batch or search page.

On this page