Skip to main content
Endstate chips are real, physical NFC chips - NTAG 424 DNA - embedded in or attached to your products. They are genuine, tamper-resistant hardware: each chip holds a secret it never reveals, and every tap produces a fresh, single-use cryptographic value derived from it. That is what makes a tap impossible to forge or replay. When a user taps the product, the chip delivers two values: a 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 a chip_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 returns chip.already_scanned (HTTP 410).
  • Never log it. The e value 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.
The unit must belong to a collection. Pairing is permanent and issues the unit - it receives its serial number within the collection.

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_id and e at 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_id and e, 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.
Contact Endstate to configure your verified domain and where unpaired taps should route.

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 - the redirect_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.
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.
The rules:
  • The replacement chip must already exist in your organization and be unpaired. This call never creates a chip - an unknown new_chip_id returns chip.not_found, and a chip that is already paired (to this unit or any other) returns chip.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 returns chip_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.
Poll 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’s e value. The flow:
  1. A user taps the product. The tap URL delivers chip_id and e to your application.
  2. Your server calls POST /v1/taps with the chip_id and e value in the request body.
  3. 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.
A successful response is proof the physical chip was present and tapped at that moment. It is not replayable.

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 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

Do not retry a chip.already_scanned error with the same e value. The credential is consumed on first use. A replay indicates either a duplicate request or an attempted scan replay - investigate before proceeding.

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.
Filter by 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.