> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endstate.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Test without hardware

> Use test chips to exercise the full verification flow in development — no physical NFC hardware required.

Test chips let you build and verify your integration end-to-end before you have encoded hardware in hand. They behave identically to real chips in every API call — the only difference is how you obtain an `e` value: you generate one on demand via the `/tap` endpoint instead of reading it from a physical tag.

Use this guide to run the complete loop — create a test chip, generate a tap credential, and verify it — entirely with `curl`.

## Full walkthrough

<Steps>
  <Step title="Create a test chip">
    Pair a test chip to an existing unit by setting `is_test: true`. You do not supply a `chip_id` or an initial `e` value — Endstate assigns the `chip_id` for you.

    ```bash theme={null}
    curl -X POST https://api2.endstate.io/v1/chips \
      -H "Authorization: Bearer end_sk_test_..." \
      -H "Content-Type: application/json" \
      -d '{
        "unit_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "is_test": true
      }'
    ```

    ```json theme={null}
    {
      "chip_id": "ABCDEF0123",
      "is_test": true,
      "scan_count": 0,
      "created_at": "2026-06-15T10:00:00.000Z",
      "unit": {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "external_id": null,
        "name": null,
        "attributes": null,
        "redirect_url": null,
        "created_at": "2026-06-15T09:59:00.000Z",
        "collection": {
          "id": "8e1a7f50-90ab-4cde-b012-3456789abcde",
          "name": "Canvas Tote Collection",
          "external_id": null,
          "contract": {
            "address": "0x1111111111111111111111111111111111111111",
            "chain_id": 84532,
            "status": "active"
          },
          "token": { "status": "pending", "serial": null }
        }
      }
    }
    ```

    Note the `chip_id` returned in the response — you use it in every subsequent call. Pairing the chip also triggers issuance: the unit's `token.status` transitions from `"pending"` to `"active"` and a serial number is assigned.
  </Step>

  <Step title="Generate a tap value">
    Call the `/tap` endpoint to get a fresh, single-use `e` value. This endpoint is only available on test chips — calling it on a real encoded chip returns `chip.not_a_test_chip`.

    ```bash theme={null}
    curl -X POST https://api2.endstate.io/v1/chips/ABCDEF0123/tap \
      -H "Authorization: Bearer end_sk_test_..."
    ```

    ```json theme={null}
    {
      "chip_id": "ABCDEF0123",
      "e": "C78566198547116F3A715DC1C62AF96F",
      "count": 1
    }
    ```

    `count` is the emulated chip counter used to generate this `e` value — it increments with each simulated tap, mirroring how a physical chip's scan counter behaves.

    Each call produces a new `e` value. Use it immediately in the next step — it is single-use and cannot be reused.

    <Note>
      `/tap` takes no required fields, and an empty body is accepted — with or
      without a `Content-Type: application/json` header, the API treats it as `{}`.
      Only a **non-empty** body that isn't valid JSON is rejected
      (`validation.failed`).
    </Note>

    Optionally pass `{ "count": <integer> }` in the body to pin the emulated counter for the generated `e` value — it must be greater than the chip's current counter. When omitted, the next counter value is used automatically; most integrations never need to set it.
  </Step>

  <Step title="Verify the tap">
    Submit the `e` value exactly as you would for a real tap. The verify endpoint makes no distinction between test chips and encoded chips at this point — the flow is identical.

    ```bash theme={null}
    curl -X POST https://api2.endstate.io/v1/chips/ABCDEF0123 \
      -H "Authorization: Bearer end_sk_test_..." \
      -H "Content-Type: application/json" \
      -d '{
        "e": "C78566198547116F3A715DC1C62AF96F"
      }'
    ```

    ```json theme={null}
    {
      "id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
      "session_token": {
        "token": "end_sess_AbCd1234EfGh5678IjKl9012MnOp",
        "expires_at": "2026-06-15T10:10:00.000Z",
        "scope": {
          "chip_id": "ABCDEF0123",
          "unit_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "organization_id": "c0ffee00-1234-5678-90ab-cdef01234567"
        }
      },
      "chip": {
        "chip_id": "ABCDEF0123",
        "scan_count": 1
      },
      "unit": {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "external_id": null,
        "name": "Canvas Tote #42",
        "attributes": null,
        "collection": {
          "id": "8e1a7f50-90ab-4cde-b012-3456789abcde",
          "name": "Canvas Tote Collection",
          "external_id": null,
          "contract": {
            "address": "0x1111111111111111111111111111111111111111",
            "chain_id": 84532,
            "status": "active"
          },
          "token": {
            "status": "active",
            "serial": 1
          }
        }
      },
      "redirect_url": null,
      "dry_run": false
    }
    ```

    A successful response confirms verification worked. The `session_token` is short-lived proof that the chip was tapped; pass it to a client to authorize unit-scoped actions without exposing your API key.
  </Step>

  <Step title="Repeat as needed">
    Call `POST /v1/chips/{chip_id}/tap` again to get a new `e` value for your next verification. Each `e` is single-use — generate a fresh one for each call to the verify endpoint.

    ```bash theme={null}
    curl -X POST https://api2.endstate.io/v1/chips/ABCDEF0123/tap \
      -H "Authorization: Bearer end_sk_test_..."
    ```

    You can run this loop as many times as you need during development. `scan_count` increments with each successful verification.
  </Step>
</Steps>

## How test chips compare to encoded chips

|                           | Encoded chip                    | Test chip                             |
| ------------------------- | ------------------------------- | ------------------------------------- |
| **`is_test`**             | `false`                         | `true`                                |
| **Created with**          | `chip_id` + `e` (from hardware) | `is_test: true` (no `chip_id` needed) |
| **`chip_id` assigned by** | You (from the physical tag)     | Endstate                              |
| **`e` value source**      | Physical tap of the NFC tag     | `POST /v1/chips/{chip_id}/tap`        |
| **`/tap` endpoint**       | Returns `chip.not_a_test_chip`  | Returns a fresh `e` value             |
| **Verify endpoint**       | Identical behavior              | Identical behavior                    |
| **`scan_count`**          | Increments on each verification | Increments on each verification       |

Test chips are otherwise indistinguishable from encoded chips through the API. Your verification handler does not need to branch on `is_test`.

To list only your test chips, pass `is_test=true` to the chips list endpoint:

```bash theme={null}
curl "https://api2.endstate.io/v1/chips?is_test=true" \
  -H "Authorization: Bearer end_sk_test_..."
```

## Validate without recording a scan

<Tip>
  Pass `"dry_run": true` in a verify request to validate an `e` value without
  recording a scan or issuing a session token. This is useful for confirming
  your integration handles the request/response shape correctly before
  committing real scan records. The `e` value is consumed either way — generate
  a new one from `/tap` for your next call.
</Tip>

```bash theme={null}
curl -X POST https://api2.endstate.io/v1/chips/ABCDEF0123 \
  -H "Authorization: Bearer end_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "e": "C78566198547116F3A715DC1C62AF96F",
    "dry_run": true
  }'
```

When `dry_run` is `true`, the response has `dry_run: true`, `session_token` and `id` are `null`, and `scan_count` does not increment.

## Development and production

<Warning>
  Test chips are for development only. Production verification requires encoded
  NFC hardware chips — physical tags that generate `e` values on each tap.
  Before going live, replace test chips with encoded chips and confirm your
  integration handles real tap URLs correctly. See [Environments](/environments)
  for the API base URL and testing model.
</Warning>

## Next steps

<CardGroup cols={2}>
  <Card title="Verify a unit" icon="scan-line" href="/guides/verify-a-unit">
    The full production verification guide, including how real tap URLs deliver
    the chip\_id and e value.
  </Card>

  <Card title="Chips & verification" icon="nfc" href="/concepts/chips">
    How chips work, what the e value is, and the full verification model.
  </Card>
</CardGroup>
