FinBaze for Platforms

SDKs

The server SDKs share their behaviour (see the language pages: Node, Python, PHP, Kotlin and Java): client-credentials exchange with a cached token, the FinBaze-Version header, an Idempotency-Key on every mutation that can be retried, retries that honour Retry-After, typed errors, auto-paging, and a constant-time webhook check. This page is generated from one snippet catalogue, so the five versions of each call cannot drift apart.

Install#

Node

npm install @finbaze/platform-sdk

Python

pip install finbaze

PHP

composer require finbaze/finbaze-php

Kotlin

implementation("com.finbaze:finbaze-java:0.1.0")

Java

<dependency>
  <groupId>com.finbaze</groupId>
  <artifactId>finbaze-java</artifactId>
  <version>0.1.0</version>
</dependency>

Create the client#

A fb_sk_test_ or fb_rk_test_ key uses the sandbox API, a live key the live API. The SDK exchanges the key for an access token, caches it and refreshes it a minute before it expires.

Node

import Finbaze from "@finbaze/platform-sdk";

const finbaze = new Finbaze({ secretKey: process.env.FINBAZE_SECRET_KEY });

Python

import os
from finbaze import FinBaze

finbaze = FinBaze(secret_key=os.environ["FINBAZE_SECRET_KEY"])

PHP

use FinBaze\FinBaze;

$finbaze = new FinBaze(getenv('FINBAZE_SECRET_KEY'));

Kotlin

val finbaze = FinBaze(secretKey = System.getenv("FINBAZE_SECRET_KEY"))

Java

FinBaze finbaze = FinBaze.builder().secretKey(System.getenv("FINBAZE_SECRET_KEY")).build();

Create a company#

Idempotent by externalId: calling it again returns the same company with created false. The sandbox accepts the magic KvK number 00000001.

Node

const company = await finbaze.platform.companies.create({
  externalId: "acme-1",
  registrationCountry: "NL",
  registrationNumber: "00000001",
});

Python

company = finbaze.platform.companies.create(
    {"externalId": "acme-1", "registrationCountry": "NL", "registrationNumber": "00000001"}
)
company_id = company.company.id

PHP

$company = $finbaze->platform->companies->create([
    'externalId' => 'acme-1',
    'registrationCountry' => 'NL',
    'registrationNumber' => '00000001',
]);
$companyId = $company->company->id;

Kotlin

val company = finbaze.platform.companies.create(
    externalId = "acme-1",
    registrationCountry = Country.NL,
    registrationNumber = "00000001",
)
val companyId = company.company.sdkPlatformCompanyFields.id

Java

var company = finbaze.getPlatform().getCompanies().createAsync("acme-1", Country.NL, "00000001").get();
String companyId = company.getCompany().getSdkPlatformCompanyFields().getId();

Upsert a user and give it access#

Users are yours: identify them by your own id. role is admin, member or viewer.

Node

const user = await finbaze.platform.users.upsert({ externalUserId: "user-42", firstName: "Sam", lastName: "Jansen" });
await finbaze.platform.users.setAccess({ userId: user.id, companyId: company.id, role: "admin" });

Python

user = finbaze.platform.users.upsert({"externalUserId": "user-42", "firstName": "Sam", "lastName": "Jansen"})
finbaze.platform.users.set_access(user_id=user.id, company_id=company_id, role="admin")

PHP

$user = $finbaze->platform->users->upsert(['externalUserId' => 'user-42', 'firstName' => 'Sam', 'lastName' => 'Jansen']);
$finbaze->platform->users->setAccess(companyId: $companyId, role: 'admin', userId: $user->id);

Kotlin

val user = finbaze.platform.users.upsert(externalUserId = "user-42", firstName = "Sam", lastName = "Jansen")
finbaze.platform.users.setAccess(companyId = companyId, role = PermissionSet.admin, userId = user.id)

Java

var user = finbaze.getPlatform().getUsers().upsertAsync("user-42", "Sam", "Jansen").get();
finbaze.getPlatform().getUsers().setAccessAsync(companyId, PermissionSet.admin, user.getId()).get();

Mint a user token#

A short-lived token that acts as one platform user on the companies you name. asUser does this for you and caches the token.

Node

const token = await finbaze.platform.userTokens.create({ userId: user.id, companyIds: [company.id] });

Python

token = finbaze.platform.user_tokens.create(user_id=user.id, company_ids=[company_id])
as_user = finbaze.as_user(user_id=user.id, company_ids=[company_id])

PHP

$token = $finbaze->platform->userTokens->create(userId: $user->id, companyIds: [$companyId]);
$asUser = $finbaze->asUser(userId: $user->id, companyIds: [$companyId]);

Kotlin

val token = finbaze.platform.userTokens.create(userId = user.id, companyIds = listOf(companyId))
val asUser = finbaze.asUser(userId = user.id, companyIds = listOf(companyId))

Java

var token = finbaze.getPlatform().getUserTokens().createAsync(user.getId(), null, List.of(companyId), null).get();
FinBaze asUser = finbaze.asUser(user.getId(), null, List.of(companyId), null);

Create an embed session#

The client secret works once: create a new session for every mount and refresh, and hand only clientSecret to the browser.

Node

const session = await finbaze.platform.embedSessions.create({
  company: company.id,
  user: { id: user.id },
  components: { vatReturn: { enabled: true } },
  previousSessionId, // from the browser SDK's fetchClientSecret
});
return { clientSecret: session.clientSecret };

Python

session = finbaze.platform.embed_sessions.create(
    company=company_id,
    user={"id": user.id},
    components={"vatReturn": {"enabled": True}},
)
client_secret = session.client_secret

PHP

$session = $finbaze->platform->embedSessions->create(
    company: $companyId,
    user: ['id' => $user->id],
    components: ['vatReturn' => ['enabled' => true]],
);
$clientSecret = $session->clientSecret;

Kotlin

val session = finbaze.platform.embedSessions.create(
    company = companyId,
    user = EmbedUser.id(user.id),
    components = EmbedComponents.builder().vatReturn().build(),
)
val clientSecret = session.clientSecret

Java

var session = finbaze.getPlatform().getEmbedSessions().createAsync(
    companyId, EmbedUser.id(user.getId()), EmbedComponents.builder().vatReturn(true).build()).get();
String clientSecret = session.getClientSecret();

List with auto-paging#

Lists are cursor pages. data is the current page; the auto-paging helper walks every page and fetches the next one only when you reach it.

Node

const page = await finbaze.platform.companies.list({ limit: 100 });
await page.autoPagingEach(async (c) => console.log(c.externalId));

Python

for c in finbaze.platform.companies.list(limit=100).auto_paging_iter():
    print(c.external_id)

PHP

foreach ($finbaze->platform->companies->list(limit: 100)->autoPaging() as $c) {
    echo $c->externalId, "\n";
}

Kotlin

finbaze.platform.companies.list(limit = 100).autoPaging().collect { println(it.externalId) }

Java

for (var c : finbaze.getPlatform().getCompanies().listAsync(100, null).get().autoPagingIterable()) {
    System.out.println(c.getExternalId());
}

Verify a webhook#

Pass the body exactly as received, before any JSON parsing. The check is constant-time, accepts any of several signatures while a secret rotates, and rejects timestamps more than five minutes off.

Node

const event = await finbaze.webhooks.constructPlatformEvent(rawBody, req.header("x-finbaze-signature"), process.env.FINBAZE_WEBHOOK_SECRET);

Python

event = finbaze.webhooks.construct_platform_event(raw_body, request.headers["X-FinBaze-Signature"], secret)

PHP

$event = $finbaze->webhooks->constructPlatformEvent(file_get_contents('php://input'), $_SERVER['HTTP_X_FINBAZE_SIGNATURE'] ?? null, $secret);

Kotlin

val event = Webhooks.constructPlatformEvent(rawBody, request.header("X-FinBaze-Signature"), secret)

Java

Map<String, Object> event = Webhooks.constructPlatformEvent(rawBody, request.getHeader("X-FinBaze-Signature"), secret);

Handle errors#

Errors are typed from extensions.code (see errors). Network errors, 429 and 5xx are retried for you; a mutation that can be retried carries an Idempotency-Key.

Node

try {
  await finbaze.platform.companies.retrieve("c_missing");
} catch (error) {
  if (error instanceof FinbazeRateLimitError) await sleep(error.retryAfterSeconds * 1000);
  else throw error;
}

Python

from finbaze import FeatureDisabledError, RateLimitError

try:
    finbaze.platform.features.effective(company_id)
except RateLimitError as error:
    time.sleep(error.retry_after_seconds or 1)
except FeatureDisabledError as error:
    log.warning("hidden: %s (request %s)", error.code, error.request_id)

PHP

use FinBaze\Errors\FeatureDisabledError;
use FinBaze\Errors\RateLimitError;

try {
    $finbaze->platform->features->effective($companyId);
} catch (RateLimitError $e) {
    sleep((int) ($e->retryAfterSeconds ?? 1));
} catch (FeatureDisabledError $e) {
    error_log("hidden: {$e->errorCode} (request {$e->requestId})");
}

Kotlin

try {
    finbaze.platform.features.effective(companyId)
} catch (e: RateLimitError) {
    delay(((e.retryAfterSeconds ?: 1.0) * 1000).toLong())
} catch (e: FeatureDisabledError) {
    log.warn("hidden: ${e.code} (request ${e.requestId})")
}

Java

try {
    finbaze.executeBlocking(new SdkPlatformCompanyFeaturesQuery(companyId));
} catch (RateLimitError e) {
    Thread.sleep((long) ((e.getRetryAfterSeconds() != null ? e.getRetryAfterSeconds() : 1.0) * 1000));
} catch (FeatureDisabledError e) {
    log.warn("hidden: {} (request {})", e.getCode(), e.getRequestId());
}

Upload many objects (NDJSON)#

One request, applied in the background. The API requires an Idempotency-Key; the SDK generates one, and reusing it retries the same upload safely.

Node

const job = await finbaze.platform.bulk.create({
  object: "relations",
  lines: [{ externalId: "r1", name: "First" }, { externalId: "r2", name: "Second" }],
  company: { externalId: "acme-1" },
});

Python

job = finbaze.platform.bulk.create(
    object="relations",
    lines=[{"externalId": "r1", "name": "First"}, {"externalId": "r2", "name": "Second"}],
    company={"external_id": "acme-1"},
)

PHP

$job = $finbaze->platform->bulk->create(
    object: 'relations',
    lines: [['externalId' => 'r1', 'name' => 'First'], ['externalId' => 'r2', 'name' => 'Second']],
    company: ['externalId' => 'acme-1'],
);

Kotlin

val job = finbaze.platform.bulk.create(
    objectType = "relations",
    lines = listOf(mapOf("externalId" to "r1", "name" to "First"), mapOf("externalId" to "r2", "name" to "Second")),
    company = "ext:acme-1",
)

Java

var lines = List.of(Map.<String, Object>of("externalId", "r1", "name", "First"), Map.<String, Object>of("externalId", "r2", "name", "Second"));
var job = finbaze.getPlatform().getBulk().createAsync("relations", lines, "ext:acme-1").get();