Basely

Basely API

Referencia de la API

Una clave por negocio. Lee los libros, las cuentas del banco, las transacciones, las facturas y las cuentas por pagar de un negocio, escribe en ellos con el permiso del dueño y recibe webhooks firmados cuando algo cambia.

Inicio rápido

  1. El dueño del negocio abre API en el panel de Basely y crea una clave con los scopes que tu sistema necesita.
  2. Guarda la clave en el entorno de tu servidor. Se muestra una sola vez.
  3. Llama a /v1/me para probar la conexión y después a los endpoints que necesites.
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_..."

Autenticación

Envía la clave como Authorization: Bearer bsk_live_... en cada solicitud.

  • El negocio sale de la clave. No hay id de negocio en la solicitud, y una clave nunca alcanza otro negocio.
  • Las claves de prueba (bsk_test_) leen el negocio real y validan escrituras, pero nunca guardan.
  • Solo el dueño del negocio crea, rota y revoca claves. La clave deja de funcionar cuando quien la creó ya no es el dueño.
  • Rotar mantiene la clave anterior funcionando durante el plazo que elijas, para que tu servidor cambie sin caerse.

Scopes

Cada clave lleva solo los scopes que recibió. Un endpoint sin su scope responde 403 SCOPE_DENIED.

ScopeServicioAcceso
financial:readfinancialLee
accounts:readfinancialLee
transactions:readfinancialLee
transactions:writefinancialEscribe en los libros
invoices:readfinancialLee
invoices:writefinancialEscribe en los libros
bills:readfinancialLee
bills:writefinancialEscribe en los libros
contacts:readcrmLee
contacts:writecrmEscribe
conversations:readphoneLee
texts:sendphoneEscribe
inbox:writephoneEscribe
calls:createphoneEscribe
sessions:managephoneEscribe
otp:sendphoneEscribe
phone_contacts:writephoneEscribe
bank:refreshbankingPróximamente
email:sendemailPróximamente
sms:sendsmsPróximamente
calendar:readcalendarPróximamente
calendar:writecalendarPróximamente
files:readfilesPróximamente

Endpoints

URL base abajo. Toda respuesta tiene el encabezado x-request-id.

Plataforma

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

Finanzas

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.

Teléfono y textos

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.

Convenciones

  • El dinero va en centavos enteros con la moneda. Las fechas en ISO 8601.
  • Las listas devuelven object: list, data, has_more. Usa limit (hasta 100) y from/to para filtrar.
  • Los saldos vienen de los libros, que el banco mantiene al día solo. Leer nunca le pregunta al banco directo.
  • Actualizar ahora en el banco (Fresh) es una acción aparte, con su propia cuota mensual. Leer la API nunca gasta Fresh.
  • Límite por clave por minuto: 120 lecturas, 30 escrituras, 20 reportes. Al pasarlo: 429 con retry-after.

Errores

El error tiene un código estable, un mensaje para personas y el id de la solicitud. Los errores de escritura agregan reason y 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

El dueño registra la dirección de tu sistema y elige los eventos. La entrega reintenta durante 24 horas.

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

Cada entrega va firmada con el secreto de la dirección en basely-signature y trae basely-event-id para descartar repeticiones. Verifícalo así:

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 ?? ""));
}