# Billify > API-first invoicing service for CZ/EU indie SaaS and B2B. REST-first, deterministic VAT resolver, provider-agnostic. 100 % EU infrastructure, GDPR compliant, no Merchant-of-Record fees. Billify is a B2B invoicing primitive. A client posts a domain payload — supplier, customer, items, dates — and receives a legally compliant invoice with Czech and EU regulatory requirements handled server-side: reverse charge (Art. 196 Council Directive 2006/112/EC), DUZP (date of taxable supply), per-day ČNB exchange rates for EUR invoices, VIES validation as an audit trail, and thread-safe per-supplier invoice numbering. Scope is strictly **CZ + EU**, both B2B and B2C. Non-EU customers are rejected at the API boundary with a 422 and a hint to use a Merchant of Record (Lemon Squeezy, Paddle). Billify does not collect sales tax, does not parse payment-provider webhooks, and is not a Merchant of Record — the client owns payment logic and calls Billify only to issue the invoice. The REST API is served from the dedicated host `api.billify.cz`. The marketing and Livewire UI run on `billify.cz`. The UI exists for manual correction and operational tasks (mark as paid, duplicate, delete draft) but does not expand the canonical API surface. ## API endpoints Base URL: `https://api.billify.cz`. Auth: `Authorization: Bearer bfy_xxx` (Laravel Sanctum personal access tokens; the `bfy_` prefix is the configured `SANCTUM_TOKEN_PREFIX`). Tokens carry a `team:{id}` ability that scopes every call to a single team. All resources are addressed by ULID; the internal BIGINT primary key is never exposed. Invoices - [POST /v1/invoices](https://api.billify.cz/v1/invoices): Issue an invoice from a domain payload. Synchronous (2–10 s). Accepts `paid_at` (born-Paid), `locked: true` (born-locked), and `api_profile_id` (inherit profile defaults). `vat_mode` is **not** accepted — it is derived server-side by `VatModeResolver`. Honors `Idempotency-Key` — see [Idempotency](#idempotency). - [POST /v1/invoices/dry-run](https://api.billify.cz/v1/invoices/dry-run): Validate the payload and compute totals (VAT mode, ČNB rate, CZK equivalents) without persisting and without VIES. Separate rate-limit bucket. - [GET /v1/invoices](https://api.billify.cz/v1/invoices): Paginated list, team-scoped. - [GET /v1/invoices/{ulid}](https://api.billify.cz/v1/invoices/01HX5XYZABC): Fetch invoice detail. Cross-team access returns 404. - [GET /v1/invoices/{ulid}/pdf](https://api.billify.cz/v1/invoices/01HX5XYZABC/pdf): Render the invoice PDF. Inline by default; pass `?download=1` for `Content-Disposition: attachment`. Localized CZ/EN based on customer country. `Cache-Control: no-store`. - [POST /v1/invoices/{ulid}/mark-paid](https://api.billify.cz/v1/invoices/01HX5XYZABC/mark-paid): Transition Issued → Paid (optional `paid_at` body field). Drafts → 422 `draft_cannot_be_paid`; locked invoices → 422 `invoice_locked`. - [POST /v1/invoices/{ulid}/lock](https://api.billify.cz/v1/invoices/01HX5XYZABC/lock): Freeze invoice against edit and delete. Drafts → 422 `draft_cannot_be_locked`. - [POST /v1/invoices/{ulid}/unlock](https://api.billify.cz/v1/invoices/01HX5XYZABC/unlock): Reverse a lock. Customers - [GET /v1/customers](https://api.billify.cz/v1/customers): List team-scoped customers. - [POST /v1/customers](https://api.billify.cz/v1/customers): Create a customer. Country must be CZ or EU; non-EU codes are rejected at validation with a 422 pointing the client at a Merchant of Record. - [GET /v1/customers/{ulid}](https://api.billify.cz/v1/customers/01HX5XYZABC): Detail. - [PATCH /v1/customers/{ulid}](https://api.billify.cz/v1/customers/01HX5XYZABC): Partial update. Suppliers (read-only via API — created in the UI) - [GET /v1/suppliers](https://api.billify.cz/v1/suppliers): List team-scoped suppliers. - [GET /v1/suppliers/{ulid}](https://api.billify.cz/v1/suppliers/01HX5XYZABC): Detail. Bank accounts - [GET /v1/bank-accounts](https://api.billify.cz/v1/bank-accounts): List team-scoped bank accounts. - [POST /v1/bank-accounts](https://api.billify.cz/v1/bank-accounts): Create. - [GET /v1/bank-accounts/{ulid}](https://api.billify.cz/v1/bank-accounts/01HX5XYZABC): Detail. - [PATCH /v1/bank-accounts/{ulid}](https://api.billify.cz/v1/bank-accounts/01HX5XYZABC): Partial update. API profiles — read-only via API; managed in the UI - [GET /v1/api-profiles](https://api.billify.cz/v1/api-profiles): List team-scoped API profiles. Reference one by ULID via `api_profile_id` in a `POST /v1/invoices` payload to inherit defaults (currency, payment method, numbering sequence, bank account, CZ payment symbols, `auto_paid_on_supply`, `lock_after_issue`). No implicit default — selection is always explicit. - [GET /v1/api-profiles/{ulid}](https://api.billify.cz/v1/api-profiles/01HX5XYZABC): Detail. Webhook ingress (Stripe → Billify own subscription billing only) - [POST /webhooks/stripe](https://api.billify.cz/webhooks/stripe): Stripe webhook receiver for Billify's own SaaS subscription billing (not a client integration entry point). HMAC-verified via `Stripe-Signature`, idempotent via the `stripe_events` table, rate-limited at 120/min per IP. Rate limits are per Sanctum token (IP fallback for unauthenticated calls) and vary by subscription tier of the team in the token's ability: | Tier | Read /min | Write /min | Dry-run /min | PDF /min | |-----------|-----------|------------|--------------|----------| | Free | 60 | 10 | 30 | 10 | | Pro / Pro+| 200 | 50 | 100 | 20 | | Unlimited | 600 | 120 | 240 | 60 | 429 responses include `Retry-After`. ## VAT logic Billify separates two VAT dimensions: - **`VatStatus`** — a property of the supplier. Cases: `NonPayer`, `IdentifiedPerson`, `VatPayer`. Changes rarely. - **`VatMode`** — a property of the invoice. Cases: `None`, `Standard`, `ReverseCharge`. Immutable once the invoice is issued, baked into the JSON snapshot. `VatModeResolver` deterministically derives `VatMode` from supplier VatStatus × customer country × customer VAT ID presence. The REST API does not accept `vat_mode` in the payload — the resolver is the single authority, so automation contracts remain deterministic. The Livewire UI exposes an override for human judgment; API consumers do not. For EU B2B reverse-charge invoices, VIES is consulted at issue time via `Http::pool` (2 parallel requests against `ec.europa.eu/taxation_customs/vies/rest-api/...`, 30 s timeout). Definitive verdicts (`Valid` / `Invalid`) are cached per `(supplierVAT, customerVAT)` pair until `endOfDay()`; transient `Unavailable` is never cached. The `vies_validations` table records every call as an immutable per-invoice audit trail (FK `invoice_id` cascade) with the raw `response_payload` retained for § 92a evidence. `Pending` invoices (issued today, latest verdict `Unavailable`) are retried every 15 minutes by the `billify:retry-pending-vies` scheduled command, which dispatches a single-shot `RetryViesValidationForInvoice` job per invoice (`ShouldBeUnique`, 60 s timeout). After midnight the invoice rolls out of the retry window and its `vies_compliance_status` accessor flips to `Exhausted`. The accessor states are: `NotApplicable`, `Verified`, `Pending`, `Invalid`, `Acknowledged`, `Exhausted`. The `Acknowledged` state exists only for UI users who explicitly chose "Issue anyway" over an `Invalid` verdict — the API rejects an `Invalid` outright with 422. ## Idempotency Write endpoints (`POST /v1/invoices`, `POST /v1/invoices/*/mark-paid|lock|unlock`, `POST /v1/customers`, `PATCH /v1/customers/*`, `POST /v1/bank-accounts`, `PATCH /v1/bank-accounts/*`) honor an optional `Idempotency-Key` header (`[A-Za-z0-9:_.\-]{1,255}`). Scoping: per `(team, key)` pair — two teams can legitimately share the same key. Two TTLs: 1 minute while the request is in flight, 24 hours after the response is finalized. Replays of the same key with the same canonical request body return the cached response byte-for-byte with `Idempotency-Replayed: true`. Replays with a different body → 409 `idempotency_key_conflict` carrying `original_request_hash`, `original_status_code`, and `original_created_at`. Concurrent duplicates while the first is still processing → 409 `idempotency_key_in_flight` with `Retry-After: 5`. A malformed key → 422 `invalid_idempotency_key`. 5xx responses are **not** cached — the in-flight row is deleted, clients retry safely. Calls without the header pass through with an informational `X-Idempotency-Hint` response header. ## Positioning Not a replacement for Fakturoid or iDoklad in breadth — Billify is deliberately narrow (CZ + EU only, no OSS, no B2C-non-EU). It competes on developer experience: API-first, deterministic, versioned, with structured error codes. Not a Stripe Invoicing alternative — Stripe's invoicing is global but not Czech-tax-compliant (no DUZP, no souhrnné hlášení, manual reverse charge). Billify covers exactly what Stripe Invoicing leaves on the floor for CZ/EU SaaS. Side project, side-project economics. Target is 1–2 paying customers covering hosting; first customer is [captchaapi.eu](https://captchaapi.eu), a GDPR-compliant CAPTCHA service from the same author. ## Ecosystem - [captchaapi.eu](https://captchaapi.eu): GDPR-compliant proof-of-work CAPTCHA. Same author, same infrastructure. Billify's first customer — they invoice Stripe subscriptions through Billify's API. ## Public web pages - [Homepage](https://billify.cz/): Marketing landing. - [Blog index](https://billify.cz/blog): Article listing. - [Blog post](https://billify.cz/blog/{slug}): Single article. - [robots.txt](https://billify.cz/robots.txt): Dynamic; disallows auth, settings, and `/api/` paths; emits the sitemap reference. - [sitemap.xml](https://billify.cz/sitemap.xml): Dynamic; home, blog index, individual blog posts, `docs/api`, and the legal pages below. ## Legal and compliance - [Obchodní podmínky / Terms](https://billify.cz/pravni/obchodni-podminky) - [Ochrana údajů / Privacy policy](https://billify.cz/pravni/ochrana-udaju) - [GDPR](https://billify.cz/pravni/gdpr) (301 redirect — merged into the Privacy policy) - [Zpracovatelé / Subprocessors](https://billify.cz/pravni/zpracovatele) - [Zpracovatelská smlouva / DPA (Art. 28 GDPR)](https://billify.cz/pravni/zpracovatelska-smlouva) - [DPA print view](https://billify.cz/pravni/zpracovatelska-smlouva/tisk): Plain version for printing / archival. ## Owner Vladislav Rajtmajer, Czech OSVČ (IČO 73396249). Contact via [billify.cz](https://billify.cz). ## Optional - [Source code on GitHub](https://github.com/rajtik/billify-livewire): Laravel 13 + Livewire v4 + Flux UI Pro + PostgreSQL. Not a framework — a service whose code is open for audit. - [Internal project doc (project.md)](https://github.com/rajtik/billify-livewire/blob/main/docs/project.md): Full product scope, positioning, technical decisions, and roadmap. - [REST API integration guide (rest-api.md)](https://github.com/rajtik/billify-livewire/blob/main/docs/rest-api.md): Single source of truth for everything API — auth, rate limiting, error envelope conventions, idempotency contract, reverse-charge legal context, the full `POST /v1/invoices` validation flow, doc-authoring rules. - [Scramble OpenAPI spec (machine-readable)](https://api.billify.cz/docs/api.json): Auto-generated from controller code — full request/response shapes, validation rules, enum values. Source of truth for endpoint contract. - [Scramble interactive docs](https://api.billify.cz/docs/api): Browsable HTML rendering of the OpenAPI spec.