NFC Agent

Overview

What the NFC agent is, when you need one, and how a tap reaches the Davi API.

The Davi NFC Agent is a small program that runs on the machine with the card reader. It talks to the reader over PC/SC, accepts scans from phones and WebNFC browsers, and publishes everything it sees on a local WebSocket — so your application reads one stream and does not care which of those produced a tap.

It is a separate program with its own releases, not part of the Davi API. It runs on your hardware, listens on localhost, and works on its own.

When you need one

A browser cannot talk to a USB card reader. Anything that reads or writes a physical card from a desktop needs something native in between, and that is what the agent is:

  • A door or a kiosk reading cards to admit people to a session
  • A counter looking somebody up from the card they tap
  • Encoding — writing a Davi URL onto a new card before handing it over

You do not need it to use Davi. Every API in these docs works from a phone, a server or a browser with no reader anywhere. You need it when a physical card is part of the flow.

From a tap to Davi

A scanned tag's uid is the Davi card identifier. That is the whole join between the two products: the agent gives you a uid, and Davi's identifier tagged union takes it as { "type": "card", "value": uid }.

So a door is one WebSocket and one HTTP call:

const AGENT = "ws://localhost:9470/ws";
const DAVI = "https://api.davi.social/api/v1";
const SESSION_SLUG = "week-1";
const TOKEN = process.env.DAVI_ORG_TOKEN; // an org-scoped token

const ws = new WebSocket(AGENT);

ws.addEventListener("message", async (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type !== "tagData" || msg.payload.err) return;

  const res = await fetch(`${DAVI}/sessions/${SESSION_SLUG}/admit`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      identifier: { type: "card", value: msg.payload.uid },
      idempotency_key: `${SESSION_SLUG}:${msg.payload.uid}:${msg.payload.scannedAt}`,
      proof_metadata: { method: "nfc_tap" },
    }),
  });

  const admit = await res.json();
  switch (admit.decision) {
    case "admitted":
      return show("Welcome in");
    case "needs_confirm":
      return show("No ticket — register first");
    case "blocked":
      return show(reasonText[admit.blocked_reason] ?? "Not admitted");
  }
});

The agent needs no Davi credentials and never sees your token: it hands you a uid, and the call to Davi is yours to make. Which means the door's credential only has to be able to admit — see Org Activities for the scopes, and Membership Lifecycle if the card carries a tier.

One client at a time. The first connection claims the session and later ones are refused until it drops, so a second tab is not a second reader.

Where to go next

InstallationInstall it, and what your OS needs
Client APIThe WebSocket your application speaks — scans, writes, capabilities
Device APIFeeding the agent from a phone, a browser or your own hardware
Errors and TLSError codes, and how devices trust the certificate
Custom BuildsEmbedding the agent as a Go library

The agent is open source under the MIT licence — dotside-studios/davi-nfc-agent.