← Skofa

Developers

Read your business's Skofa data from your own software, send in new records, and get notified the moment work completes. The full machine-readable contract is the OpenAPI 3.1 specification.

Authentication

An owner or admin creates an API key in Settings → Developers, choosing its scopes and expiry. The key is shown once. Send it on every request:

curl https://<your-skofa-host>/api/v1/agents \ -H "Authorization: Bearer sk_live_…"

A key only ever sees the business that created it. It stops working when revoked, when it expires, or when its creator is no longer an owner or admin of that business. Browser sessions are not accepted by the API. Each key may make 120 requests per minute.

Scopes and endpoints

MethodPathScope
GET/api/v1/customersREAD_CUSTOMERS
POST/api/v1/customersWRITE_CUSTOMERS
GET/api/v1/invoices, /api/v1/invoices/{id}ACCESS_FINANCIAL_DATA
GET/api/v1/workflow-runs, /api/v1/workflow-runs/{id}READ_WORKFLOW_RUNS
GET/api/v1/agents, /api/v1/agents/{id}READ_AGENTS
GET/api/v1/goals, /api/v1/goals/{id}READ_GOALS

Pagination and errors

Lists return { data: [...], nextCursor }, newest first. Pass ?cursor= with the previous nextCursor and ?limit= (1–100, default 25). Single resources return { data: {...} }.

Errors are JSON — { error: { code, message, requestId } } — with stable codes: unauthenticated (401), forbidden (403, e.g. missing scope), not_found (404, including any id that belongs to another business), invalid_request (400), conflict (409), rate_limited (429) and internal_error (500).

Safe retries (Idempotency-Key)

Send an Idempotency-Key header (a fresh UUID per logical request) with POST /customers. If the network drops and you retry with the same key and the same body, you get the original response back — marked Idempotent-Replayed: true — and nothing is created twice. Reusing a key with a different body returns 409 conflict, as does a retry while the first request is still running. Keys belong to the API key that sent them and expire after 24 hours. Only successful responses are stored, so a request that failed can be corrected and retried with the same key.

Webhooks

Add an endpoint in Settings → Developers and choose events: workflow.completed, workflow.failed, approval.pending, invoice.paid. Skofa POSTs a JSON event { id, type, createdAt, businessId, data } with a Skofa-Signature: t=…,v1=… header — an HMAC-SHA256 of <t>.<raw body> keyed with the endpoint's secret.

import { createHmac, timingSafeEqual } from "node:crypto"; // rawBody: the exact request body text, BEFORE JSON.parse function verifySkofaSignature(secret, header, rawBody, toleranceSeconds = 300) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2))); const t = Number(parts.t); if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false; const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest(); const given = Buffer.from(parts.v1 ?? "", "hex"); return given.length === expected.length && timingSafeEqual(given, expected); }

Respond with any 2xx within 10 seconds. Anything else is retried with exponential backoff (8 attempts over roughly 15 hours) and then marked failed; redirects are not followed. Delivery is at-least-once, so deduplicate on the event id (also sent as Skofa-Event-Id). Endpoints must be public https:// URLs.

Send test event POSTs a signed ping event to one endpoint through the same delivery path and shows the answer your endpoint actually gave; a test event is sent once and never retried. You can change an endpoint's URL in place (it is validated exactly like a new one). Rotate secret issues a new signing secret, shown once, and switches over immediately: every later delivery, including retries of earlier events, is signed only with the new secret. Deliveries your endpoint rejects meanwhile are retried for about 15 hours, so update your endpoint within that time — or verify against both secrets while you switch.