chip_id that identifies the chip and a one-time credential called an e value. You send those to the API, and Endstate confirms the item is authentic.
What a chip is
A chip is a physical NTAG 424 DNA NFC tag bonded to a single product. Every chip has achip_id - a 10-character hex string (e.g., ABCDEF0123) that appears in the tap URL your application receives. The chip_id is stable and public; it identifies the chip but does not prove a tap happened.
Each chip is paired to exactly one unit. Pairing is permanent on the chip’s side - a chip cannot be re-paired to a different unit. The unit’s side can still change until the item is claimed: a unit’s chip can be swapped for a new one via chip replacement, which retires the old chip rather than moving it.
Chips track scan_count: the number of times a tap has been successfully verified. This count increments each time a valid verification request completes (unless dry_run is used).
Two kinds of chips
Encoded chips are genuine NTAG 424 DNA NFC hardware. You supply the
chip_id and a valid initial e value read from the chip when pairing it to a unit.
Test chips are simulated - no physical hardware required. Create one with is_test: true and then call POST /v1/test-helpers/taps with that chip_id in the body to generate a fresh e value you can use in a verify request. Test chips let you run the full verification flow in development without having NFC hardware in hand. See Testing without hardware for the complete walkthrough.
Test chips are simulated - no physical hardware required. Create one with is_test: true and then call POST /v1/chips/{chip_id}/tap to generate a fresh e value you can use in a verify request. Test chips let you run the full verification flow in development without having NFC hardware in hand. See Testing without hardware for the complete walkthrough.
The e value
Every tap produces a fresh e value - a 32-character hex string included in the tap URL alongside the chip_id. Treat the e value as a credential:
- Single-use. Once a verify request succeeds with a given
e, that value is consumed. Submitting it again returnschip.already_scanned(HTTP 410). - Never log it. The
evalue proves a tap occurred; logging it creates a replay risk. - Short window. Pass it to the verify endpoint promptly after receiving it.
The c value
Some tap URLs also include a c value - a 16-character hex verification code
produced by the chip alongside e. When it is present, include it in your
pairing and verify requests (both accept an optional c). It is not required
today, but sending it whenever you have it prepares your integration for
upcoming chip-verification hardening. The
Reader SDK surfaces it automatically whenever the
tag provides one.
Pairing a chip
Before a chip can be verified, it must be paired to a unit - this is what links the physical hardware to its digital record.POST /v1/chips is the pairing primitive: it binds a chip’s chip_id and initial e value to a unit_id.
Build your own pairing experience
Integrate the pairing API into your existing application and call it however fits your operation:- Pre-pair during fulfillment. Read the chip’s
chip_idandeat encoding time and pair it before the product ships. This is the canonical path for a production catalog. - Pair from your own app. Read chips directly with an NFC chip reader to capture each chip’s
chip_idande, then pair them through your own interface - your staff or users never leave your application. You can also have Endstate redirect taps on an unpaired chip to your app, so a tap drops the holder straight into your pairing flow.
Pairing chips in bulk
POST /v1/chips/bulk pairs up to 100 chips in one request - the natural fit for encoding lines and batch fulfillment. Each entry uses the same payload as the single pairing call; all units must belong to the same collection, and units are issued in the order provided. The request is all-or-nothing: if any entry fails validation, nothing is paired. Send an Idempotency-Key header to retry safely, and expect chip.bulk_pending if a previous bulk pairing for the collection is still processing.
Use Endstate’s hosted pairing screen
If you would rather not build your own, Endstate hosts a pairing screen. Physical chips ship pre-encoded to tap to a verify URL -https://<your-domain>/verify/<chip_id>?e=<e>. When a chip that has not been paired yet is tapped, the holder is routed to that screen, which binds the chip to a unit. The pairing it creates is part of the same system you read through this API - the chip and unit then appear in GET /v1/chips and GET /v1/units.
A chip is permanently bound to one unit. However it is paired, a chip cannot
be re-paired to a different unit. If a unit’s chip is damaged before the item
is claimed, replace the unit’s chip - the old chip is
retired, never reused.
After a chip is paired
Tapping a paired chip redirects the user to your verified domain - theredirect_url returned by the verify call. That is where you run your verified experience.
Look up a chip
GET /v1/chips/{chip_id} returns one chip and a snapshot of the unit it is paired to, including that unit’s issuance status. Use it when you have a chip_id in hand - from a tap, a support ticket, or your own records - and want the current state without paging GET /v1/chips.
Response
Response
unit is null when the chip has not been paired yet. This is a read: it records no tap and issues no session token, so it is not a substitute for verifying a tap.
Replacing a chip
Until a unit is claimed, you can replace its chip - pair a new chip and retire the current one. Use this when a chip is damaged or fails QA after pairing but before the item reaches its owner.Response
Response
- The replacement chip must already exist in your organization and be unpaired. This call never creates a chip - an unknown
new_chip_idreturnschip.not_found, and a chip that is already paired (to this unit or any other) returnschip.already_paired. - The unit must be issued and not yet claimed. An unissued unit returns
unit.not_minted; once the unit is claimed its chip is permanently locked and replacement returnschip_replacement.locked. - One replacement can be open per unit at a time - a second attempt while one is settling returns
chip_replacement.in_progress. - Endstate performs the swap for you. Replacement is asynchronous; there is no client-broadcast mode. Requires an API key.
GET /v1/units/{unit_id}/chip-replacements/{replacement_id} until the replacement settles:
Once
confirmed, the new chip verifies for the unit and appears in the unit’s chips; the retired chip is detached from it.
What verification does
Verification is the act of confirming a physical product is authentic by validating the tap’se value. The flow:
- A user taps the product. The tap URL delivers
chip_idandeto your application. - Your server calls
POST /v1/tapswith thechip_idandevalue in the request body. - Endstate validates the credential against the chip, confirms the item is authentic, increments
scan_count, and returns the unit’s metadata plus a session token.
Verify a tap
Request
POST /v1/taps also accepts a publishable key in place of the API key, so a verify page with no backend behind it can record the tap directly from the browser. See Host your own verify page.
Request fields
string
required
The tapped chip’s 10-character hex identifier, from the tap URL.
string
required
The one-time credential from the tap URL. Must be 32 hex characters.
Single-use - submitting a previously used
e returns chip.already_scanned.string
The tap verification code from the tap URL, when the tag provides one. 16
uppercase hex characters.
boolean
When
true, Endstate validates the e value but does not record the tap or
issue a session token. Use this to confirm a tap is valid before committing
it. Defaults to false. API key only.integer
Lifetime of the issued session token in seconds. Accepted range:
60-3600.
Defaults to 600 (10 minutes). Has no effect when dry_run is true. API
key only - a publishable-key request always gets the default.Response
Response
Response
Response fields
string | null
The tap’s unique identifier (UUIDv4).
null when dry_run is true - no tap
is recorded.object
An object containing
token (the end_sess_... string), expires_at (ISO
8601 expiry timestamp), and scope (chip_id, unit_id, organization_id).
The token string proves this chip was just tapped - pass it to GET /v1/session-tokens/current from a client to
confirm its scope without exposing your API key. null when dry_run is
true.object
The chip that was tapped.
object
The unit this chip is paired to, including its metadata and issuance status.
string | null
The canonical verified-domain URL for this chip
(
https://<your-verified-domain>/verify/<chip_id>), or null if your
organization has no verified domain. You can redirect users here after a
successful tap. Despite the shared name, this is not the tap
redirect you configure on units and collections -
that one controls where Endstate’s hosted tap flow sends the user before
verification.boolean
Echoes the
dry_run request flag. When true, no tap was recorded and no
session token was issued - id and session_token are null.Error codes
Tap history
Every verified tap is recorded.GET /v1/taps returns that history - cursor-paginated, oldest first - for dashboards, reconciliation, and audit. Each item’s id is the same identifier the tap response returned when the tap was recorded, so a stored tap result can be matched to its history entry later.
Response
Response
chip_id, unit_id, or a time window (created_after / created_before, both exclusive). By default only taps of encoded chips on physical items are returned; set is_test=true to see test-chip taps instead. Tap history is telemetry about your products - it carries no information about who tapped.
Chip endpoints
For full request/response schemas, see the API reference.
Next steps
Session tokens
Understand what the session token proves and how to introspect it from a
client.
Verify a unit
Step-by-step guide to the full tap-to-verification flow.
Testing without hardware
Use test chips to run the full verification flow without NFC hardware.
API reference
Complete endpoint reference for chips and all other resources.

