TAGBASE

Verifications

Submit a scan and read back the authenticity verdict.

View as Markdown

A verification is the platform’s verdict for a scan. When someone taps a tag, your solution forwards the scan to the platform and receives a verification that says whether the tag is genuine. This is the core call your integration makes.

How scanning works

Your solution owns the scan entry point. A tag’s chip is programmed with a URL that points at your app (https://<your-entry-point>/<tag_id>?<scan params>; create the tag with {id} in its url to get the tag id into the path), so a tap arrives as a request to you, with the tag id in the path and the scan parameters in the query string. You then hand the scan to the platform, which validates it and records the verification.

Tap Your App POST /verifications Verdict

Hand it to your coding agent

Copy this brief into Claude Code, Cursor, or whatever you build with. It covers the scan, the session, and the page the customer reloads. Read it before you write the handler.

Add a TAGBASE scan flow to this app.

A customer taps a tag. The tag opens a URL on your server. You forward that
tap to TAGBASE and show the verdict. Read these pages first:

https://platform.tagbase.io/docs/api/verifications
https://platform.tagbase.io/docs/api/sessions
https://platform.tagbase.io/docs/guides/building-a-solution

1. Own the tap URL. Read the tag id from the path. Copy the query string
   into attributes, unchanged. From your server, POST once to
   /api/v1/tags/:tag_id/verifications. The customer's page never calls
   the platform. Send no session relationship on the first tap.
2. Store the response before you answer the browser. Keep the verification
   id (vrf_…) and the session id (ses_…, under relationships.session.data.id)
   against this visitor.
3. Redirect the browser to a stable page whose address is the verification
   id. The tap URL is not that page. A reload of the stable page calls
   GET /api/v1/verifications/:id and renders attributes.status. It does
   not POST.
4. Keep one flow for this visit. When a request for this tap is in flight,
   or you already hold a verification whose status is pending, valid, or
   invalid, render that state. A second render does not POST the same
   query string again. A second tap is a new URL with new parameters.
   POST that tap once, with the stored session id in relationships.session.
5. Detect private browsing in the customer's browser, on the result page.
   TAGBASE does not return a private-browsing field, and it has no call
   that detects it. When the window is private and the status is pending,
   tell the customer to leave private browsing and tap again. Pending in
   that window is not a step that is still working.
6. Recover a stale session. A session stays open for the tag's
   session_duration (600 seconds unless the tag sets another value). When
   you sent a session id and the response carries a different one, TAGBASE
   opened a new pending session. Store the new id. Show pending for it.
7. Render every status. pending asks for a second tap, except in a private
   window (step 5). valid shows the item as genuine. invalid warns the
   customer that the item may be a copy.

When the page is built, run the checklist at
https://platform.tagbase.io/docs/guides/best-practices.

Fields

Field Type Notes
id string vrf_-prefixed, assigned by the platform.
status string The verdict (see below).
tamper_status string The seal reading (see below). null on a tag without a seal.
inserted_at string ISO 8601 timestamp (UTC, microsecond precision).

A verification also references its session and its tag as relationships.

Status values

Status Meaning
pending Scan accepted; the session hasn’t resolved to a final verdict yet.
valid Genuine: the tag verified successfully.
invalid Failed: the scan didn’t check out.

A verification you create comes back as exactly one of these three.

Seal readings

Some tags carry a tamper seal: a physical loop that the tag checks each time it is tapped to see whether the product has been opened. Every scan of such a tag returns the reading in tamper_status, alongside the verdict:

Reading Meaning
sealed The seal is intact; the product has not been opened.
resealed The seal was opened at some point and is now closed again.
open The seal is broken; the product has been opened.
invalid The tag could not read its seal.

tamper_status is null for a tag without a seal. Order a tag with one and the field starts carrying a reading; nothing else about your integration changes.

The seal is independent of status. A tag can verify as valid and still report open, which describes a genuine product in opened packaging. Treat the two as separate signals, and decide for yourself whether an opened seal changes what you show the customer.

Submit a scan

POST /api/v1/tags/:tag_id/verifications

:tag_id is the tag id of the scanned tag. The key you present must own that tag, or the platform responds 404.

This posts one scan at a time. If you already hold both scans of a tag, you can submit them together and resolve in a single call.

Request

The attributes are the tap URL’s query string, parsed into key/value pairs: every parameter, unchanged. When a tag is tapped, its chip produces a URL whose query string carries the data for that tap; your entry point receives it, and you copy the whole parsed query string into attributes. You never name or interpret those parameters: pass them through verbatim.

The parameter names depend on the chip type and can change between chip generations, so don’t filter by name. Forward every query parameter. Keep your own routing data (such as the tag id) in the path, not the query string, so the query string holds only what the chip wrote.

To continue a session an earlier verification returned, set the session relationship to its id; omit relationships entirely to start a fresh one.

Field Required Notes
attributes yes The scan parameters, copied from the tap URL’s query string.
relationships.session no The session to continue. Omit to start a new one.

Here’s a scan that starts a fresh session, with just the forwarded tap parameters and no session relationship (to continue a session you’d add one alongside them):

{
  "data": {
    "type": "verifications",
    "attributes": {
      "...": "...scan parameters copied from the tap URL..."
    }
  }
}

To continue a session, add the session relationship:

{
  "data": {
    "type": "verifications",
    "attributes": {
      "...": "...scan parameters copied from the tap URL..."
    },
    "relationships": {
      "session": { "data": { "type": "sessions", "id": "ses_2cZuH8Cb7L5fseae6FzNDc" } }
    }
  }
}
curl https://platform.tagbase.io/api/v1/tags/tag_XPg3Zq7gEExNezDWWW7Zzo/verifications \
  -X POST \
  -H "Authorization: Bearer $TAGBASE_API_KEY" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -d '{ "data": { "type": "verifications", "attributes": { "...": "...tap URL params..." } } }'

Response: 201 Created

{
  "data": {
    "type": "verifications",
    "id": "vrf_AoNAiMHGpUb2fxAB93eM4R",
    "attributes": {
      "status": "pending",
      "tamper_status": "sealed",
      "inserted_at": "2026-06-08T12:34:56.123456Z"
    },
    "relationships": {
      "session": { "data": { "type": "sessions", "id": "ses_2cZuH8Cb7L5fseae6FzNDc" } },
      "tag":     { "data": { "type": "tags",     "id": "tag_XPg3Zq7gEExNezDWWW7Zzo" } }
    }
  }
}

Persist id, status, and the session id on your side as soon as you receive them; you can also re-fetch a verification by id later. Store tamper_status too if your tags carry a seal. The tag measures it at tap time, so re-fetching a verification later returns the reading from that tap rather than the state of the seal today.

Errors

Status When
400 The body has no data.attributes object.
401 Missing, invalid, or revoked key.
404 No such tag under your team, or the tag isn’t written to a chip yet.
422 The scan couldn’t be recorded; nothing was saved, so retrying the same request is safe. Not a failed security check.

Note that a tag that fails its security check still returns 201 with status: "invalid". That’s a successful verification with a negative verdict. 422 means the request itself couldn’t be processed, not that the tag is fake.

Sessions and resolution

  • A scan posted without a session relationship opens a new session: the verification comes back pending with a session id. Store that id against the scanning visitor (their session or a cookie). Scans are linked only because you re-present this id, not by anything the platform tracks about the device.
  • A scan posted with a session relationship continues that session, and the verdict resolves to valid or invalid.

A session stays open for the tag’s session_duration: 10 minutes unless you configured the tag otherwise. After that it expires; a later scan referencing it starts a fresh pending session instead of resolving the old one. The response then carries a new session id. Store that id.

Keep the page safe to reload

Scan parameters work once. POST them once from your server, then redirect the browser to a page whose address is the verification id. A reload of that page calls GET /api/v1/verifications/:id and shows attributes.status.

Store the session id against the visitor before you answer. A reload reads that id. It does not open a session, and it does not POST the same tap again. When a request for this tap is already in flight, or you already hold a pending, valid, or invalid verification for it, render that state.

The two taps have to share the session you stored. Private browsing keeps them apart. TAGBASE does not return a private-browsing field. Detect private browsing in the customer’s browser. When the window is private, tell the customer to leave private browsing and tap again. A pending status there is not a step that is still working.

The checks to run when the page is built are on Best practices.

Submit both scans at once

The flow above spans two requests glued by a session. If you already hold both scans of a tag (for example a client that buffered taps while offline and is now syncing), you can submit them together and get the final verdict in a single call, with no pending step and no second round-trip.

Send data as an array of exactly two scans instead of a single object. Each entry is a verifications resource whose attributes are one scan’s tap parameters, copied verbatim, the same pass-through as a single scan. Order matters: the first entry is the first tap, the second is the second tap. You don’t set a session relationship; the call resolves on its own.

Request

{
  "data": [
    { "type": "verifications", "attributes": { "...": "...first tap params..." } },
    { "type": "verifications", "attributes": { "...": "...second tap params..." } }
  ]
}
curl https://platform.tagbase.io/api/v1/tags/tag_XPg3Zq7gEExNezDWWW7Zzo/verifications \
  -X POST \
  -H "Authorization: Bearer $TAGBASE_API_KEY" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -d '{ "data": [
        { "type": "verifications", "attributes": { "...": "...first tap params..." } },
        { "type": "verifications", "attributes": { "...": "...second tap params..." } }
      ] }'

Response: 201 Created

You get a data array of the verification records the call created, in scan order. The last entry carries the final verdict (valid or invalid). Read your authenticity result from it.

{
  "data": [
    {
      "type": "verifications",
      "id": "vrf_AoNAiMHGpUb2fxAB93eM4R",
      "attributes": { "status": "pending", "inserted_at": "2026-06-08T12:34:56.123456Z" },
      "relationships": {
        "session": { "data": { "type": "sessions", "id": "ses_2cZuH8Cb7L5fseae6FzNDc" } },
        "tag":     { "data": { "type": "tags",     "id": "tag_XPg3Zq7gEExNezDWWW7Zzo" } }
      }
    },
    {
      "type": "verifications",
      "id": "vrf_XBUGqbnP8BzQ7yJAmyrtaw",
      "attributes": { "status": "valid", "inserted_at": "2026-06-08T12:34:56.789012Z" },
      "relationships": {
        "session": { "data": { "type": "sessions", "id": "ses_2cZuH8Cb7L5fseae6FzNDc" } },
        "tag":     { "data": { "type": "tags",     "id": "tag_XPg3Zq7gEExNezDWWW7Zzo" } }
      }
    }
  ]
}

As with the single-scan call, persist each id and status on receipt; each is also retrievable by id.

Errors

Same as a single scan, plus one shape check:

Status When
400 data is an array but doesn’t hold exactly two scans, or an entry is missing its attributes.

Retrieve a verification

GET /api/v1/verifications/:id

Fetch a verification by id: its status and its session and tag relationships. The key you present must own the underlying tag, or the platform responds 404.

curl https://platform.tagbase.io/api/v1/verifications/vrf_AoNAiMHGpUb2fxAB93eM4R \
  -H "Authorization: Bearer $TAGBASE_API_KEY" \
  -H "Accept: application/vnd.api+json"

Response: 200 OK

{
  "data": {
    "type": "verifications",
    "id": "vrf_AoNAiMHGpUb2fxAB93eM4R",
    "attributes": {
      "status": "valid",
      "inserted_at": "2026-06-08T12:34:56.123456Z"
    },
    "relationships": {
      "session": { "data": { "type": "sessions", "id": "ses_2cZuH8Cb7L5fseae6FzNDc" } },
      "tag":     { "data": { "type": "tags",     "id": "tag_XPg3Zq7gEExNezDWWW7Zzo" } }
    }
  }
}

Errors

Status When
401 Missing, invalid, or revoked key.
404 No such verification under your team.