Skip to content

insufficient_scope

HTTP 403 type: authorization_error param: null
{
"error": {
"type": "authorization_error",
"code": "insufficient_scope",
"message": "This key holds scopes [invoices:read] and cannot write invoices. Required scope: invoices:write.",
"param": null,
"upstream": null,
"doc_url": "https://docs.fiskhub.com/errors/insufficient_scope"
}
}

What happened

The key authenticated successfully but does not carry the scope this operation requires. The message names the scopes it holds and the one it needs.

Nothing was fiscalized.

Should you retry?

Not with the same key. Scopes are fixed when a key is issued; they are not negotiated per request.

The fix

Use a key that holds the required scope, or issue one.

OperationScope
POST /v1/invoices, POST /v1/invoices/{id}/retryinvoices:write
GET /v1/invoices, GET /v1/invoices/{id}invoices:read
POST / PATCH on companies, devicesfleet:write
GET on companies, devicesfleet:read
POST /v1/certificatescertificates:write
GET /v1/certificatescertificates:read

A write scope does not imply the matching read scope; grant both when a key needs both.

Scoping keys well

The point of scopes is that a key can only do what its holder needs, so a leak costs less.

  • A device fiscalizing sales needs invoices:write and nothing else. It never reads the fleet and never touches certificates. A key that leaks off a machine in a public place should not be able to enumerate the estate.
  • A reconciliation job needs invoices:read only. It reads yesterday’s invoices and files a report; it must not be able to create one.
  • A provisioning script needs fleet:write, typically alongside certificates:write, and runs from somewhere you control.

Resist the single all-scopes key shared by everything. It works until it is on a machine you no longer physically control, and then the blast radius is the whole account.