- Utwórz klucz w Ustawienia → API (plan Pro). Klucz
wst_live_… pokazujemy raz. - Sprawdź klucz, plan i limity (działa z każdym scope):
export WYSTAW_API=https://wystaw.io/api/v1
export WYSTAW_API_KEY=wst_live_… # nigdy w URL ani w kodzie przeglądarki
curl -sS "$WYSTAW_API/me" -H "Authorization: Bearer $WYSTAW_API_KEY"
Wystaw fakturę od razu (?issue=true): numer zostaje nadany, a wysyłka do KSeF trafia do kolejki. O przyjęciu przez KSeF dowiesz się z webhooka invoice.ksef.accepted.
{
"brandId": "<brandId z GET /brands>",
"customerId": "<id z POST /customers>",
"type": "vat",
"buyer": {
"name": "Kawiarnia Pod Lipą Sp. z o.o.",
"nip": "2860913907",
"vatUe": null,
"otherTaxId": null,
"address": {
"street": "ul. Lipowa",
"buildingNo": "5",
"apartmentNo": null,
"postalCode": "31-001",
"city": "Kraków",
"country": "PL"
},
"email": "[email protected]"
},
"issueDate": "2026-10-15",
"saleDate": "2026-10-15",
"dueDate": "2026-10-29",
"placeOfIssue": null,
"currency": "PLN",
"lang": "pl",
"lines": [
{
"productId": "<id z GET /products>",
"name": "Projekt logo",
"qty": "1",
"unit": "usł.",
"unitPriceNet": "1500.00",
"vatRate": "23",
"discountPct": null
}
],
"paymentMethod": "transfer",
"bankAccountId": "<id z GET /bank-accounts>",
"notes": null,
"reverseCharge": false,
"splitPayment": false,
"exemption": null,
"correction": null
}
curl -sS -X POST "$WYSTAW_API/invoices?issue=true" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @faktura.json
curl -sS "$WYSTAW_API/invoices/$ID/pdf?lang=en" -H "Authorization: Bearer $WYSTAW_API_KEY" -o faktura.pdf
# po przyjęciu przez KSeF: e-mail z PDF i XML (domyślnie do nabywcy)
curl -sS -X POST "$WYSTAW_API/invoices/$ID/send" -H "Authorization: Bearer $WYSTAW_API_KEY"
Kwoty to stringi dziesiętne ("1500.00"), daty ISO 8601. Klient TypeScript: pakiet packages/sdk (openapi-fetch + typy z openapi.json).
Nagłówek Authorization: Bearer wst_live_… (lub wst_test_…). Klucz działa jak członek zespołu (rola member) ograniczony do swoich scope'ów; klucz w adresie URL jest odrzucany (400). Maks. 10 aktywnych kluczy na firmę; unieważnienie działa natychmiast. CORS jest wyłączony — API jest dla serwerów, nie przeglądarek.
Scope → endpointy| Scope | Endpointy |
|---|
| invoices:read | GET /invoices, GET /invoices/{id} |
| invoices:write | POST /invoices, PUT /invoices/{id}, DELETE /invoices/{id}, POST /invoices/{id}/issue, POST /invoices/{id}/send, POST /invoices/{id}/corrections, POST /invoices/{id}/mark-paid |
| customers:read | GET /customers, GET /customers/lookup, GET /customers/{id} |
| customers:write | POST /customers, PATCH /customers/{id}, DELETE /customers/{id} |
| products:read | GET /products, GET /products/{id} |
| products:write | POST /products, PATCH /products/{id}, PUT /products/{id}, POST /products/{id}/archive |
| brands:read | GET /brands, GET /bank-accounts |
| files:read | GET /invoices/{id}/pdf, GET /invoices/{id}/xml, GET /invoices/{id}/upo |
| webhooks:manage | GET /webhooks, POST /webhooks, DELETE /webhooks/{id}, POST /webhooks/{id}/test, POST /webhooks/{id}/enable |
POST /invoices wymaga nagłówka Idempotency-Key (np. UUID; pozostałe POST-y go akceptują). Ponowienie z tym samym kluczem i treścią przez 24 h zwraca zapisaną odpowiedź (nagłówek Idempotent-Replayed: true) — bez duplikatu faktury. Ten sam klucz z inną treścią → 422; równoległe żądanie z tym samym kluczem → 409.
60 żądań na minutę i 600 na godzinę na klucz (okno przesuwne). Każda odpowiedź ma nagłówki RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset; po przekroczeniu 429 rate_limited z Retry-After. Limity planu (np. liczba faktur) → 429 plan_limit; brak planu Pro → 402 plan_required. Body ≤ 1 MB.
Każdy błąd to application/problem+json (RFC 9457) ze stałym polem code; walidacja zwraca listę pól w errors. Wszystkie kody (23).
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://wystaw.io/developers/errors#validation_failed",
"title": "Validation failed",
"status": 400,
"detail": "Invalid input",
"code": "validation_failed",
"errors": [{ "field": "lines.0.unitPriceNet", "message": "Invalid decimal" }],
"instance": "/api/v1/invoices",
"requestId": "1b9d…"
}
Zdarzenia: invoice.issued, invoice.ksef.accepted, invoice.ksef.rejected, invoice.offline.pending, invoice.paid. Wysyłamy POST JSON na adres HTTPS; odpowiedz 2xx w 10 s. Błędy ponawiamy 5× z rosnącym odstępem; endpoint, który przez 24 h nie przyjął żadnego zdarzenia, wyłączamy i wysyłamy e-mail. Włączysz go ponownie w ustawieniach API albo przez POST /webhooks/{id}/enable. Dane w data to stan faktury w chwili wysyłki.
{
"id": "6f1c…", // stałe między ponowieniami — deduplikuj po nim
"event": "invoice.ksef.accepted",
"createdAt": "2026-10-15T10:12:03.120Z",
"tenantId": "0c4e…",
"data": {
"invoiceId": "a8d2…",
"number": "FV/2026/10/001",
"status": "accepted",
"ksefNumber": "6260181591-20261015-0A1B2C3D4E5F-7A"
}
}
Nagłówek Wystaw-Signature: t=…,v1=… to HMAC-SHA256 sekretem endpointu (whsec_…, pokazywany raz) z "<t>.<surowe body>". Weryfikuj na surowym body, przed parsowaniem JSON:
import { createHmac, timingSafeEqual } from 'node:crypto';
/** Wystaw-Signature: t=<unix>,v1=<hex hmac-sha256(secret, "<t>.<raw body>")> */
export function verifyWystawSignature(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2)));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // replay window 5 min
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
const got = Buffer.from(String(parts.v1 ?? ''), 'hex');
return got.length === expected.length && timingSafeEqual(got, expected);
}
InvoicesDrafts, issuing (KSeF), corrections, payments, files.
Newest first, cursor-paginated (`nextCursor`). Arrays (`status`, `type`) accept comma-separated values.
Parametry GET /invoices| Pole | Typ |
|---|
| brandId | query · string (uuid) |
| customerId | query · string (uuid) |
| status | query · "draft" | "issued" | "sending" | "accepted" | "rejected" | "offline" | "accepted_offksef"[] |
| type | query · "vat" | "advance" | "correction" | "proforma"[] |
| lang | query · "pl" | "en" | "pl-en" |
| paymentStatus | query · "unpaid" | "partially_paid" | "paid" |
| paymentMethod | query · "transfer" | "cash" | "card" | "other" |
| dateFrom | query · string |
| dateTo | query · string |
| q | query · string |
| cursor | query · string |
| limit | query · integer |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/invoices?status=accepted,issued&limit=20" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices?status=accepted,issued&limit=20`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Creates a draft. With `?issue=true` the draft is issued right away (number assigned, KSeF queued). Requires `Idempotency-Key`. If issuing fails the draft stays — the problem carries `invoiceId`. Advance invoices (`type: "advance"`) are not supported yet → 422 `not_supported_yet` (`feature: "advance_invoice"`); the value stays in the schema for forward compatibility.
Parametry POST /invoices| Pole | Typ |
|---|
| issue | query · booleanIssue right away (assigns the number and queues KSeF). |
| offline24 | query · booleanWith `issue=true`: issue in offline24 mode. |
| skipKsef | query · booleanWith `issue=true`: "Pomiń KSeF" — consumer invoice kept outside KSeF (status `accepted_offksef`). Not allowed for a buyer with a Polish NIP (400 `validation`). |
| paidAt | query · stringWith `issue=true`: already paid on this date (payment recorded at issue, like `/paid`). |
| paidAmount | query · stringWith `paidAt`: amount paid; omitted = paid in full. |
| paymentMethod | query · "transfer" | "cash" | "card" | "other"With `paidAt`: payment method actually used. |
| Idempotency-Key * | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Body: InvoiceDraftInput
Body POST /invoices| Pole | Typ |
|---|
| brandId * | string (uuid) |
| customerId * | string (uuid) | null |
| type * | "vat" | "advance" | "correction" | "proforma" |
| buyer * | object |
| issueDate * | string |
| saleDate * | string |
| dueDate * | string | null |
| placeOfIssue * | string | null |
| currency * | "PLN" | "EUR" | "USD" |
| lang * | "pl" | "en" | "pl-en" |
| lines * | object[] |
| paymentMethod * | "transfer" | "cash" | "card" | "other" |
| bankAccountId * | string (uuid) | null |
| notes * | string | null |
| reverseCharge | boolean |
| splitPayment | boolean |
| exemption * | object | null |
| correction * | object | null |
Odpowiedzi: 201 JSON · 422 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/invoices" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @faktura.json
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() },
body: JSON.stringify(invoice),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Parametry GET /invoices/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/invoices/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Only drafts can be changed; issued invoices are corrected with `/corrections`. Advance invoices (`type: "advance"`) are not supported yet → 422 `not_supported_yet` (`feature: "advance_invoice"`); the value stays in the schema for forward compatibility.
Parametry PUT /invoices/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Body: InvoiceDraftInput
Body PUT /invoices/{id}| Pole | Typ |
|---|
| brandId * | string (uuid) |
| customerId * | string (uuid) | null |
| type * | "vat" | "advance" | "correction" | "proforma" |
| buyer * | object |
| issueDate * | string |
| saleDate * | string |
| dueDate * | string | null |
| placeOfIssue * | string | null |
| currency * | "PLN" | "EUR" | "USD" |
| lang * | "pl" | "en" | "pl-en" |
| lines * | object[] |
| paymentMethod * | "transfer" | "cash" | "card" | "other" |
| bankAccountId * | string (uuid) | null |
| notes * | string | null |
| reverseCharge | boolean |
| splitPayment | boolean |
| exemption * | object | null |
| correction * | object | null |
Odpowiedzi: 200 JSON · 422 JSON · błędy: problem+json
curl -sS -X PUT "$WYSTAW_API/invoices/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d @body.json
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}`, {
method: 'PUT',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Parametry DELETE /invoices/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Odpowiedzi: 204 · błędy: problem+json
curl -sS -X DELETE "$WYSTAW_API/invoices/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
// 204 No Content
Assigns the number and queues the KSeF submission. Status changes are pushed as webhooks. `skipKsef: true` keeps a consumer invoice outside KSeF (status `accepted_offksef`, PDF e-mailed to the buyer; only `invoice.issued` is emitted). `paidAt` (+ `paidAmount`, `paymentMethod`) records a payment received before issue (FA(3) `Zaplacono`/`ZaplataCzesciowa`; `invoice.paid` when paid in full). Drafts of kinds that are not supported yet (advance invoices, corrections of advance or foreign-currency invoices) cannot be issued (422 `not_supported_yet`) — delete them instead.
Parametry POST /invoices/{id}/issue| Pole | Typ |
|---|
| id * | path · string (uuid) |
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Body: IssueInvoiceRequest
Body POST /invoices/{id}/issue| Pole | Typ |
|---|
| offline24 | boolean |
| confirmationToken | string |
| skipKsef | boolean |
| paidAt | string |
| paidAmount | string |
| paymentMethod | "transfer" | "cash" | "card" | "other" |
Odpowiedzi: 200 JSON · 422 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/invoices/$ID/issue" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}/issue`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Queues an e-mail with the PDF and the FA(3) XML — to the buyer e-mail of the invoice or to `to` (max 5), in the invoice language or `lang`. Only invoices accepted by KSeF, issued offline, or issued proformas (otherwise 409 `invalid_state`). Without `to` and without a buyer e-mail → 400.
Parametry POST /invoices/{id}/send| Pole | Typ |
|---|
| id * | path · string (uuid) |
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Body: SendInvoiceRequest
Body POST /invoices/{id}/send| Pole | Typ |
|---|
| to | string (email)[] |
| lang | "pl" | "en" | "pl-en" |
Odpowiedzi: 202 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/invoices/$ID/send" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to":["[email protected]"],"lang":"en"}'
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}/send`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"to":["[email protected]"],"lang":"en"}),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Corrections of advance invoices (KOR_ZAL) and of foreign-currency (non-PLN) invoices are not supported yet → 422 `not_supported_yet` (`feature: "advance_correction" | "fx_correction"`).
Parametry POST /invoices/{id}/corrections| Pole | Typ |
|---|
| id * | path · string (uuid) |
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Body: CreateCorrectionRequest
Body POST /invoices/{id}/corrections| Pole | Typ |
|---|
| reason * | string |
| effect * | "1" | "2" | "3" |
Odpowiedzi: 201 JSON · 422 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/invoices/$ID/corrections" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason":"Rabat posprzedażowy","effect":"1"}'
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}/corrections`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"reason":"Rabat posprzedażowy","effect":"1"}),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
`amount: null` marks the invoice as paid in full (fires `invoice.paid`).
Parametry POST /invoices/{id}/mark-paid| Pole | Typ |
|---|
| id * | path · string (uuid) |
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Body: MarkInvoicePaidRequest
Body POST /invoices/{id}/mark-paid| Pole | Typ |
|---|
| paidAt * | string |
| amount | string | nullnull = paid in full |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/invoices/$ID/mark-paid" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"paidAt":"2026-10-15","amount":null}'
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}/mark-paid`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"paidAt":"2026-10-15","amount":null}),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
`lang` = `pl`, `en` or `pl-en` (bilingual). Generated on first request.
Parametry GET /invoices/{id}/pdf| Pole | Typ |
|---|
| id * | path · string (uuid) |
| lang | query · "pl" | "en" | "pl-en" |
Odpowiedzi: 200 application/pdf · 202 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/invoices/$ID/pdf?lang=en" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-o faktura.pdf
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}/pdf?lang=en`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const file = Buffer.from(await res.arrayBuffer());
Parametry GET /invoices/{id}/xml| Pole | Typ |
|---|
| id * | path · string (uuid) |
Odpowiedzi: 200 application/xml · błędy: problem+json
curl -sS "$WYSTAW_API/invoices/$ID/xml" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}/xml`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const file = Buffer.from(await res.arrayBuffer());
Parametry GET /invoices/{id}/upo| Pole | Typ |
|---|
| id * | path · string (uuid) |
Odpowiedzi: 200 application/xml · błędy: problem+json
curl -sS "$WYSTAW_API/invoices/$ID/upo" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/invoices/${id}/upo`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const file = Buffer.from(await res.arrayBuffer());
CustomersCustomers and company lookup (GUS, VIES).
Parametry GET /customers| Pole | Typ |
|---|
| q | query · string |
| cursor | query · string |
| limit | query · integer |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/customers?q=kawiarnia" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/customers?q=kawiarnia`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Parametry POST /customers| Pole | Typ |
|---|
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Body: CreateCustomerRequest
Body POST /customers| Pole | Typ |
|---|
| kind * | "company" | "person" |
| name * | string |
| nip * | string | null |
| vatUe * | object | null |
| otherTaxId * | string | null |
| address * | object |
| email * | string (email) | null |
| defaultLang * | "pl" | "en" | "pl-en" |
| defaultCurrency * | "PLN" | "EUR" | "USD" |
| notes * | string | null |
Odpowiedzi: 201 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/customers" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"kind":"company","name":"Kawiarnia Pod Lipą Sp. z o.o.","nip":"2860913907","vatUe":null,"otherTaxId":null,"address":{"street":"ul. Lipowa","buildingNo":"5","apartmentNo":null,"postalCode":"31-001","city":"Kraków","country":"PL"},"email":"[email protected]","defaultLang":"pl","defaultCurrency":"PLN","notes":null}'
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/customers`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"kind":"company","name":"Kawiarnia Pod Lipą Sp. z o.o.","nip":"2860913907","vatUe":null,"otherTaxId":null,"address":{"street":"ul. Lipowa","buildingNo":"5","apartmentNo":null,"postalCode":"31-001","city":"Kraków","country":"PL"},"email":"[email protected]","defaultLang":"pl","defaultCurrency":"PLN","notes":null}),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
GUS / Biała lista (NIP) or VIES (VAT-UE) — use the result to prefill a customer.
Parametry GET /customers/lookup| Pole | Typ |
|---|
| nip | query · string |
| vatUe | query · object |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/customers/lookup?nip=2860913907" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/customers/lookup?nip=2860913907`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Parametry GET /customers/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/customers/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/customers/${id}`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
PATCH/api/v1/customers/{id}
Update a customer (partial)
Scope: customers:writecustomers:read · akcje: getCustomer → upsertCustomer
Fields not sent keep their values (read with getCustomer, saved with upsertCustomer) — needs `customers:read` too.
Parametry PATCH /customers/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Body: UpdateCustomerRequest
Body PATCH /customers/{id}| Pole | Typ |
|---|
| kind | "company" | "person" |
| name | string |
| nip | string | null |
| vatUe | object | null |
| otherTaxId | string | null |
| address | object |
| email | string (email) | null |
| defaultLang | "pl" | "en" | "pl-en" |
| defaultCurrency | "PLN" | "EUR" | "USD" |
| notes | string | null |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS -X PATCH "$WYSTAW_API/customers/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/customers/${id}`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"email":"[email protected]"}),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Issued invoices keep their buyer snapshot.
Parametry DELETE /customers/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Odpowiedzi: 204 · błędy: problem+json
curl -sS -X DELETE "$WYSTAW_API/customers/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/customers/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
// 204 No Content
ProductsProduct catalogue used on invoice lines.
Recently used first, then by name; cursor-paginated (`nextCursor`). `q` matches the name or SKU.
Parametry GET /products| Pole | Typ |
|---|
| q | query · string |
| brandId | query · string (uuid) |
| includeInactive | query · boolean |
| cursor | query · string |
| limit | query · integer |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/products?q=logo&limit=20" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/products?q=logo&limit=20`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Parametry POST /products| Pole | Typ |
|---|
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Body: CreateProductRequest
Body POST /products| Pole | Typ |
|---|
| brandId * | string (uuid) | null |
| name * | string |
| description * | string | null |
| unit * | string |
| priceNet * | string |
| vatRate * | "5" | "8" | "23" | "0 KR" | "0 WDT" | "0 EX" | "zw" | "oo" | "np I" | "np II" |
| currency * | "PLN" | "EUR" | "USD" |
| pkwiu * | string | null |
| gtu * | "GTU_01" | "GTU_02" | "GTU_03" | "GTU_04" | "GTU_05" | "GTU_06" | "GTU_07" | "GTU_08" | "GTU_09" | "GTU_10" | "GTU_11" | "GTU_12" | "GTU_13" | null |
| sku * | string | null |
| active | boolean |
Odpowiedzi: 201 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/products" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"brandId":null,"name":"Projekt logo","description":null,"unit":"usł.","priceNet":"1500.00","vatRate":"23","currency":"PLN","pkwiu":null,"gtu":null,"sku":"LOGO-1","active":true}'
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/products`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"brandId":null,"name":"Projekt logo","description":null,"unit":"usł.","priceNet":"1500.00","vatRate":"23","currency":"PLN","pkwiu":null,"gtu":null,"sku":"LOGO-1","active":true}),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Parametry GET /products/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/products/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/products/${id}`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Fields not sent keep their values (read with getProduct, saved with upsertProduct) — needs `products:read` too.
Parametry PATCH /products/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Body: UpdateProductRequest
Body PATCH /products/{id}| Pole | Typ |
|---|
| brandId | string (uuid) | null |
| name | string |
| description | string | null |
| unit | string |
| priceNet | string |
| vatRate | "5" | "8" | "23" | "0 KR" | "0 WDT" | "0 EX" | "zw" | "oo" | "np I" | "np II" |
| currency | "PLN" | "EUR" | "USD" |
| pkwiu | string | null |
| gtu | "GTU_01" | "GTU_02" | "GTU_03" | "GTU_04" | "GTU_05" | "GTU_06" | "GTU_07" | "GTU_08" | "GTU_09" | "GTU_10" | "GTU_11" | "GTU_12" | "GTU_13" | null |
| sku | string | null |
| active | boolean |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS -X PATCH "$WYSTAW_API/products/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"priceNet":"1800.00"}'
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/products/${id}`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"priceNet":"1800.00"}),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Full replacement (all fields). For a partial update use `PATCH`.
Parametry PUT /products/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Body: CreateProductRequest
Body PUT /products/{id}| Pole | Typ |
|---|
| brandId * | string (uuid) | null |
| name * | string |
| description * | string | null |
| unit * | string |
| priceNet * | string |
| vatRate * | "5" | "8" | "23" | "0 KR" | "0 WDT" | "0 EX" | "zw" | "oo" | "np I" | "np II" |
| currency * | "PLN" | "EUR" | "USD" |
| pkwiu * | string | null |
| gtu * | "GTU_01" | "GTU_02" | "GTU_03" | "GTU_04" | "GTU_05" | "GTU_06" | "GTU_07" | "GTU_08" | "GTU_09" | "GTU_10" | "GTU_11" | "GTU_12" | "GTU_13" | null |
| sku * | string | null |
| active | boolean |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS -X PUT "$WYSTAW_API/products/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d @body.json
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/products/${id}`, {
method: 'PUT',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Archived products are hidden from the catalogue; past invoices are not affected.
Parametry POST /products/{id}/archive| Pole | Typ |
|---|
| id * | path · string (uuid) |
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/products/$ID/archive" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/products/${id}/archive`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
BrandsBrands (numbering, look) and bank accounts.
Every invoice belongs to a brand (`brandId`): numbering, look and defaults.
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/brands" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/brands`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
`bankAccountId` for invoices paid by transfer.
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/bank-accounts" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/bank-accounts`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
WebhooksSigned status notifications.
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/webhooks" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/webhooks`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
HTTPS only. The signing `secret` (`whsec_…`) is returned once — store it.
Parametry POST /webhooks| Pole | Typ |
|---|
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Body: CreateWebhookRequest
Body POST /webhooks| Pole | Typ |
|---|
| url * | string (uri) |
| events * | "invoice.issued" | "invoice.ksef.accepted" | "invoice.ksef.rejected" | "invoice.offline.pending" | "invoice.paid"[] |
| description | string | null |
Odpowiedzi: 201 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/webhooks" \
-H "Authorization: Bearer $WYSTAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/wystaw/webhooks","events":["invoice.ksef.accepted","invoice.ksef.rejected"],"description":"Sklep"}'
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/webhooks`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"url":"https://example.com/wystaw/webhooks","events":["invoice.ksef.accepted","invoice.ksef.rejected"],"description":"Sklep"}),
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Parametry DELETE /webhooks/{id}| Pole | Typ |
|---|
| id * | path · string (uuid) |
Odpowiedzi: 204 · błędy: problem+json
curl -sS -X DELETE "$WYSTAW_API/webhooks/$ID" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/webhooks/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
// 204 No Content
Parametry POST /webhooks/{id}/test| Pole | Typ |
|---|
| id * | path · string (uuid) |
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/webhooks/$ID/test" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/webhooks/${id}/test`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
Endpoints failing for 24 h are disabled automatically (`active: false`, `disabledReason`). This turns deliveries back on and resets the failure window. Idempotent.
Parametry POST /webhooks/{id}/enable| Pole | Typ |
|---|
| id * | path · string (uuid) |
| Idempotency-Key | header · stringUnique key (e.g. UUID) — a retry with the same key and payload returns the stored response (24 h). |
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS -X POST "$WYSTAW_API/webhooks/$ID/enable" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/webhooks/${id}/enable`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();
AccountThe key, its tenant and plan.
Works with any scope.
Odpowiedzi: 200 JSON · błędy: problem+json
curl -sS "$WYSTAW_API/me" \
-H "Authorization: Bearer $WYSTAW_API_KEY"
TypeScript (fetch)
const res = await fetch(`${WYSTAW_API}/me`, {
headers: { Authorization: `Bearer ${process.env.WYSTAW_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // application/problem+json
const data = await res.json();