- Answers, field groups, ID checks, imports and exports
- GET /api/v1/fields/orders/{orderId}
- GET /api/v1/fields/groups
- GET /api/v1/fields/groups/{id}
- GET /api/v1/fields/customers/{customerId}/id-verification
- GET /api/v1/fields/definitions/export
- POST /api/v1/fields/imports
- GET /api/v1/fields/imports
- GET /api/v1/fields/imports/{id}
- POST /api/v1/fields/imports/{id}/apply
- POST /api/v1/fields/imports/{id}/recheck
- POST /api/v1/fields/imports/{id}/cancel
- POST /api/v1/fields/answers-exports
- GET /api/v1/fields/answers-exports
- GET /api/v1/fields/answers-exports/{id}
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 field | Notes |
|---|---|
filename, content | Required: the file’s name and its text (UTF-8, at most 5 MB). |
kind | definitions (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). |
format | auto (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. |
mapping | For 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. |
conflict | A 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_disabled | true turns imported groups off so you can check them first. Left out: off for a file from another store. |
include_settings | Also 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 field | Notes |
|---|---|
from, to | Required: days (Y-m-d, the store’s time zone), at most 366 days apart. |
layout | wide (default: one row per order line, a column per question) or long (one row per answer). |
group_keys | Only these field groups (a comma-separated list or an array). |
include_cart_answers | Default true. |
include_customer_id | Default false. |
confirm | Required: 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).
