RFID tokens
Available: tenant fleet API. Driver cards and other identifiers your
chargers authorize locally: the OCPP 1.6 idTag and the OCPP 2.0.1 idToken
with its type. Start in the RFID tokens group of the
API reference. Reads need tokens:read; writes and
imports need tokens:write, which only a tenant-admin can grant.
| Step | Contract |
|---|---|
| List and find | GET /v1/tenants/{tenant_id}/tokens (cursor after, limit; filters status, type, id_token, external_reference) |
| Read | GET …/tokens/{token_id} |
| Register | POST …/tokens (201, synchronous, Idempotency-Key) |
| Block, expire, relabel | PATCH …/tokens/{token_id} (status, expires_at, label, external_reference) |
| Delete | DELETE …/tokens/{token_id} (only a token no transaction used) |
| Bulk import | POST …/tokens/import (JSON or CSV, up to 5,000 rows, dry_run=true first) |
{"id_token": "04A1B2C3D4E5F6", "type": "ISO14443", "status": "Accepted",
"expires_at": "2027-06-30T00:00:00Z", "label": "Van 12", "external_reference": "evc-card-81723"}
type defaults to ISO14443 (RFID cards). OCPP 1.6 chargers send no type, so
it only matters to OCPP 2.0.1 chargers, which must report the same type.
status is Accepted, Blocked or Expired; after expires_at the core
answers Expired. A value and its type are immutable: a replacement card is a
new token. ocpp16_compatible is false when the value is longer than the 20
characters an OCPP 1.6 idTag allows.
One value per tenant
The charging core compares token values case-insensitively, and an OCPP 1.6
charger's idTag arrives without a type. Two rows with the same value would
make the core refuse the card, so a value is unique per tenant regardless of
case and type: registering 04a1b2c3 when 04A1B2C3 exists answers
409 token_exists with the existing token_id.
The same value in another tenant is that tenant's own, independent token: each charger is authorized against its own tenant's tokens, and no tenant can see, change or learn about another tenant's tokens.
Payment flow charging tokens
Bills register a short-lived charging token for each paid start (iv followed
by 18 hex digits). Ivora's earlier payment service registered one for each
remote start too (remotestart- or PAY_ followed by its checkout number). The
payment flow alone manages these: they are never listed or returned here, any
request naming one answers token_not_found, and creating or importing a value
of these forms answers token_reserved, whatever its case.
Roaming partners' tokens
When you publish over OCPI, the cards and app accounts of Ivora's roaming
partners can charge at your chargers (OCPI publishing).
Their partner manages those tokens, so they are not listed or returned here
either. A value one of them holds is taken: creating it answers
409 token_exists without a token_id, and importing it answers
token_reserved. A card you registered first is never replaced by a partner's
token of the same value.
A station start command with a partner's token value answers
422 token_reserved: the session would be credited to that partner. Start
with one of your own tokens instead.
Block rather than delete
DELETE removes a token only when no transaction used it. A token with
charging history answers 409 token_has_history and stays: set its status
to Blocked instead, which also keeps your reports' history intact.
Bulk import
Send JSON {"rows": [...]} or Content-Type: text/csv with a header row
naming id_token and any of type, status, expires_at, label and
external_reference (at most 1 MiB):
id_token,type,status,expires_at,label,external_reference
04A1B2C3D4E5F6,ISO14443,Accepted,,Van 12,evc-card-81723
04B2C3D4E5F6A7,,Blocked,2027-06-30T00:00:00Z,Driver 7,evc-card-81724
Each row matches an existing token by external_reference, else by
id_token (case-insensitive). New values are created (ISO14443, Accepted
unless given); for an existing token only the given fields change and an empty
cell keeps the current value. Clearing a field is a PATCH.
- Run
?dry_run=truefirst. It validates and plans every row without writing and needs noIdempotency-Key. - Apply with an
Idempotency-Key. The response counts rows per action (create,update,unchanged,error) and lists every error row and every changed row with itstoken_id.
Invalid rows are skipped and reported; valid rows still apply. Row codes:
invalid, duplicate_in_file, token_reserved, token_type_mismatch (an
existing token has another type), external_reference_conflict and
token_ambiguous (the tenant already holds the value in several types, written
by another tool; resolve them first). A CSV the parser cannot read (a NUL byte,
a cell over 128 KiB) answers 422 invalid_request before any row is planned.
An import is idempotent by content: running the same file again creates nothing
and reports the rows unchanged. A 503 outcome_unknown can leave some rows
applied; run the same file again with a new key and it converges. Imports and
creates of one tenant run one at a time; a concurrent one answers
409 request_in_progress. If a PATCH assigns a row's external_reference to
another token while the import runs, the row is still applied without it and
reported with external_reference_conflict.
Moving from another CSMS (for example EV Connect): export the driver cards
with their stable card or driver identifier, map it to external_reference,
dry-run, fix the error rows, then apply. Keep driver names and contact details
out of label and external_reference if your policy treats them as personal
data; your application can keep that mapping.
Events
Every change emits token.created, token.updated (with changed, the field
names) or token.deleted, carrying the token record; import events add
"source": "import". Subscribing to them needs tokens:read; see
webhooks.
Defaults
typeisISO14443when a create or a new import row omits it.DELETEis refused once a transaction used the token; block it instead.- An import never clears a field: an empty cell or absent field keeps the
current value. Clear
expires_at,labelorexternal_referencewith aPATCHthat sendsnull. - Assigning an
external_referenceanother token holds answers409 external_reference_takenand changes nothing, also when twoPATCHrequests race for it.
AI agents
The MCP interface exposes list_tokens and get_token only, and shows each
id_token as **** and its last 4 characters: a copied card value can start
charging. Registering, blocking, deleting and importing cards stay with your
backend.