API

Olvano REST API — token authentication, invoices, contacts and webhooks. With an interactive reference.

Olvano has a REST API for connecting your own systems — create and read invoices, manage contacts and listen to events via webhooks. This page takes you from a token to your first call; the full, runnable list of endpoints lives in the interactive reference.

Before you start

You need two things:

  1. An account on the byznys plan (the free plan has no API).
  2. An API token bound to that account (see below).

Tokens are created by the account owner. One token belongs to exactly one account and carries its own permissions (scopes), so you can issue a separate token per integration.

Creating an API token

In the app go to Settings → API tokens (/app/{slug}/settings/api-tokens), choose New token, name it and pick its permissions. The plaintext token is shown only once — copy it immediately and store it safely.

Tokens are prefixed with sg_. Only an HMAC digest is stored, so the token can never be shown again — if you lose it, create a new one and delete the old.

Permissions (scopes)

Scope Allows
invoices create and edit invoices, payments, sending
expenses create and edit expenses
reports read reports and summaries

Reading (GET) common resources needs no scope; writing does. Owner-only operations (managing tokens, webhooks and bank accounts) are never available to an API token — even one issued by an owner returns 403.

Authentication and base URL

Authenticate every request with the token in the Authorization header:

Authorization: Bearer sg_your_token

The base URL is your instance's domain + /api. Account endpoints have the shape /api/accounts/{slug}/…. The examples below use environment variables:

export STARGATE="https://app.stargate.app"   # replace with your instance domain (locally http://localhost:3000)
export SLUG="your-account-slug"
export TOKEN="sg_your_token"

Your first request

List an account's invoices:

curl "$STARGATE/api/accounts/$SLUG/invoices" \
  -H "Authorization: Bearer $TOKEN"

The response is a paginated list:

{
  "invoices": [ { "id": "…", "number": "2026-0001", "status": "open", "total": "3630.00" } ],
  "total": 128,
  "page": 1,
  "pageSize": 40
}

List endpoints accept these query parameters:

Parameter Meaning Default
page page number (from 1) 1
pageSize items per page (max 100) 40
sort sort column (allowed set varies per resource) per resource
dir sort direction: asc / desc desc
q full-text search (1–200 chars)

Invoices can additionally be filtered by status, documentType and subjectId. An invalid parameter never breaks the list — the default is used instead (e.g. ?page=abc1). Every response carries total, page and pageSize, so the page count is easy to derive.

Creating an invoice

curl -X POST "$STARGATE/api/accounts/$SLUG/invoices" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subjectId": "CUSTOMER_ID",
    "lines": [
      { "name": "Consulting", "quantity": 2, "unitPrice": "1500", "vatRate": 21 }
    ]
  }'

subjectId is the customer's ID — get it from the contacts list (GET /api/accounts/$SLUG/subjects) or create one via POST …/subjects. Required fields are subjectId and at least one line in lines (each with name and unitPrice). Optionally you can send documentType, currency, variableSymbol, due, issuedOn and more — full schema in the interactive reference.

Success returns 201 and the created invoice:

{ "invoice": { "id": "…", "number": "2026-0002", "status": "open", "total": "3630.00" } }

Errors

Errors share a consistent envelope — an HTTP status plus JSON with statusMessage and a data field for machine handling:

{
  "statusCode": 400,
  "statusMessage": "Invalid invoice",
  "data": { "formErrors": [], "fieldErrors": { "subjectId": ["Required"] } }
}
Status When data
400 invalid body/parameters fieldErrors, formErrors (per-field breakdown)
401 missing or invalid token
403 missing scope / owner-only operation / feature not in plan code (for plan: plan_feature_unavailable)
404 resource or account not found (account existence is hidden) code
402 plan limit reached { "code": "plan_limit_reached", "limit": 10 }
422 domain rule (e.g. invoice with no lines) code

For domain errors, data.code is a machine-readable code (e.g. subject_not_found) you can rely on more than the message text.

Plan limits

The free plan makes no API calls (quota 0); byznys has the API and all features. When a limit is exceeded (e.g. contact count) the API returns 402 with plan_limit_reached; for a feature outside the plan (webhooks, expenses…) it returns 403 with plan_feature_unavailable. Read your account's current limits and usage via:

curl "$STARGATE/api/accounts/$SLUG/entitlements" -H "Authorization: Bearer $TOKEN"

Webhooks

A webhook notifies you of events in real time, instead of polling the API. Webhooks are a byznys plan feature and are managed by the account owner.

Register via the API (or in account settings):

curl -X POST "$STARGATE/api/accounts/$SLUG/webhooks" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUrl": "https://your-server.com/hooks/stargate",
    "events": ["invoice_paid", "invoice_sent"],
    "authHeader": "Bearer your-shared-secret"
  }'

Put specific names in events, or * for all. Available events:

invoice_created, invoice_sent, invoice_paid, invoice_overdue, invoice_cancelled, invoice_uncollectible, invoice_viewed, invoice_reminder_sent, recurring_generator_invoice_created.

Olvano sends a POST to your URL with the body:

{ "event_name": "invoice_paid", "body": { "…": "event data" } }
  • If you set authHeader, it is sent as the Authorization header — use it to verify the request really came from Olvano.
  • Every delivery carries an Idempotency-Key header (UUID) — use it to deduplicate.
  • Delivery is retried up to with exponential backoff until you return a 2xx status. Failed deliveries are listed via GET …/webhooks/{id}/failed_deliveries.

Interactive reference

The complete, always-current list of endpoints — with parameters, schemas and the ability to call them right from the browser: