Charger management

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.

StepContract
List and findGET /v1/tenants/{tenant_id}/tokens (cursor after, limit; filters status, type, id_token, external_reference)
ReadGET …/tokens/{token_id}
RegisterPOST …/tokens (201, synchronous, Idempotency-Key)
Block, expire, relabelPATCH …/tokens/{token_id} (status, expires_at, label, external_reference)
DeleteDELETE …/tokens/{token_id} (only a token no transaction used)
Bulk importPOST …/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.

  1. Run ?dry_run=true first. It validates and plans every row without writing and needs no Idempotency-Key.
  2. 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 its token_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

  • type is ISO14443 when a create or a new import row omits it.
  • DELETE is 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, label or external_reference with a PATCH that sends null.
  • Assigning an external_reference another token holds answers 409 external_reference_taken and changes nothing, also when two PATCH requests 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.