SDKs & API clients

Finscale is a plain REST API: JSON in, JSON out, Bearer auth, predictable ids. Every example in these docs is curl, because curl is the whole truth — no wrapper hiding a header from you. When you want types, generate a client in your language from the same OpenAPI document that powers the API reference.

curl-first, by design

There is no proprietary Finscale SDK to install, version, and wait on. That's deliberate:

  • The HTTP surface is small. One base URL, one auth header, one error envelope, cursor pagination. An SDK would abstract about a screenful of conventions.
  • What curl shows is what production does. Every doc sample translates line-for-line into any HTTP client — the headers you see are the headers you send.
  • No language lottery. Generated clients come from the machine-readable contract, so every language is a first-class citizen the day an endpoint ships.

Requests run against the sandbox at https://api.finscale.dev/v1 with any sk_test_ key — try one in the API playground without writing code.

The OpenAPI document

The complete machine-readable contract lives at:

Fetch the contract
curl https://api.docs.finscale.dev/openapi.json -o openapi.json

It describes every endpoint, request and response schema, error shape, and webhook event payload — the same source that renders the API reference. Each operation carries a stable operationId (createPayment, listMerchants, submitDisputeEvidence…), which generators turn into method names, so generated code reads like the reference. It always tracks current v1; pin the file in your repo and regenerate on your own schedule.

Generating a typed client

Any OpenAPI 3.1 generator works. Recipes for the common stacks:

openapi-typescript + openapi-fetch
# types only — zero runtime cost
npx openapi-typescript https://api.docs.finscale.dev/openapi.json -o finscale.d.ts

# typed client over fetch
npm install openapi-fetch
client.ts
import createClient from "openapi-fetch";
import type { paths } from "./finscale";

const finscale = createClient<paths>({
  baseUrl: "https://api.finscale.dev/v1",
  headers: { Authorization: `Bearer ${process.env.FINSCALE_SECRET_KEY}` },
});

const { data, error } = await finscale.POST("/payments", {
  headers: { "Idempotency-Key": "idem_ord_9f21_0716" },
  body: {
    amount: 4900,
    currency: "EUR",
    payment_method: "ideal",
    reference: "ord_9f21_0716",
    return_url: "https://shop.example.com/checkout/return",
  },
}); // data is a fully typed Payment

These are independent open-source tools, named as examples — any generator that reads OpenAPI 3.1 produces an equivalent client.

What your client must still do

Generation gives you types and method names. The conventions that make an integration production-grade stay your job, whichever route you take:

ConcernRule
AuthSecret keys live server-side only; inject Authorization: Bearer from config, never from code. See Authentication.
IdempotencyEvery POST carries an Idempotency-Key derived from your own entity — idem_ord_9f21_0716, not a random UUID per attempt.
RetriesRetry 429 honoring Retry-After and 5xx with backoff — same key, bounded attempts. See Rate limits.
ErrorsMap the error envelope to typed failures on error.code; log request_id on every non-2xx.
MoneyAmounts are integers in minor units with an ISO 4217 currency — 4900 + "EUR" is €49.00. Never floats.
ToleranceIgnore unknown fields and enum values — additive change is normal inside v1. See Versioning.
WebhooksSignature verification is part of your integration, not the client — recipes in Webhooks.
Wrap once, use everywhere Put auth, idempotency, retries, and error mapping in one internal module wrapping the generated client — about a hundred lines — and forbid direct HTTP calls to the API from the rest of your codebase. Every convention above then holds everywhere by construction.