TLS & Certificates
The agent serves wss:// with a self-signed certificate generated from a key it
creates once and keeps. Nothing is installed into any trust store by default.
Native devices: pin the key
Phones, readers and other native clients should not install a certificate
authority. They verify the agent by pinning its public key, reported as
serverInfo.publicKeyPin at registration and handed out at pairing. The pin
survives certificate reissues, which happen whenever the host's addresses
change.
See Setting up an iOS or Android device for the pairing flow and the trust-evaluation code, including the two ways it commonly goes wrong.
Browsers: provide a certificate, or install a CA
A browser cannot pin, so it needs a certificate it already trusts:
- Provide one: point
-cert/-keyat a certificate for a name you control that resolves to the agent. Nothing is installed, and the browser trusts it because a public CA issued it. -install-ca: creates a local certificate authority and installs it in the system trust store. A CA there can sign for any name, not just this agent, so prefer option 1 where you can arrange it.
With -install-ca, the bootstrap server on port 9472 serves the root
certificate for installation, PIN-gated.
Browsers also need their origin allowed. See Browser origins. A trusted certificate and an allowed origin are separate requirements, and a failure of either looks the same from the page.
Error Codes
Errors arrive as a response with success: false, a human-readable error
string, and a structured payload:
{
"id": "req_1",
"type": "error",
"success": false,
"error": "data too large: 900 bytes exceeds tag NDEF capacity of 504 bytes",
"payload": {
"code": "CAPACITY_EXCEEDED",
"retryable": false,
"op": "WriteData",
"tagUID": "04:A1:B2:C3"
}
}
code has always been present and its strings are stable. retryable, op,
and tagUID are additive: a client reading only code is unaffected.
retryable answers whether repeating the identical request could plausibly
succeed. Combined with code it gives three distinct outcomes:
| Condition | Meaning | What a client should do |
|---|---|---|
retryable: true, code ≠ TAG_REMOVED | Transient: I/O glitch, full queue, timeout | Retry, with backoff |
retryable: true, code = TAG_REMOVED | The tag left the field mid-operation | Ask the user to present the tag again |
retryable: false | Refused on its merits | Do not retry; surface it |
Protocol errors
Raised by the bridge itself, before reaching a tag.
| Code | Retryable | Description |
|---|---|---|
PARSE_ERROR | no | Message was not valid JSON |
INVALID_PAYLOAD | no | Payload did not match the message type |
INVALID_REQUEST | no | Required field missing or invalid |
INVALID_MESSAGE_TYPE | no | Message type not valid at this point in the exchange |
UNKNOWN_TYPE | no | Unrecognized message type |
INVALID_DEVICE | no | Device ID did not match the connection |
TAG_MISMATCH | no | The tag present is not the one the request named |
TAG_NOT_NAMED | no | Request named no tag and did not ask for one to be guessed |
REGISTRATION_FAILED | no | Device could not be registered |
SESSION_LOCKED | no | Another client holds the session |
TAG_SEND_FAILED | yes | Tag data could not be delivered internally |
READ_ERROR | yes | Failed to read from the connection |
TIMEOUT | yes | Operation timed out |
DEVICE_GONE | no | Target device disconnected |
INTERNAL_ERROR | yes | Unexpected agent-side failure |
BUSY | yes | Earlier work has not finished: a reader completing an operation its caller abandoned, or more requests outstanding than the connection queues |
UNKNOWN_ERROR | no | Unclassified: never advertised as retryable |
NFC errors
Something happened at the tag. These mirror the agent's internal error codes.
| Code | Retryable | Description |
|---|---|---|
NOT_SUPPORTED | no | Tag or device does not support the operation |
TAG_REMOVED | yes | Tag left the field mid-operation |
AUTH_FAILED | no | Authentication failed: the same key will fail again |
READ_FAILED | yes | Read failed |
WRITE_FAILED | yes | Write failed |
TRANSCEIVE_FAILED | yes | Raw exchange failed |
TAG_NOT_CONNECTED | yes | No tag connected |
READ_ONLY | no | Tag is locked, or the agent is in read-only mode |
RAW_CHANNEL_DISABLED | no | The raw APDU channel is off; enable it to send raw exchanges |
CAPACITY_EXCEEDED | no | Data larger than the tag's usable NDEF capacity |
INVALID_DATA | no | Data was malformed |
MULTIPLE_TAGS | no | More than one tag in the field; separate them and try again |
NO_CARD | yes | Nothing is holding the tag the request named |