# Core endpoints Connectivity, company discovery, async-operation polling, and company settings. Every session starts with GET /companies to resolve the companyId that all other URLs need. Conventions (auth, envelope, pagination, dry-run, idempotency, standard errors) are in SKILL.md and are not repeated per endpoint. ### `GET /api/v1/companies` **List companies the API key can access.** `scope:companies:read · risk:low · idempotent` Returns every non-archived company the API key user is a member of, together with their role. Use the returned `id` as `{companyId}` in subsequent endpoints. **Use when:** You need to discover which company IDs an API key has access to before calling company-scoped endpoints. **Do not use for:** Fetching a single company you already know the id of: use GET /api/v1/companies/{companyId} for that. **Pitfalls:** - Multi-company keys (e.g. consultants) will see >1 result. Always pass the correct companyId in subsequent paths. - Archived companies are excluded; if a company disappears the user has been removed from it or it was archived. Response `200`: ```ts { data: { id: string, name: string, org_number: string, entity_type: string, role: "owner" | "admin" | "member" | "viewer", created_at: string }[], meta: { request_id: string, api_version: string, next_cursor?: string, audit?: { voucher_number?: string, voucher_url?: string, audit_trail_url?: string, immutable_at?: string }, partial_expansions?: string[] } } ``` --- ### `PATCH /api/v1/companies/{companyId}/settings` **Partially update company settings.** `scope:companies:write · risk:medium · idempotent · dry-run · reversible` Patches the company payment details (bank account, Bankgiro, Plusgiro, Swish, IBAN/BIC), the contact details shown on invoices (contact_person, email, phone, website), and the custom invoice email texts. All fields optional; at least one must be supplied. Idempotent (mandatory Idempotency-Key). Dry-runnable. The same validation as the MCP staging tool applies: Bankgiro/Plusgiro numbers are Luhn-checked and invoice email texts only accept a fixed placeholder set. **Use when:** You need to change the payment or contact details that appear on invoices, or override the invoice email texts, directly over REST instead of the staged MCP flow. **Do not use for:** Legal or tax profile changes (org number, VAT registration, fiscal year, accounting method): those are not exposed on the public API. Reading settings (no GET endpoint yet; use the MCP tool gnubok_get_company_settings). **Pitfalls:** - Idempotency-Key is mandatory; calls without it return 400. - contact_person is stored as default_our_reference: the default "Our reference" value on new invoices. - bankgiro and plusgiro must carry a valid Luhn check digit; null or empty string clears them. - invoice_email_texts only accepts the placeholders {fakturanummer} {kundnamn} {förnamn} {företag} {förfallodatum} {belopp}; any other {token} is rejected. Null clears every override. | Parameter | In | Type | Required | Notes | |---|---|---|---|---| | `companyId` | path | `string` | yes | | Request body: ```ts { bank_name?: string, clearing_number: string | "", account_number: string | "", bankgiro: string | "", plusgiro: string | "", swish?: string, iban: string | "", bic: string | "", contact_person?: string, email: string | "", phone?: string, website: string | "", invoice_email_texts?: { sv?: { subject?: string, greeting?: string, body?: string, signoff?: string }, en?: { subject?: string, greeting?: string, body?: string, signoff?: string } } } ``` Response `200`: ```ts { data: { company_id: string, bank_name: string, clearing_number: string, account_number: string, bankgiro: string, plusgiro: string, swish: string, iban: string, bic: string, contact_person: string, email: string, phone: string, website: string, invoice_email_texts: { sv?: { subject?: string, greeting?: string, body?: string, signoff?: string }, en?: { subject?: string, greeting?: string, body?: string, signoff?: string } } }, meta: { request_id: string, api_version: string, next_cursor?: string, audit?: { voucher_number?: string, voucher_url?: string, audit_trail_url?: string, immutable_at?: string }, partial_expansions?: string[] } } ``` --- ### `GET /api/v1/health` **Health check.** `risk:low · idempotent` Reports the API is reachable and what version is currently served. Public; no auth required. **Use when:** You want to verify connectivity, latency, or which API version is live before issuing other requests. **Do not use for:** Anything that needs authenticated data. This endpoint returns no company-specific information. **Pitfalls:** - A 200 here only means the API process responds: downstream Postgres/Supabase may still be degraded. Response `200`: ```ts { data: { status: "ok" | "degraded", service: "gnubok", api_version: string, timestamp: string }, meta: { request_id: string, api_version: string, next_cursor?: string, audit?: { voucher_number?: string, voucher_url?: string, audit_trail_url?: string, immutable_at?: string }, partial_expansions?: string[] } } ``` --- ### `GET /api/v1/operations/{id}` **Poll a long-running operation by id.** `scope:operations:read · risk:low · idempotent` Returns the current snapshot of a v1 async operation: status (queued / running / succeeded / failed / cancelled), progress (jsonb, free-form), result (on success), and error (on failure). The operation_id is returned by the POST endpoints that initiate async work (period close, year-end, currency revaluation, SIE import). **Use when:** You started an async operation and need to know whether it has finished. Poll every 5-30 seconds until a terminal status. (The 202 response advertises `operation.completed` as the eventual push signal, but that webhook event is not deliverable yet — polling is the only supported completion signal today.) **Do not use for:** Fetching the resource the operation produced: once status=succeeded, read the result field or call the resource-specific GET endpoint. Cancelling a running operation (no cancel endpoint exists in v1). **Pitfalls:** - Terminal statuses (`succeeded`, `failed`, `cancelled`) are final; the row never transitions out of them. - progress is free-form jsonb; agents should treat it as opaque except for the documented fields `phase` (string), `current` / `total` (numbers for percent calculation). - started_at is null while status=queued (the work has not begun yet); completed_at is null until a terminal status is reached. | Parameter | In | Type | Required | Notes | |---|---|---|---|---| | `id` | path | `string` | yes | | Response `200`: ```ts { data: { operation_id: string, type: string, status: "queued" | "running" | "succeeded" | "failed" | "cancelled", progress?: Record, result: unknown, error: { code?: string, message?: string, details?: unknown }, started_at: string, completed_at: string, poll_url: string, webhook_event: "operation.completed" }, meta: { request_id: string, api_version: string, next_cursor?: string, audit?: { voucher_number?: string, voucher_url?: string, audit_trail_url?: string, immutable_at?: string }, partial_expansions?: string[] } } ```