API manual

Last updated: 11 August 2026

The HTTP API gives your own system exactly what you see in the panel: the entities behind your SPV certificate, the documents ANAF has sent you, and their original XML.

It is part of the Platinum and Accountant plans. On other plans a token can still be created, but every request answers 403 rather than 401 — so nobody goes hunting for a credentials bug they do not have.

What you need

  • An account on the Platinum or Accountant plan.
  • An API token, created under Settings → Integrations. It is shown once, at creation.
  • At least one entity with monitoring switched on — the API only answers about the tax ids you monitor.

You need nothing from ANAF. The certificate, the OAuth dance and the SPV polling stay on our side; you only talk to this API.

Base address and version

https://alertespv.ro/api/v1

The version has been in the path since the first line. New fields may appear in a response at any time, so read them by name and ignore the ones you do not know. Existing fields do not change meaning and do not disappear without a new version in the path.

Every response is JSON, with one exception: the XML download, which returns the file exactly as ANAF issued it.

Authentication

The token travels in the "Authorization: Bearer …" header. There are no sessions, no cookies and no keys in the query string.

curl -s https://alertespv.ro/api/v1/entities \
  -H "Authorization: Bearer spva_your_key" \
  -H "Accept: application/json"
  • A token always starts with "spva_", so a leaked one is recognisable in a log or a bad paste.
  • Five active tokens per account at most. Revoking takes effect immediately.
  • We do not keep the plaintext — if you lost it, create another and revoke the old one.
  • The "last used" column under Settings → Integrations tells you whether a token is still in use before you revoke it.

The token opens every fiscal document on the account. Keep it on a server, in an environment variable — never in versioned code, in a mobile app, or in JavaScript running in a browser.

SituationResponseWhat it means
Header missing, or without the "spva_" prefix401Missing or malformed API token.
Unknown or revoked token401Unknown API token.
Valid token, plan without the API403The API is not part of your current plan.

Limits and pagination

120 requests a minute per account. Over the limit the answer is 429, with a "Retry-After" header saying how many seconds to wait. The limit is far more than a daily sync needs and far less than a loop with a bug costs.

  • Paginated lists accept "per_page" between 1 and 100; 100 by default.
  • Ask for a page with "page". The number of pages is in "meta.last_page".
  • Documents come back newest first, by their creation date at ANAF, then by id.

GET /entities — entities and slots

Returns everything your certificate can see — companies and individuals — together with the state of your plan's slots. An entity is listed even when it is not monitored; monitoring is what spends a slot.

{
  "data": [
    {
      "id": 12,
      "cif": "44674942",
      "kind": "company",
      "name": "EXEMPLU SRL",
      "monitored": true,
      "enabled": true,
      "last_polled_at": "2026-08-11T07:00:12+00:00",
      "slot_releasable_from": "2026-09-09"
    }
  ],
  "slots": { "used": 1, "limit": 25, "free": 24 }
}
FieldTypeWhat it is
idnumberThe entity's identifier here. This is what goes into "/entities/{id}/start".
cifstringThe tax id or personal id, without the "RO" prefix.
kindstring"company" or "person".
namestring or nullThe name as ANAF gives it.
monitoredbooleanWhether the entity currently holds a slot.
enabledbooleanWhether polling is on. A stopped entity can still hold its slot.
last_polled_atISO 8601 or nullThe last successful poll at ANAF.
slot_releasable_fromdate or nullThe first day the slot can be released, if monitoring stays off.

POST /entities/{id}/start and /stop

Starting spends a slot that does not come back immediately. The response says outright whether one was taken ("slot_taken"), because a script is exactly the caller most likely to start thirty entities without having read anything first.

curl -s -X POST https://alertespv.ro/api/v1/entities/12/start \
  -H "Authorization: Bearer spva_your_key" \
  -H "Accept: application/json"
  • "slot_taken": false means the entity already held a slot — the call was idempotent, you were not charged twice.
  • Stopping always answers "slot_released": false. A slot is released only at a subscription-month boundary, at least 30 days after it was taken, and only if monitoring was already off by then.
  • An entity belonging to another account answers 403, not 404.

GET /documents — the documents

The documents of the tax ids you monitor, with the summary extracted from the XML. A "cif" that is not among them is silently ignored as a filter — the list stays within what you are allowed to see.

ParameterValueEffect
cifstringNarrows to a single monitored tax id.
fromdateFrom the start of that day, by creation date at ANAF.
todateUp to the end of that day, inclusive.
per_page1–100Rows per page. 100 by default.
pagenumberThe page requested. 1 by default.
curl -s -G https://alertespv.ro/api/v1/documents \
  -H "Authorization: Bearer spva_your_key" \
  --data-urlencode "cif=44674942" \
  --data-urlencode "from=2026-08-01" \
  --data-urlencode "to=2026-08-11" \
  --data-urlencode "per_page=50"
  • "type" and "details" come from ANAF exactly as it words them: "FACTURA PRIMITA", "FACTURA TRIMISA", "ERORI FACTURA". Treat them as text, not as a closed enumeration.
  • "summary" is null when the document has not been archived yet, or when the XML is not an invoice we can read (an error report with no content, an unrecognised format).
  • "summary.kind" is "invoice", "credit_note", "errors" or "unsupported".
  • "counterpartyRole" says which party is the other one: "supplier" on a received invoice, "customer" on one you issued. The same invoice has different roles for the two companies, when both are monitored.
  • Amounts are strings, not numbers: "990.08" stays exactly that. Do not put them through a float on the way into an accounting system.
  • "anaf_id" plus "cif" identify the document at ANAF; "id" is its identifier here.

GET /documents/{id}/xml — the original file

Returns the XML exactly as ANAF issued it, as a download named "{cif}-{anaf_id}.xml". It is not JSON and it is not modified in any way — it is the copy we archived.

curl -s -OJ https://alertespv.ro/api/v1/documents/8814/xml \
  -H "Authorization: Bearer spva_your_key"
ResponseWhenWhat to do
403The document belongs to a tax id you do not monitor.Check the list from /entities.
404The document was never archived.Nothing to download; "archived": false told you so beforehand.
410It was archived, then fell outside the plan's retention.The row stays, the file does not. Download sooner, or move to a plan with longer retention.

Response codes

CodeMeaning
200The request succeeded.
401Token missing, malformed, unknown or revoked.
403The plan does not include the API, or the resource belongs to another account.
404The resource does not exist.
409Every slot on the plan is taken for the current subscription month.
410The file existed and was deleted when retention expired.
422Invalid parameters. The body names the field and the reason.
429Over 120 requests a minute. Wait as long as "Retry-After" says.

Recipe: yesterday's documents, with their XML

The usual shape of a daily sync: ask for the documents in a date range, remember what you already took by "id", and download the XML only for the archived ones.

# Yesterday, every monitored tax id, first page.
YESTERDAY=$(date -d yesterday +%F)

curl -s -G https://alertespv.ro/api/v1/documents \
  -H "Authorization: Bearer $ALERTESPV_TOKEN" \
  --data-urlencode "from=$YESTERDAY" \
  --data-urlencode "to=$YESTERDAY" \
  --data-urlencode "per_page=100" | jq '.data[] | {id, cif, type, archived}'

If you want to hear about documents the moment they appear rather than on the hour, use webhooks. The API is for syncing and for asking; the webhook is for being told.

Read nextWebhook manual