View Categories

Launching soon on the Shopify App StoreAbout Custom FieldsJoin the waitlist

REST API reference: Custom Fields

7 min read

The REST API v1 endpoints of SI Custom Fields (Fields, Waiver & ID): read the answers on an order, the field groups, a customer’s ID check status, and import or export field definitions and answers. The key, limits, pagination and error format are the same as for all our apps: see the API overview. The same operations are in GraphQL: GraphQL API reference: Custom Fields.

Needs the Custom Fields app (Fields, Waiver & ID). It works on a store without Rentals & Bookings. Make the key on its API access page; a key made in any of our apps works too, because keys belong to the store. On a store without the app, every endpoint here answers 403 with the code rentals_rest_app_not_installed and data.app: "fields".

See Getting started with Custom Fields.

The examples use two shell variables, API="https://YOUR-API-ADDRESS/api/v1" and KEY="sirb_your_secret_key". GET needs a read key; everything else needs a key with Allow changes (write).

Answers, field groups, ID checks, imports and exports #

ID verification is a status only: none, pending, verified, rejected or expired (past the document’s printed expiry date), with that expiry date. The API never returns an ID document, an image, a file link or a document id.

GET /api/v1/fields/orders/{orderId} #

An order’s answers per line (rental or regular product) and for the whole cart, the fees they added, the damage waiver line and the ID check’s status. recorded: false when the app has nothing for the order. name and synced_at come with a recorded order. The order is read from Shopify the first time; refresh=true reads it again.

curl -s "$API/fields/orders/5550001" -H "Authorization: Bearer $KEY"
# 200
{
  "order_id": 5550001, "recorded": true, "name": "#1042",
  "lines": [{"kind": "rental", "title": "Party tent 6×12 m", "variant_title": null, "quantity": 1,
             "answers": [{"label": "Event address", "value": "12 Example Road"}, {"label": "Flooring", "value": ["Wooden floor"]}],
             "fees": [{"label": "Flooring", "amount": "120.00"}]}],
  "cart_answers": [{"label": "How did you hear about us?", "value": "A friend"}],
  "waiver": {"state": "accepted", "amount": "35.00", "label": "Damage waiver"},
  "id_verification": {"mode": "checkout", "status": "verified", "expires_at": "2029-05-31"},
  "fees_total": "155.00", "currency": "USD", "synced_at": "2026-11-02T14:05:30+00:00"
}

GET /api/v1/fields/groups #

The field groups and their questions, in the order the storefront shows them: [{id, key, name, description, enabled, placement, scope, collections, products, tags, product_types, checkout_check, position, fields: [{id, key, type, label, help, placeholder, required, options, price, price_type, charge_per, conditions, min, max, maxlength, allowed_types, max_size, audience, default}]}].

curl -s "$API/fields/groups" -H "Authorization: Bearer $KEY"

GET /api/v1/fields/groups/{id} #

One group. 404 not_found otherwise.

GET /api/v1/fields/customers/{customerId}/id-verification #

A customer’s ID status: their verified document that is still valid, else their latest document. mode is the store’s ID check setting: off, optional, checkout or pickup.

curl -s "$API/fields/customers/555001/id-verification" -H "Authorization: Bearer $KEY"
# 200
{"customer_id": 555001, "mode": "checkout", "status": "verified", "expires_at": "2029-05-31"}

GET /api/v1/fields/definitions/export #

The question definitions as a file, answered as text inside JSON: {filename, content_type, byte_size, content}. Parameters: format (json, the default: lossless, to copy to another store or keep as a backup; or csv, to edit in a spreadsheet), group_ids (comma-separated; all when left out), include_settings (JSON only: the damage waiver, ID check, priced option and message settings; never the ID staff email).

curl -s "$API/fields/definitions/export?format=json" -H "Authorization: Bearer $KEY"
# 200
{"filename": "si-fields-definitions-2026-11-30.json", "content_type": "application/json", "byte_size": 272,
 "content": "{\n    \"format\": \"si-fields-definitions\",\n    \"version\": 1,\n    …\n    \"groups\": [ … ]\n}\n"}

POST /api/v1/fields/imports #

Uploads a definitions file. It is checked in the background: read the import until its status is preview (or failed), show the plan, then apply it.

Body fieldNotes
filename, contentRequired: the file’s name and its text (UTF-8, at most 5 MB).
kinddefinitions (default: field groups are created, replaced, skipped or copied) or update (only the labels and prices of the store’s questions and options change, from a JSON or CSV file of this app).
formatauto (default: by the name and the first character), json or csv; also easify (an Easify Product Options CSV) or mapped (any spreadsheet CSV, read through mapping). Groups from easify and mapped files arrive turned off.
mappingFor mapped: {"source_name": "…", "columns": {"question": "Header", "group": "…", "type": "…", "option": "…", "price": "…", "required": "…", "help": "…", "placeholder": "…"}}, the header names of the file’s columns. Only question is required; the mapping is remembered under source_name.
conflictA group whose key the store has already: skip (default: keep the store’s), replace (replace it and all its questions) or copy (import a copy with new keys).
import_disabledtrue turns imported groups off so you can check them first. Left out: off for a file from another store.
include_settingsAlso import the settings of a JSON file.
curl -s -X POST "$API/fields/imports" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d "{\"filename\": \"groups.json\", \"content\": $(jq -Rs . < groups.json), \"conflict\": \"skip\"}"
# 202
{"id": "01k9zt3m8q4f2c7x1v5n6b0d9e", "status": "queued", "format": "json", "filename": "groups.json", "…": "…"}

An import answers {id, status, format, kind, origin, source_name, filename, byte_size, conflict, import_disabled, include_settings, source_shop, source_platform, source_version, plan_hash, counts: {groups, fields, create, replace, skip, copy, errors, warnings}, plan: [{key, name, action, new_key, fields, options, enabled, unmatched, messages}], settings, messages: [{severity, code, message, path, row, column, group_key, field_key}], result, created_at, created_by, applied_at, expires_at}. origin is file, library (a template of the library) or update; source_platform is shopify, library, easify or spreadsheet. An update import lists the labels and prices it changes. A message points at the problem with a JSON pointer (path) or a CSV row and column. status is queued, checking, preview, stale, failed, applying, applied, cancelled or expired (a preview expires after 24 hours).

GET /api/v1/fields/imports #

The latest imports, newest first. first: how many (20 by default, at most 50).

GET /api/v1/fields/imports/{id} #

One import.

POST /api/v1/fields/imports/{id}/apply #

Applies the plan the preview showed. Body: plan_hash (the preview’s). All or nothing, in the background (202): read the import until applied, failed or stale (the field groups changed after the check: recheck it). Refused while the preview has errors.

POST /api/v1/fields/imports/{id}/recheck #

Checks the import again (202).

POST /api/v1/fields/imports/{id}/cancel #

Drops an import that wasn’t applied.

POST /api/v1/fields/answers-exports #

Makes a CSV of the answers customers gave on orders in a date range. It holds what customers typed, so confirm must be true. Made in the background (202): read the export until status is ready.

Body fieldNotes
from, toRequired: days (Y-m-d, the store’s time zone), at most 366 days apart.
layoutwide (default: one row per order line, a column per question) or long (one row per answer).
group_keysOnly these field groups (a comma-separated list or an array).
include_cart_answersDefault true.
include_customer_idDefault false.
confirmRequired: true.
curl -s -X POST "$API/fields/answers-exports" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"from": "2026-11-01", "to": "2026-11-30", "layout": "wide", "confirm": true}'
# 202
{"id": "01m3bf9nyk0vktz9fre4s2tzcs", "status": "queued", "from": "2026-11-01", "to": "2026-11-30", "layout": "wide", "…": "…"}

GET /api/v1/fields/answers-exports #

The latest exports, newest first (first, at most 50).

GET /api/v1/fields/answers-exports/{id} #

One export: {id, status, from, to, layout, group_keys, include_cart_answers, include_customer_id, rows, byte_size, download_url, created_at, created_by, ready_at, expires_at, downloaded_at, error}. While it is ready, every read gives a fresh download_url that works for 15 minutes without a key. The file is deleted 24 hours after it is ready. Answers to file questions show the file name, never a link, and ID documents are never included.

Refusals of import and export (rentals_rest_…): 400 invalid, has_errors, file_too_large, too_many_rows, range_too_long, confirm (with data.params); 404 not_found; 409 busy (another import is open), plan_changed, stale, expired, already_applied, not_cancellable, not_ready; 429 too_many_exports (20 a day).