Home / Authentication

Authentication

Where the key goes, what the server does with it, and which check answers first when a call fails.

Every render call needs your API key. The usual place is a header:

Authorization: Bearer <your api key>

Keys come from the dashboard at dashboard.ireceipt.pro. The rest of this page is about the failure cases, because they are less obvious than the success case.

If you are looking at a 401 or a 403 right now, the short version is on errors and retries: 401 means no key reached the server, 403 means one did and was not accepted. This page explains what counts as "reached" and what else a 403 can mean.

Two ways in, and two that look like ways in

The server reads the key from the Authorization header or, if there is no such header, from a token query parameter. The API reference lists token as the alternative to the header, and it is what makes a plain GET link possible. A key in a URL ends up in server logs and browser history, so keep it for cases where a header is impossible.

X-Api-Key and ?api_key= are common in this category, and neither is read. Neither produces an error saying so: they come back as 401, the same response as sending nothing, because from the server's point of view that is what you did.

# read, and rejected: 403 means the server saw a key
curl -X POST '...?token=not-a-real-key'     # 403 {"message":"Token invalid"}

# not read at all: 401, same as no credential
curl -X POST '...?api_key=<key>'           # 401 {"message":"Unauthorized"}
curl -X POST ...  -H 'X-Api-Key: <key>'    # 401 {"message":"Unauthorized"}

So a 401 while you are holding a key you are certain is good is almost always a question of where you put it.

Public templates need a key too

Worth saying because the URL suggests otherwise. The path is:

POST https://api.ireceipt.pro/v1/{format}/{scope}/{templateId}

{scope} is public or private, and it describes the template — whether it is one of the shared library templates or one you authored in your own dashboard. It does not describe the endpoint. A call to a public template with no key gets a 401 exactly like a private one.

What the server does with the header

It removes the word bearer, in any case, and all whitespace, and looks up what is left as a key. Which means:

With a key that does not exist, every variant answers 403 {"message":"Token invalid"}. "Token invalid" means exactly that: what was left after the clean-up is not a live key of any account.

Which check answers first

A call goes through four gates in a fixed order, and the first one that fails answers. Knowing the order tells you what a status code can and cannot be about.

  1. Route. {format} must be pdf, jpg, png or webp. Otherwise 404 {"message":"Not Found"}, with or without a key.
  2. A key at all. Otherwise 401 {"message":"Unauthorized"}.
  3. The key. It must resolve, be in date, and the plan's monthly allowance must have room. Otherwise 403 with Token invalid, Token expired or Limit exceeded.
  4. The request. {scope} must be public or private and the template must exist. Otherwise 400 with Invalid template type or Invalid template name. Sizes are checked here too — see below.

Two consequences. A typo in {format} is a 404 before your key is even looked at. And a typo in {scope} or the template id with a working key is a 400, not a 403 — but with a broken key you will see the 403 from gate 3 first, and the 400 only once the key is fixed.

The one 403 in gate 4 is about your plan, not your key: on the free plan a render can be at most 1200 px on either side, and anything larger answers A size larger than 1200x1200 requires a paid subscription. Past the hard limits — 5000 px for PDF, 3200 px for images — it is a 400 on any plan.

Every render endpoint also accepts GET, with variables, size and format as query parameters, so a method change is not something to debug either.

Do not call this from the browser

A preflight from any origin gets a permissive answer. Read off the live API on 2026-11-01:

access-control-allow-origin: *
access-control-allow-methods: GET,POST,OPTIONS
access-control-allow-headers: authorization,content-type

The actual response then carries an origin decided by the allowed domains on your account. With none set, it echoes whatever origin called, so a fetch() from any site with your key in it works. With domains set, an origin outside the list gets https://api.ireceipt.pro instead, and the browser refuses to hand the response to the page.

That check is the browser's, not the server's: the render still runs and still counts against your allowance, and anything outside a browser — curl, a script — ignores it entirely. Allowed domains stop someone else's site from embedding calls with your key; they do not make a key in frontend code private. An API key in frontend JavaScript is in the bundle and in the network tab, and anyone who wants it has it in seconds.

If you need a document generated in response to something a user does in the browser, put your own endpoint in between and let it hold the key.

Where to keep the key

Nothing exotic. Environment variable on the server, injected at deploy, not committed:

import { IReceiptPRO } from '@ireceipt.pro/js'

const client = new IReceiptPRO(process.env.IRECEIPT_API_KEY)

The SDK takes the key at construction and sets the header itself, so build the client once at startup rather than per request.

What this page does not cover

Next

Written by Nixie, iReceipt PRO's assistant — an assistant, not a person. The 401, 404, CORS and "Token invalid" answers were read off the live API with the curl shown next to them; the answers that need a working key — expired key, used-up allowance, bad scope, unknown template, sizes — are read from the API's source, last checked 2026-11-01. If something here stops being true, it is a bug in the page — mail [email protected] and it will be fixed.