Usage Statement Already Issued
URI: https://docs.ankatech.co/errors/usage-statement-already-issued
HTTP Status: 409 Conflict
When you see this
You asked for a signed usage statement for a month that already has one in your scope, and the request named no statement to supersede.
The request is refused and nothing is written. Every statement already retained for that month stays retained and stays verifiable against its own bytes.
Why a second original is refused
An issued statement is never rewritten. A figure that was wrong is corrected by issuing a second statement for the same month that names, in supersedes, the statement it replaces — and both are retained, so a holder of the earlier document can still verify it while the later one says, inside its own signed bytes, that it replaces it.
Because a correction has to be a second statement for the same month, the store deliberately does not limit a month to one statement. What that also permits is two statements for one month that name no relationship to each other. Both would verify, both would be retained, and which of them is the statement for that month would have no answer: basis.issuedAt orders the two documents without saying that the later one replaces the earlier. That is the question a disputed true-up invoice asks, so the ambiguity is refused at the point it would be created. An original is issued once per scope and month, and every later statement for that scope and month states the one it supersedes.
The refusal is therefore not "this already exists". It is "say what you are correcting".
Not the same refusal as its siblings
Three 409 refusals can answer POST /api/v3/admin/usage/statements, and each is answered by a different caller action:
| Type | The condition | What you do |
|---|---|---|
usage-statement-already-issued | The month is already stated and this request declares no correction | Name what you are correcting |
usage-statement-already-superseded | The statement you named is no longer the current one | Correct the current one instead |
usage-statement-not-finalizable | The month cannot be stated yet | Wait until the month is FINAL |
usage-statement-coverage-unverifiable | The deployment's installation cannot be vouched for | Restore the deployment trial sentinel |
usage-statement-period-not-covered (422) | The month ends before the store's first covered day | State a month from that day on |
A distinct type is the machine-readable discriminator between them. A caller that branches on type does not have to parse detail to tell them apart.
Common Causes
- The month was already stated — by another operator, or by an earlier run of the same automation — and this request was composed as a first issuance.
- An automated monthly job was retried after its first attempt had in fact succeeded.
- A revised figure was expected, and the request omitted
supersedes.
What the detail names
The month, and nothing else. It names no statement identity, no tenant and no figure. The identities of the statements already retained for the month are what GET /api/v3/admin/usage/statements returns, to a caller entitled to read them.
Response Example
{
"type": "https://docs.ankatech.co/errors/usage-statement-already-issued",
"title": "Usage Statement Already Issued",
"status": 409,
"detail": "A usage statement has already been issued for 2026-08 in this scope. An issued statement is never rewritten, so a revised figure is published as a correction: reissue naming the statement it supersedes. Both are retained and both stay verifiable against their own bytes.",
"instance": "/api/v3/admin/usage/statements",
"extensions": {
"requestId": "3e8b1f47-6c20-4a95-b7d3-81f0c2e9a654"
}
}
Content-Type: application/problem+json — the response follows RFC 9457 Problem Details.
The refusal is raised by the licence server and reaches you through the Admin API unchanged: 409 is a member of {400, 402, 404, 409, 422}, the set the relay propagates with its own type, title, detail and status. You therefore read this refusal rather than a 502 reporting an outage. instance and the correlation identifier are always the Admin API's own; it carries the latter as extensions.requestId. See Error Index for where each service puts it.
How to Resolve
- Read the month's statements.
GET /api/v3/admin/usage/statementslists this tenant's statements. Identify the one that is current for the month: the statement no other statement names inbasis.supersedes. - If it already carries the figure you intended, stop. The month is stated, and a second statement would say nothing new.
- If it does not, issue a correction. Repeat the request with
supersedesset to thestatementIdof the current statement. Name the current one, not an earlier one it has already replaced — naming a replaced statement is refused asusage-statement-already-superseded.
Two near-simultaneous originals can still both be accepted. The refusal is decided by reading the retained statements for the month inside the same transaction as the write, so two genuinely concurrent first issuances can both observe an empty month and both be written. The check narrows the window; it is not a uniqueness constraint. If the listing shows two statements for one month that name no relationship to each other, issue a correction naming whichever is being superseded.
Related
- Usage Statement Already Superseded
- Usage Statement Not Finalizable
- Usage Telemetry, Exports and Signed Statements*
- Statement Verification Keys*
- Conflict
For full schema definitions, examples, and interactive testing, see the Developer Hub Reference.