API reference
Record the facts. Get the dates.
Every request is authenticated with a bearer API key. Amounts are decimal strings, dates are ISO 8601, and every computed date comes back with the article it derives from.
Authentication
Create keys in the dashboard. A key is shown once and stored only as a SHA-256 digest, so a lost key is replaced rather than recovered.
Authorization: Bearer rf_live_XXXXXXXXXXXXXXXXXXXXXXXXEndpoints
- POST
/api/v1/casesOpen a case when a contract is concluded.
Goods require a delivery date; the request is refused without one rather than falling back to the order date, which would shorten the period.
- GET
/api/v1/cases/{externalId}The case, its period, its deadlines and its full history.
Never cached: whether the period is still open changes at midnight without anything being written.
- POST
/api/v1/cases/{externalId}/eventsMove the case on — declare, return, refund, refuse.
One endpoint for every transition, because the legal object is the transition: what happened, when, and on whose say-so.
- POST
/api/v1/cases/{externalId}/confirmationsIssue a durable-medium document.
Returns the PDF base64-encoded with its SHA-256, so you can attach it to an email in the same request.
- GET
/api/v1/confirmations/{id}Download an issued document.
The stored bytes are re-checked against the stored digest before being served.
# Open a case when the contract is concluded
curl -X POST https://revokeflow.altixcode.com/api/v1/cases \
-H "Authorization: Bearer rf_live_..." \
-H "Content-Type: application/json" \
-d '{
"externalId": "ORD-10024",
"kind": "GOODS",
"consumerEmail": "consumer@example.com",
"description": "Wollteppich 200x300",
"amount": "249.00",
"currency": "EUR",
"deliveryPaid": "9.90",
"concludedAt": "2026-03-01T10:00:00Z",
"deliveredAt": "2026-03-09T14:30:00Z"
}'
# Record that the consumer withdrew
curl -X POST https://revokeflow.altixcode.com/api/v1/cases/ORD-10024/events \
-H "Authorization: Bearer rf_live_..." \
-H "Content-Type: application/json" \
-d '{"event":"DECLARE"}'
# Refund, once you have paid it back
curl -X POST https://revokeflow.altixcode.com/api/v1/cases/ORD-10024/events \
-H "Authorization: Bearer rf_live_..." \
-H "Content-Type: application/json" \
-d '{"event":"REFUND","refundAmount":"258.90","meta":{"ref":"pi_3Abc"}}'The consumer's withdrawal link
Every case comes back with a portalUrl. Put it in the order confirmation email: Directive (EU) 2023/2673 requires a withdrawal function the consumer can actually operate, and an address to write to is not one.
The link carries a capability token. Order references are guessable by design — knowing ORD-10024 tells you ORD-10025exists — so without a secret anyone could read a stranger’s contract or declare withdrawal on it. Treat the link like a password: it goes to the consumer, and nowhere else.
"portalUrl": "https://revokeflow.altixcode.com/w/your-org/ORD-10024?t=W1c…"The rules, precisely
| Rule | How it is implemented |
|---|---|
| Goods: from delivery | Art. 9(2)(b). The last item, where an order arrives in parts — 9(2)(b)(ii). A goods case with no delivery date is refused at creation, not defaulted to the order. |
| Services and digital content: from conclusion | Art. 9(2)(a) and (c). A delivery date is meaningless here and is not accepted as a period start. |
| Fourteen days, counted from the day after | Delivered on the 5th means day 14 is the 19th, expiring at 23:59:59.999 on that day — the consumer has the whole of the last day. If the 14th day is a Saturday or Sunday, Regulation 1182/71 (incorporated by Recital 41) runs the period to the next working day instead — the opposite of what many traders assume. |
| Not informed: plus twelve months | Art. 10(1). Send informed:false and the period runs roughly a year. If you then supply the information, send informedAt and the ordinary fourteen days run from that moment — Art. 10(2). |
| Refund: fourteen days from the declaration | Art. 13(1) — not from the goods arriving. It covers the price and the delivery charge the consumer paid, for the cheapest standard option you offered. |
| Withholding, and when it ends | Art. 13(3). For goods you may withhold until they are returned or proof of dispatch is supplied, whichever is earlier. Recording MARK_RETURN_DISPATCHED ends the right to withhold; the deadline does not move. |
| Return: fourteen days from the declaration | Art. 14(1). The consumer bears the direct cost only if you told them so beforehand — a setting, off by default, because silence means you pay. |
Why your refusal was rejected
This is the first thing customers ask, so the answer is in the response. An Article 16 exemption is claimed by asserting each of its conditions by index. If any is missing, nothing is written — the case stays as it was — and the response names what was not confirmed.
POST /api/v1/cases/ORD-10024/events
{
"event": "REFUSE",
"exemption": {
"code": "DIGITAL_CONTENT_PERFORMED",
"satisfied": [0, 3],
"evidence": "Consent recorded at checkout, log id 8812"
}
}
422 Unprocessable Entity
{
"error": {
"code": "exemption_not_established",
"message": "Art. 16(1)(m) requires every condition to be met. 2 were
not confirmed. Claiming it for any digital purchase. All four
conditions must be met, and the acknowledgement must be a
separate, recorded act — not a line in the terms.",
"details": {
"missing": [
"The consumer acknowledged that they thereby lose the right of
withdrawal",
"The trader provided confirmation of that consent on a durable
medium"
],
"article": "Art. 16(1)(m)"
}
}
}This is deliberate. A refusal recorded on an incomplete claim would be worse than no record at all: it would launder a bad decision through something that looks like a compliance tool, and it would fall apart the moment it was examined.
Durable-medium documents
Three kinds: the Annex I.B model withdrawal form reproduced verbatim, an acknowledgement that a withdrawal was received (Art. 11(2)), and a confirmation that the refund was made. Each is rendered from the case’s own facts, so the document and the record cannot disagree about a date.
Each is hashed at the moment of issue and the SHA-256 recorded. A document cannot contain its own hash — printing it would change the bytes being hashed — so it carries the reference and issue instant that address the register entry, and says so in plain words. Render the same facts twice and you get identical bytes, which is what makes a mismatch mean something.
POST /api/v1/cases/ORD-10024/confirmations
{"kind": "RECEIPT", "locale": "de"}
201 Created
{
"confirmation": {
"id": "clx…",
"kind": "RECEIPT",
"digest": "9f2c4a…", // SHA-256 of the bytes below
"bytes": 24518,
"pdfBase64": "JVBERi0xLjcK…",
"downloadUrl": "https://revokeflow.altixcode.com/api/v1/confirmations/clx…"
}
}Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_payload | The body failed validation. `details.issues` names each field. |
| 401 | missing_api_key / invalid_api_key | No bearer token, or one we do not recognise. |
| 404 | case_not_found | No case with that reference in your organisation. |
| 409 | case_exists | That reference is already in use. References address the consumer portal, so they must be unique. |
| 409 | invalid_transition | That event cannot happen from the state the case is in. The message names both, and what is allowed. |
| 409 | period_closed | A declaration was sent after the period expired. Recording it as if it were in time would misstate the record. |
| 409 | concurrent_modification | The case changed while the request was in flight. Read it again and retry. |
| 422 | delivery_date_required | A goods contract was sent without a delivery date. |
| 422 | exemption_not_established | A refusal cited an Article 16 exemption whose conditions were not all asserted. `details.missing` lists them. |