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

Variant custom fields

Read and write group attributes and linked-item fields, and submit customer answers for selected variants.

Variant groups and their linked inventory items have separate custom fields:

  • The group's top-level customFields belong to object type variant_groups.
  • Each item-based variant's customFields belong to object type items, identified by that selection's itemId. Selections referencing the same item share its stored values at the same location.
  • Customer answers collected during checkout are order-line snapshots, submitted as guest_field_answers. They do not overwrite stored group or inventory values.

Stored group attributes

Create definitions through /api/v1/locations/{locationId}/object-types/variant_groups/fields. Read values in the group's normal GET response. Set values using top-level customFields in POST /api/v1/variant-groups or PUT /api/v1/variant-groups/{id}. These writes are atomic with the group, like reservation custom fields.

Use fieldId or a stable key from the effective definitions. expectedVersion is optional; include the last-read version to reject stale edits with 409 VERSION_CONFLICT. A new value starts at version 0. See Inline values.

Stored values directly on a variant

Read variants[].customFields and variants[].metadata.customFields in a group response, or fetch the selected option directly:

curl "$COMMON_API/api/v1/variants/34?locationId=1234" \
  -H "X-API-Key: $KARMA_API_KEY"

Patch its linked item's values without replacing the group's selections:

curl -X PATCH "$COMMON_API/api/v1/variants/34?locationId=1234" \
  -H "X-API-Key: $KARMA_API_KEY" -H 'Content-Type: application/json' \
  -d '{"customFields":[{"key":"engraving","expectedVersion":0,"value":"Alex"}]}'

Replace engraving with your actual item-field key. Omit expectedVersion only when you intend to overwrite the latest value. Each change accepts either fieldId or key, and either value or reset: true. Omitted fields and an empty array preserve existing values. value: null clears an optional field; resetting removes the local override and restores an inherited item value.

The response contains id (the variant ID), itemId, locationId, customFields, and metadata.customFields. It is a field projection; use the linked item's normal GET for full inventory details. Legacy selections with itemId: null have no linked form or fields and return 422 on the direct field route.

The API key needs variant_groups.read and inventory.read; writes also need inventory.update and the field's apiWrite channel. Only API-readable fields are returned. The requested location must have access to both the group and the linked item, including inheritance rules. Responses are private and must not be cached across callers.

Group create/update requests can also include variants[].customFields alongside each selection's itemId. Those changes and the group edit share one transaction. When selections share an item, include its changes once or submit identical changes. A PUT of variants replaces the selection list; retain every selection you still need. Neither a new variant ID nor a new group resets the linked item's existing field versions.

Reads batch and deduplicate item references. Writes resolve the item schema once per group write and fetch existing field versions together per item. Invalid values and stale versions roll back the native edit and custom field changes together.

Customer answers when ordering

Use POST /api/v1/orders/create to submit customer answers. The simplified POST /api/v1/public/orders/create endpoint currently does not forward them.

1. Configure the fields

Create definitions through the Objects and fields API, using /api/v1/locations/{locationId}/object-types/items/fields. Enable channels.guestInput for fields customers should fill in; guestDisplay controls display and does not enable input. Set required and validation in the definition as needed.

Definitions apply to items at the location and inherit down the location tree. To limit an input to a particular item, use a condition on a native item property. For example, this definition asks for an engraving only when the linked item's title is Engraved cup:

{
  "key": "engraving",
  "label": "Engraving",
  "type": "text",
  "required": true,
  "config": {
    "maxLength": 40,
    "condition": { "field": "native.title", "equals": "Engraved cup" }
  },
  "channels": {
    "staff": true,
    "table": false,
    "tableDefault": false,
    "guestDisplay": false,
    "guestInput": true,
    "apiRead": true,
    "apiWrite": false,
    "analytics": true,
    "aiSource": false
  }
}

This title condition uses an exact match; review it if the item is renamed. apiWrite governs editing stored object values through the values API. Customer answers use the guest-input form instead and are saved with the order line, without changing the inventory item's stored values.

2. Discover the selected variant's form

Read the group with GET /api/v1/variant-groups/{id}. Each selected option has a variant id and, for item-based variants, an itemId pointing to its inventory item. A legacy variant with itemId: null has no linked item form.

For example, if variant 34 has itemId: 789, fetch the linked item at the ordering location:

curl "$COMMON_API/api/v1/inventory/789?locationId=1234" \
  -H "X-API-Key: $KARMA_API_KEY"

Read data.guestInput, which contains schemaToken and fields. Use this form rather than the item's customFields display values or a variant-group ID. An illustrative fragment of the item response is:

{
  "data": {
    "id": 789,
    "guestInput": {
      "schemaToken": "<copy the returned 64-character token>",
      "fields": [
        {
          "id": "2c6f83c9-993f-4c39-a75d-b78cc89f0c10",
          "key": "loc1234.engraving",
          "label": "Engraving",
          "description": "",
          "group": "",
          "type": "text",
          "required": true,
          "revision": 1,
          "config": { "maxLength": 40 }
        }
      ]
    }
  }
}

Render each field using its label, type, required flag and config. Native item conditions have already been evaluated by the server. If config.condition is returned, its field is another field's key; show the dependent input only when the other answer matches equals. For a multiselect, matching means membership; for a taxonomy, a descendant also matches its ancestor. Omit answers to hidden fields.

The token is tied to the location, linked inventory item and effective form. Load the parent item's form separately and load a form for each selected linked item; do not reuse the parent's token for a variant.

3. Submit answers on the selected variant

The API key needs inventory.read to load item forms, variant_groups.read to read groups, and inventory.create to place the order, with access to the ordering location.

Use the variant ID in variants[].id. Place the linked item's token and answers in that selection's guest_field_answers. The parent item's answers belong in items[].guest_field_answers instead. These request properties use snake_case; schemaToken and fieldId use camelCase.

For parent item 456 and selected variant 34, submit the following to POST /api/v1/orders/create. Replace the placeholder token and field UUID with values from variant 34's linked item form. This example assumes the parent item has no guest input fields; otherwise include its own submission too.

{
  "locationId": 1234,
  "order_channel": "ONLINE",
  "order_type": "TAKEAWAY",
  "order_destination": "NONE",
  "order_destination_id": null,
  "order_flow": "OPEN_TAB",
  "tab_member": { "identifier": "d7372e2f-abcd-486e-a6b7-e5ad1e401f81" },
  "vouchers": [],
  "items": [
    {
      "id": 456,
      "quantity_cents": 100,
      "comments": [],
      "variants": [
        {
          "id": 34,
          "quantity_cents": 100,
          "guest_field_answers": {
            "schemaToken": "<token from item 789 at location 1234>",
            "answers": [
              {
                "fieldId": "2c6f83c9-993f-4c39-a75d-b78cc89f0c10",
                "value": "Alex"
              }
            ]
          }
        }
      ]
    }
  ]
}

fieldId must be the form field's UUID, not its key or label. Submit only { "schemaToken", "answers" }; each answer contains only { "fieldId", "value" }. Labels, revisions and display values are generated by the server. Answers are stored as snapshots on the corresponding order lines.

Use a fresh tab_member.identifier for a new anonymous guest, or supply the guest identity supported by the Ordering API. order_flow: "OPEN_TAB" leaves payment for later; choose the flow appropriate to your integration.

Value formats and validation

Use the documented wire types: text and dates are strings, numbers are decimal strings, booleans are JSON booleans, currency is { "amount": "80", "currency": "SEK" }, and select/taxonomy/multiselect values use option IDs from the form's config.options. Relations, formulas and rollups are not guest input types.

Each submission replaces the whole form's answers. Include every visible required field; omitted optional visible fields become null. false, "0" and null are distinct values. When a form has fields, submit its token even if all inputs are optional and answers is empty. A form with no fields can be omitted. The limit is 100 answers per submission.

StatusCodeRecovery
409FIELD_FORM_CHANGEDReload the linked item's form at the same location, let the customer review the inputs, and retry with the new token. A token from another item or location also fails.
422FIELD_VALIDATIONCorrect missing required values, invalid types or option IDs, duplicate field IDs, hidden/unknown fields, or a missing form submission. Follow the error message.

Field validation happens within the order transaction. A rejected submission does not create a partial order.

On this page