Basely

Basely API

API reference

One key per business. Read the books, bank accounts, transactions, invoices and bills of a business, write to them with the owner's permission, and get signed webhooks when something changes.

https://api.baselyapp.com/v1openapi.jsonAll developer tools

Quick start

  1. The owner of the business opens API in the Basely panel and creates a key with the scopes your system needs.
  2. Put the key in your server's environment. It is shown once.
  3. Call /v1/me to check the connection, then the endpoints you need.
curl https://api.baselyapp.com/v1/me \
  -H "Authorization: Bearer bsk_live_..."

curl "https://api.baselyapp.com/v1/financial/summary?period=2026-09" \
  -H "Authorization: Bearer bsk_live_..."

Authentication

Send the key as Authorization: Bearer bsk_live_... on every request.

  • The business comes from the key. There is no business id in the request, and a key never reaches another business.
  • Test keys (bsk_test_) read the real business and validate writes, but never save them.
  • Only the owner of the business creates, rotates and revokes keys. A key stops working when the person who created it is no longer the owner.
  • Rotating keeps the old key working for a grace period you choose, so your server can switch without downtime.

Scopes

Each key carries only the scopes it was given. An endpoint without its scope answers 403 SCOPE_DENIED.

ScopeServiceAccess
financial:readfinancialReads
accounts:readfinancialReads
transactions:readfinancialReads
transactions:writefinancialWrites to the books
invoices:readfinancialReads
invoices:writefinancialWrites to the books
bills:readfinancialReads
bills:writefinancialWrites to the books
contacts:readcrmReads
contacts:writecrmWrites
conversations:readphoneReads
texts:sendphoneWrites
inbox:writephoneWrites
calls:createphoneWrites
sessions:managephoneWrites
otp:sendphoneWrites
phone_contacts:writephoneWrites
bank:refreshbankingComing later
email:sendemailComing later
sms:sendsmsComing later
calendar:readcalendarComing later
calendar:writecalendarComing later
files:readfilesComing later

Endpoints

Base URL below. Every response has an x-request-id header.

Platform

GET/v1/meany keyThe business and the key behind this request.

Financial

GET/v1/financial/summaryfinancial:readCash, inflows, outflows, expenses, recurring costs, burn and runway for a period.
GET/v1/financial/cash-flowfinancial:readCash flow month by month: operating, investing and financing.
GET/v1/financial/reports/{report}financial:readA financial report: pnl, balance-sheet, expenses-by-category or trial-balance.
GET/v1/accountsaccounts:readBank accounts connected to the books, with their ledger balance.
GET/v1/transactionstransactions:readBank transactions, newest first.
GET/v1/transactions/{id}transactions:readOne bank transaction.
POST/v1/transactions/{id}/categorizetransactions:writePost a bank transaction to a category in the books.
GET/v1/invoicesinvoices:readInvoices, newest first.
POST/v1/invoicesinvoices:writeCreate an invoice.
GET/v1/invoices/{id}invoices:readOne invoice, with what is still open.
POST/v1/invoices/{id}/paymentsinvoices:writeRecord a payment received for an invoice.
GET/v1/billsbills:readBills to pay, newest first.
POST/v1/billsbills:writeCreate a bill to pay.

CRM

GET/v1/contactscontacts:readContacts of the business.
POST/v1/contactscontacts:writeCreate a contact, or update the one with the same email or phone.

Phone and texts

GET/v1/businesses/{negocioId}/conversationsconversations:readMessages and calls, masked.
POST/v1/businesses/{negocioId}/textstexts:sendSend a text from the business number.
POST/v1/businesses/{negocioId}/emailsinbox:writeHand a received email to the inbox.
POST/v1/businesses/{negocioId}/callscalls:createRing a team member.
POST/v1/businesses/{negocioId}/contactsphone_contacts:writeDeclare a customer or a team member.
POST/v1/businesses/{negocioId}/sessionssessions:manageOpen a masked line.
DELETE/v1/businesses/{negocioId}/sessions/{sessionId}sessions:manageClose a masked line.
POST/v1/businesses/{negocioId}/otpotp:sendSend a one-time code by text.
POST/v1/businesses/{negocioId}/otp/verifyotp:sendVerify a one-time code.

Conventions

  • Money is in integer cents with a currency. Dates are ISO 8601.
  • Lists return object: list, data, has_more. Use limit (up to 100) and from/to to narrow them.
  • Balances come from the books, which the bank keeps in sync by itself. Reading never asks the bank directly.
  • A manual refresh from the bank (Fresh) is a separate action with its own monthly quota. Reading the API never spends one.
  • Rate limits per key per minute: 120 reads, 30 writes, 20 reports. Over the limit: 429 with retry-after.

Errors

Errors have a stable code, a message for people and the request id. Write errors add reason and field.

{ "error": { "code": "SCOPE_DENIED", "message": "...", "scope": "invoices:write", "request_id": "req_..." } }
MISSING_KEYSend your API key as Authorization: Bearer <key>.
INVALID_KEYThis API key is not valid.
KEY_REVOKEDThis API key was revoked or replaced. Use the current key of this business.
KEY_EXPIREDThis API key expired. Ask the owner of the business for a new one.
NOT_FOUNDNot found.
BUSINESS_FROZENThis business is frozen. Calls resume when it is active again.
BUSINESS_CLOSEDThis business is closed.
SCOPE_DENIEDThis key does not have the scope this endpoint needs.
MODULE_NOT_ENABLEDFinance is not enabled for this business.
BOOKS_NOT_SET_UPThe books of this business are not set up yet.
RATE_LIMITEDToo many requests for this key. Wait and try again.
INVALID_REQUESTThe request is not valid.
INVALID_JSONThe body must be a JSON object.
TEST_MODETest keys validate writes but never save them.
CONFLICTThe books do not allow this change right now. See reason.
FORBIDDENThe owner of this key can no longer do this in the business.
INTERNALSomething failed on our side. Try again; if it keeps failing, send us the request_id.

Webhooks

The owner adds the address of your system and picks the events. Deliveries retry for 24 hours.

transaction.createdtransaction.categorizedinvoice.createdinvoice.paidbill.createdbill.paidbank_connection.disconnected

Each delivery is signed with your endpoint secret in basely-signature, and carries basely-event-id so you can drop duplicates. Verify it like this:

import { createHmac, timingSafeEqual } from "node:crypto";

// header: basely-signature: t=<unix seconds>,v1=<hex>
export function isFromBasely(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=") as [string, string]));
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}