Docs
7

Rail credentials

Store and replace a client's rail credentials.

  • In the sandboxstore
  • Not in the sandbox yetrails

In plain words

A rail credential is the key or authorisation that lets us act at a country's network or authority for one client, such as a token for Poland's national e-invoicing system (KSeF). The client issues or authorises it, and it goes into eurinvoice through the API, never by email or chat. We keep it encrypted and never show it again. The hosted sandbox holds none yet, so a route there sends nothing.

A rail credential lets us act at a route for one client: a KSeF token, an ANAF authorisation, a platform key. It goes in through this API, never by email or chat. The API keeps it and never returns it. The calls need a key with the admin scope, and a client's key stores credentials for its own client only.

How a value is kept

A value is encrypted before it is stored, kept apart from the key that protects it, and never shown again. A credential is opened only when the service would call that client's route. Each use writes an audit row with no value.

A credential whose expires_on is less than 30 days away records an alert. Revoking clears the value and keeps the dates.

Fields

FieldMeaning
clientThe client's identifier, the same one an invoice carries: 1 to 64 letters, digits, dots, underscores or hyphens. A client's own key may leave it out.
routeDE-XRECHNUNG, PEPPOL, FR-PA, PL-KSEF or RO-EFACTURA.
kindNames the credential and decides how it is read, up to 80 characters of letters, digits, dots, underscores and hyphens: ksef-token for KSeF and anaf-oauth for ANAF. For a Peppol or France route, we give you the kind and the shape of the value when the go-live starts.
issued_byWho issued it, up to 200 characters.
issued_onThe issue date, YYYY-MM-DD.
expires_onOptional, YYYY-MM-DD. Set it when the credential expires.
environmentOptional. It must be the environment of the service you call; a sandbox stores sandbox credentials only.
legal_entityOptional, up to 64 characters. The seller id the credential acts for: a VAT id, NIP, SIREN or company id. A credential without one serves the client's other invoices.
valueThe secret, up to 16,384 characters. Required, and never returned.

The service checks that a value can work for its kind, for example that a KSeF token is JSON with a nip and a token. An anaf-oauth value is JSON text with a cui and either an access_token, or a refresh_token with the application's client_id and client_secret. A value that cannot answers 422, credential-unusable, and the answer says what shape it expects without quoting the value.

An answer carries id (cred_ and 24 hex characters), the fields above except value, stored (true while a value is held) and revoked_at once revoked.

A client can hold one live credential for each route, kind, environment and legal entity. The worker uses the oldest one for the invoice's client and route that is not revoked.

Store a credential

POST /credentials takes the fields above as JSON and answers 201. The request is credential-create.json.

curl -X POST "https://api-sandbox-eu.eurinvoice.com/credentials" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  --data-binary @credential-create.json
Response201
{
  "issued_by": "ANAF",
  "environment": "sandbox",
  "route": "RO-EFACTURA",
  "kind": "anaf-oauth",
  "expires_on": "2027-09-01",
  "stored": true,
  "client": "acme-srl",
  "issued_on": "2026-09-01",
  "id": "cred_5bebc3172daf651a788e4284"
}
Recorded on 7 Oct 2026. The value is an invented string.

Storing the same credential again while the first is live answers 409.

curl -X POST "https://api-sandbox-eu.eurinvoice.com/credentials" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  --data-binary @credential-create.json
Response409 Conflict
{
  "type": "https://eurinvoice.com/problems/credential-exists",
  "title": "This client already has this credential",
  "status": 409
}
Recorded on 7 Oct 2026.

List and read

GET /credentials returns every credential, live and revoked, as {"data": [...]}. GET /credentials/{id} returns one, and 404 for an id it does not have.

curl "https://api-sandbox-eu.eurinvoice.com/credentials" \
  -H "Authorization: Bearer <your-api-key>"
Response200 OK
{
  "data": [
    {
      "issued_by": "ANAF",
      "environment": "sandbox",
      "route": "RO-EFACTURA",
      "kind": "anaf-oauth",
      "expires_on": "2027-09-01",
      "stored": true,
      "client": "acme-srl",
      "issued_on": "2026-09-01",
      "id": "cred_5bebc3172daf651a788e4284"
    }
  ]
}
Recorded on 7 Oct 2026. There is no value in the list.

Replace

POST /credentials/{id} takes a new value, and optionally a new expires_on. Client, route, kind and issue date stay. Use it after a token refresh. The request is credential-replace.json.

curl -X POST "https://api-sandbox-eu.eurinvoice.com/credentials/cred_5bebc3172daf651a788e4284" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  --data-binary @credential-replace.json
Response200 OK
{
  "issued_by": "ANAF",
  "environment": "sandbox",
  "route": "RO-EFACTURA",
  "kind": "anaf-oauth",
  "expires_on": "2028-09-01",
  "stored": true,
  "client": "acme-srl",
  "issued_on": "2026-09-01",
  "id": "cred_5bebc3172daf651a788e4284"
}
Recorded on 7 Oct 2026.

Revoke

POST /credentials/{id}/revoke clears the value and stamps revoked_at. Repeating it changes nothing.

curl -X POST "https://api-sandbox-eu.eurinvoice.com/credentials/cred_5bebc3172daf651a788e4284/revoke" \
  -H "Authorization: Bearer <your-api-key>"
Response200 OK
{
  "issued_by": "ANAF",
  "environment": "sandbox",
  "route": "RO-EFACTURA",
  "revoked_at": "2026-10-06T19:49:12.987Z",
  "kind": "anaf-oauth",
  "expires_on": "2028-09-01",
  "stored": false,
  "client": "acme-srl",
  "issued_on": "2026-09-01",
  "id": "cred_5bebc3172daf651a788e4284"
}
Recorded on 7 Oct 2026. `stored` is false.

Warning

A revoked record stays, for the audit trail. It cannot be replaced: that answers 409, credential-revoked. Store the new credential as a new record instead. A revoked credential no longer blocks storing the same client, route and kind again.

Answers

StatusMeaning
200The list, one credential, a replace, or a revoke.
201Stored.
400The body is not JSON, a required field is missing or too long, route is not one of the five, or a date is not YYYY-MM-DD.
401No key, or an unknown key.
403The key lacks the admin scope, or names another client.
404No such credential.
409The client already has this credential (credential-exists), or the credential was revoked (credential-revoked).
413The body is over 5 MB (payload-too-large).
422The value cannot work for this kind (credential-unusable).
429Too many requests for the key. Wait for Retry-After seconds.
503The service has no master key to seal a value with (unavailable).

What each route needs

In the sandboxfrom official sources, checked 2 Oct 2026
RouteThe client givesWho issues itLifetime
PL-KSEFA KSeF token that can send invoices (InvoiceWrite) and, for statuses, read them (InvoiceRead). Its permissions are fixed when it is made, so a change needs a new token. Signing in with a KSeF certificate is not supported yet. Storing a ksef credential that is not a ksef-token answers 422, credential-unusable.The client's KSeF administrator.See the note below.
RO-EFACTURAAn OAuth authorisation for our registered ANAF application.Someone with SPV rights for the client's CUI: the legal representative or the accountant. They sign in to ANAF's portal with a qualified certificate.The access token lasts 90 days and the refresh token 365 days.
PEPPOLConsent to register the client as a Peppol participant, and the identity check the access point asks for.The client signs the consent and passes the check. We hold the access point's key, so there is no client credential to store.The access point's key is ours and we rotate it.
FR-PAThe client's own account on its Plateforme Agréée, and API credentials for the company.The client creates them on its platform, or grants us access.Set by the platform. Ask which kind it issues.
DE-XRECHNUNGNothing for Germany itself. By email, the sending mailbox. Over Peppol, the registration at the access point.The client.Not applicable.

Tip

Set expires_on from the dates above, so the 30-day alert fires before a refresh token lapses.

Sources: ANAF OAuth procedure (token lifetimes), KSeF API documentation: tokens (permissions fixed when a token is made).

Warning

Poland: sources disagree on how long a KSeF token works. The Ministry of Finance handbook (edition of 9 Feb 2026) and the taxpayer application page (changed 31 Mar 2026) say tokens authenticate until 31 Dec 2026. The Ministry's questions-and-answers page says it decided to keep tokens in KSeF 2.0 without an end date (answer 39). A public issue in the Ministry's KSeF API repository asks it to settle the difference. Checked 2 Oct 2026. Until it is settled, expect to renew the token if the Ministry sets an end date. Signing in with a certificate is not supported yet, so a token is the only way in. See the handbook, the questions and answers and the issue.

On this page