API & webhooks
REST API quickstart
Mint a key, list scans, push scans, pull a CSV — with the merge semantics and rate limits you need before writing an integration.
Before you start
The API is a Team feature, and only an owner or admin can mint keys. Keys are created
in the web dashboard under API Keys, look like fsk_ followed by 48 hex characters, and
are shown once — the server stores only a hash, so copy it when you see it. The dashboard
also shows your API base URL; the paths below are relative to it.
Authentication
Every request carries the key in the X-API-Key header:
curl -H "X-API-Key: fsk_your_key_here" \
"$FIELDSCAN_API/scans?limit=5"
List scans
curl -H "X-API-Key: fsk_your_key_here" \
"$FIELDSCAN_API/scans?since=2026-07-01T00:00:00Z&limit=100&offset=0"
Query parameters: group_id filters to one group, since is an ISO timestamp compared
against the capture time, limit defaults to 100 and clamps to 500, offset pages
through the rest. Results are newest first. The response wraps the page:
{ "scans": [], "count": 100, "offset": 0, "limit": 100 }
count is the size of this page, not the total — page until you get fewer than limit.
Push scans, and how merging works
POST /scans accepts a single object or an array of up to 500. Rows are matched on the
barcode within your account, and merged, not replaced:
- a key you omit keeps its stored value
- a key you send as null or empty string clears it
{"barcode": "4006381333931"}alone is a safe “I saw this again” ping — it refreshes the capture time and touches nothing else
Accepted fields per item: barcode (required), name, category, notes, type,
group_id, custom_fields. Anything else in the payload is dropped, not stored.
curl -X POST -H "X-API-Key: fsk_your_key_here" \
-H "Content-Type: application/json" \
-d '[{"barcode": "4006381333931", "name": "Widget, blue", "category": "Widgets"}]' \
"$FIELDSCAN_API/scans"
A successful write returns 201 with the breakdown:
{ "inserted": 1, "created": 1, "updated": 0, "duplicates_collapsed": 0, "distinct_barcodes": 1 }
List groups
curl -H "X-API-Key: fsk_your_key_here" "$FIELDSCAN_API/groups"
Returns id, name and created_at per group, sorted by name — use the id for
group_id filters and writes. A group_id that does not belong to your account is
rejected with a 400 naming the offending barcode.
Pull a CSV
curl -H "X-API-Key: fsk_your_key_here" \
-o export.csv "$FIELDSCAN_API/export/csv?group_id=optional-uuid"
This is the machine-facing export, and it differs from the in-app one on purpose: a fixed
twelve-column set in a fixed order (barcode, name, category, status, notes,
type, scan_count, scanned_at, location_name, location_lat, location_lng,
group_id), LF line endings, no TXT variant, no field picker, and no pagination — it
dumps everything the key can see. Cells that could read as spreadsheet formulas are
apostrophe-prefixed, which includes negative coordinates.
Rate limits
120 requests a minute per key, except the CSV export, which gets its own bucket of 10 a
minute because each call is a full dump. Exceeding either returns 429 with Retry-After,
X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers — honor
Retry-After and you will never think about this again.
Getting notified instead of polling
If you are polling /scans on a timer, consider flipping the direction:
signed webhooks push scan.created and
scan.updated to you as they happen.