Skip to content

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.

← All guides8 minUpdated Jul 28, 2026

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"
GET /export/csvAPI
barcode,name,category,status,group_id
5901234123457,Blue nitrile gloves, L,PPE,Reviewed,grp_8c21…
Fixed twelve columns in a fixed order, group_id included, quantity and added_by absent, LF endings. First five shown.

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.