Nisan is API-first: the dashboard is the first client of the same API you would build against. Here is how that API behaves — decided, written down, and not going to change underneath you.
The API is not open yet. There are no keys to issue and no sandbox to point you at. We publish the contract before the API opens, so you can judge it early rather than discover it late. If you are planning an integration, tell us what you need — that is the most useful thing you can do today.
Each of these is a decision on record, not an intention.
Nisan is built API-first: the app you log into is the first consumer of the same public API you would use. Nothing is reachable from the interface that the API cannot do.
Every response — success or failure — is the same envelope: success, message, data and meta. Lists carry their paging inside meta rather than changing the shape of the response.
Objects are addressed by a prefixed public id, so an id tells you what it points at. Internal database ids are never returned and never accepted.
Times are ISO-8601 with a trailing Z. The single exception is the legal date of a document, which belongs to the timezone of whoever issued it — and that is a separate, explicit field.
Each response carries a Nisan-Request-Id, on errors as much as on successes. It is always generated by the server and never read from a header you send, so quoting it in a support conversation identifies exactly one request.
Resources sit at the root and stay there. Versioning is pinned to your account and expressed with a Nisan-Version header, so a URL you wrote down does not become the wrong URL later.
An account is pinned to a specific version and there is no "use the latest" mode to fall back on. A breaking change ships as a new version that you adopt when you choose to.
Write endpoints accept an Idempotency-Key. A retry after a timeout returns the original result instead of creating a second invoice, charge or grant.
Failures return a stable machine-readable code together with a message meant for a human to read. You can branch on the code without parsing prose.
Interactive sessions use an HttpOnly cookie with CSRF protection. Programmatic access uses API keys that are scoped and separated into test and live — a test key cannot touch live data.
Success or failure, list or single object — the envelope does not change.
{
"success": true,
"message": "ok",
"data": { /* the object, or a list of them */ },
"meta": null /* paging and error codes live here */
}
/* on every response, success or error */
Nisan-Request-Id: req_… /* server-generated, always */
Nisan-Version: … /* the version your account is pinned to */
Nisan-Release: … /* which build answered */
Paged lists put current_page, per_page, total and total_pages inside meta.pagination. The version value is left out of the examples above on purpose: the first version has not been named yet. We would rather show you an empty field than a placeholder you might copy.
Only endpoints that are built are listed. Staff-only routes and the inputs that payment providers call are not part of the public API and are left out.
Every path below starts at https://api.beta.nisan.io.
POST /auth/register — Create an account with an email and a password. Country and business type are asked for later, in onboarding.
POST /auth/login — Sign in and start a session.
POST /auth/logout — End the session. Always allowed, even on an expired session.
GET /auth/session — Who is signed in, and the state of the account.
GET /compliance/onboarding — Where your onboarding stands: what is missing and what you can already do.
POST /compliance/onboarding/profile — Save your country, business type and way of selling, together.
POST /compliance/onboarding/invoice-profile — Save the details printed on your invoices.
GET /compliance/country-rules — The countries, business types and ways of selling you can choose from.
POST /customers — Add a customer card.
GET /customers — Your customers, newest first, paged.
GET /customers/{id} — One customer card, archived ones included.
PATCH /customers/{id} — Edit a customer card. Invoices already issued keep the details they were issued with.
POST /invoices — Create a draft invoice with its lines.
PATCH /invoices/{id} — Edit a draft. An issued invoice cannot be edited.
GET /invoices — Your invoices, filtered by status or customer, paged.
GET /invoices/{id} — One invoice with its lines.
POST /invoices/{id}/finalize — Issue a draft: it gets its number and becomes payable, and can be sent to the customer. Needs an Idempotency-Key.
GET /balance — Your balance in each currency, and the payout requests still open.
GET /ledger — The movements of your balance, newest first, paged.
GET /payouts — Your payout requests, newest first, paged.
GET /payouts/{id} — One payout request and where it stands.
POST /payouts — Request a payout to your bank account. Needs an Idempotency-Key. Requests are accepted once bank accounts can be added.
POST /payouts/{id}/cancel — Cancel a payout request that is still waiting; the money returns to your balance. Needs an Idempotency-Key.
GET /health — Is the API up? Public.
GET /meta/releases — The release notes and the running version. Public.
Today the account endpoints are reached with the signed-in session of the dashboard; API keys come later. Health and releases are public.
The first API version has not been named yet. Printing an example would quietly turn a placeholder into the answer, which is exactly how a temporary choice becomes permanent.
Keys cannot be issued yet, by design: a key is bound to an API version, and until the first version is named there is nothing to bind it to. A button that cannot work is not a shortcut; it is a broken promise.
None exist. When they do, they will be listed here; until then the envelope above is simple enough to call with whatever HTTP client you already have.
Three products that share one account, one identity check and one set of records.
Invoices and payments for clients anywhere
Identity, tax and the paperwork behind them
Turn a payment into real access