New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@timbrix/sdk

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@timbrix/sdk

TypeScript SDK for Timbrix API

latest
npmnpm
Version
2.5.1
Version published
Maintainers
1
Created
Source

@timbrix/sdk

TypeScript/JavaScript client for the Timbrix API — CFDI 4.0 electronic invoicing for Mexico. Fully typed (no any), works in Node 18+ and the browser, and normalizes every failed request into one typed error class.

Installation

npm install @timbrix/sdk
# or
pnpm add @timbrix/sdk
# or
yarn add @timbrix/sdk

Quickstart (< 5 minutes)

Sign up at app.timbrix.mx/register — no credit card required. Timbrix generates a sandbox API key automatically for your organization; copy it from Settings → API Keys. CFDI stamped with a sandbox key have no real fiscal validity (they use Timbrix's test PAC), so you can experiment freely.

import { Timbrix } from "@timbrix/sdk"

const client = new Timbrix({ apiKey: "sk_test_..." })

// The organization is resolved from the key itself — no organizationId needed.
const customer = await client.customers.create({
  legalName: "Cliente de Prueba SA de CV",
  taxId: "DUM901231AB3",
  taxSystem: "601",
  email: "facturas@cliente-prueba.mx",
  defaultInvoiceUse: "G01",
  addressStreet: "Blvd. Atardecer",
  addressExterior: "142",
  addressNeighborhood: "Centro",
  addressCity: "Huatabampo",
  addressMunicipality: "Huatabampo",
  addressZip: "86500",
  addressState: "Sonora",
})

const product = await client.products.create({
  description: "Servicio de prueba",
  productKey: 84111506,
  unitKey: "E48",
  price: 100.0,
})

const invoice = await client.invoices.create({
  type: "I",
  series: "A",
  folioNumber: "1",
  date: new Date().toISOString(),
  paymentForm: "01",
  paymentMethod: "PUE",
  use: "G03",
  customerId: customer.id,
  items: [{ productId: product.id, quantity: 1, amount: 100.0 }],
})

console.log(invoice.uuid) // the folio fiscal assigned by the SAT via the PAC

Move to production by swapping the sandbox key for a live one (sk_live_...) — the base URL, request shapes, and response shapes are identical in both environments.

Authentication

The SDK supports two auth strategies, mutually exclusive (bearerToken takes precedence if both are set):

import { Timbrix } from "@timbrix/sdk"

// API key — server-to-server integrations. Sent as X-API-Key.
// The organization is resolved from the key, so most resource methods
// accept an optional organizationId instead of a required one.
const client = new Timbrix({ apiKey: "sk_live_..." })

// Bearer token — acting as a logged-in Timbrix user (e.g. from OAuth).
// Sent as Authorization: Bearer <token>. organizationId is required on
// resource methods that need it.
const client = new Timbrix({ bearerToken: "eyJhbGciOi..." })

baseUrl defaults to http://localhost:3001 (for local development against the API); pass baseUrl: "https://api.timbrix.mx" for sandbox or production traffic — same URL for both, since the environment is determined by which API key you use.

Swap credentials on an existing client (e.g. after a token refresh in a CLI) with client.setAuth(bearerToken?, apiKey?).

Sandbox vs. production

There's no separate sandbox base URL — sk_test_... (or any key created while livemode is off) and sk_live_... keys hit the same https://api.timbrix.mx, and the API scopes every request to the environment the key belongs to. When authenticating with a bearer token instead, pass environment: "sandbox" | "production" on the methods that accept it (invoices.list, invoices.reportSummary, invoices.exportCsv, etc.) to filter explicitly — omitting it defaults to "production" for those report endpoints, since sandbox timbres carry no fiscal weight.

API Reference

Invoices (CFDI 4.0)

// Stamp (timbrar) a new CFDI — type: "I" (Ingreso, default), "E" (Egreso), or "T" (Traslado)
const invoice = await client.invoices.create({
  series: "A",
  folioNumber: "1",
  date: new Date().toISOString(),
  paymentForm: "01",
  use: "G03",
  customerId: "customer-id",
  items: [{ productId: "product-id", quantity: 1, amount: 100 }],
})

// Preview a PDF without stamping (no quota consumed, nothing persisted)
const pdfPreview = await client.invoices.previewPdf({ ...sameShapeAsCreate })

// List, paginated and filterable
const { data, total } = await client.invoices.list({
  page: 1,
  limit: 20,
  status: "vigente",
  dateFrom: "2026-01-01",
  dateTo: "2026-01-31",
})

// Get / download
const one = await client.invoices.get("folio-fiscal-uuid")
const xml = await client.invoices.getXml("folio-fiscal-uuid") // Blob
const pdf = await client.invoices.getPdf("folio-fiscal-uuid") // Blob

// Cancel per SAT rules (may return cancellationStatus: "pendiente" if the
// receptor's approval is required — Timbrix can't confirm that in real time)
await client.invoices.cancel("folio-fiscal-uuid", { motivo: "02" })

// Email the CFDI (XML + PDF) to the receptor
await client.invoices.sendEmail("folio-fiscal-uuid", {
  to: "cliente@example.com",
})

// Current-month CFDI usage against the plan's quota
const usage = await client.invoices.usage()

// Fiscal report summary + CSV/XML/PDF bulk export (capped at 500 invoices/request)
const summary = await client.invoices.reportSummary({ dateFrom: "2026-01-01" })
const csv = await client.invoices.exportCsv({ status: "vigente" }) // Blob
const xmlZip = await client.invoices.exportXmlZip() // Blob
const pdfZip = await client.invoices.exportPdfZip() // Blob

Customers

await client.customers.list()
await client.customers.get("customer-id")
await client.customers.create({
  legalName: "Acme SA de CV",
  taxId: "XAXX010101000",
  taxSystem: "601",
  email: "facturas@acme.mx",
  defaultInvoiceUse: "G03",
  addressStreet: "Reforma",
  addressExterior: "1",
  addressNeighborhood: "Centro",
  addressCity: "CDMX",
  addressMunicipality: "Cuauhtemoc",
  addressZip: "06000",
  addressState: "CDMX",
})
await client.customers.update("customer-id", { legalName: "New name" })
await client.customers.delete("customer-id")

Products

await client.products.list({ description: "widget" }) // filters are optional
await client.products.get("product-id")
await client.products.create({
  description: "Widget",
  productKey: 84111506, // SAT c_ClaveProdServ code
  price: 100,
})
await client.products.update("product-id", { price: 150 })
await client.products.delete("product-id")

// Bulk import from a CSV file (Blob/File) — partial success, one error per bad row
const result = await client.products.importCsv(csvBlob, "products.csv")

SAT catalogs

await client.sat.regimenes({ tipoPersona: "fisica" })
await client.sat.usosCfdi({ tipoPersona: "moral", regimen: "601" })
await client.sat.searchClavesProdServ({ q: "arroz", limit: 10 })
await client.sat.searchClavesUnidad({ q: "kilo" })

Organizations

await client.organizations.create({ name: "Acme Corp", slug: "acme-corp" })
await client.organizations.get("acme-corp")
await client.organizations.update("acme-corp", { name: "New Name" })
await client.organizations.updateLegalData("acme-corp", {
  legal_name: "Acme SA de CV",
})
await client.organizations.delete("acme-corp")

// Members & invites
await client.organizations.members("acme-corp")
await client.organizations.invite("acme-corp", {
  email: "a@b.com",
  role: "member",
})
await client.organizations.removeMember("acme-corp", "member-id")
await client.organizations.updateMemberRole("acme-corp", "member-id", {
  role: "admin",
})
await client.organizations.invites("acme-corp")
await client.organizations.cancelInvite("acme-corp", "invite-id")

Webhooks

await client.webhooks.list("organization-id")
await client.webhooks.get("organization-id", "webhook-id")
await client.webhooks.create("organization-id", {
  url: "https://example.com/webhooks",
  events: ["invoice.stamped", "invoice.cancelled"],
})
await client.webhooks.update("organization-id", "webhook-id", {
  isActive: false,
})
await client.webhooks.delete("organization-id", "webhook-id")
await client.webhooks.deliveries("organization-id", "webhook-id")
await client.webhooks.test("organization-id", "webhook-id")

API Keys

await client.apiKeys.list("organization-id")
await client.apiKeys.get("organization-id", "key-id")
const created = await client.apiKeys.create("organization-id", {
  name: "Production Key",
})
console.log(created.plainKey) // only returned once, at creation
await client.apiKeys.update("organization-id", "key-id", { name: "Renamed" })
await client.apiKeys.delete("organization-id", "key-id")
await client.apiKeys.stats("organization-id")
await client.apiKeys.validate() // validates the currently-authenticated key

OAuth apps & tokens

const app = await client.oauth.createApp({
  name: "My App",
  scopes: ["read:invoices"],
})
await client.oauth.getApp(app.clientId)
await client.oauth.listApps("organization-id")
await client.oauth.updateApp(app.clientId, { name: "Renamed" })
await client.oauth.deleteApp(app.clientId)
await client.oauth.listTokens(app.clientId)
await client.oauth.revokeToken("token-id")

// Client credentials flow
const token = await client.oauth.generateToken({
  clientId: app.clientId,
  clientSecret: "...",
  scopes: ["read:invoices"],
})

// Authorization code flow
await client.oauth.exchangeCode({
  code: "auth-code",
  clientId: app.clientId,
  clientSecret: "...",
  redirectUri: "https://example.com/callback",
})
await client.oauth.refreshToken({ refreshToken: token.refresh_token! })

Auth & Users

const { access_token } = await client.auth.login({
  email: "a@b.com",
  password: "...",
})
await client.auth.logout()

await client.users.me()
await client.users.getById("user-id")
await client.users.getByEmail("a@b.com")
await client.users.getOrganizations("user-id")

Error handling

Every failed request rejects with a TimbrixApiError — never ky's generic HTTPError. It carries the API's own message (validation arrays are joined into one string), the domain error code, an optional suggestion, and the HTTP status:

import { Timbrix, TimbrixApiError } from "@timbrix/sdk"

const client = new Timbrix({ apiKey: "sk_live_..." })

try {
  await client.organizations.get("non-existent")
} catch (error) {
  if (error instanceof TimbrixApiError) {
    console.error(error.statusCode) // 404
    console.error(error.code) // "NOT_FOUND"
    console.error(error.message) // "Organization not found"
    console.error(error.suggestion) // e.g. "Check the organization id" (when the API provides one)
  }
}

Rate-limit errors (statusCode === 429) get a (retry after Ns) hint appended to message automatically when the API sends a Retry-After header.

TypeScript support

Every request/response shape is exported, so you rarely need to write your own types:

import type {
  CreateInvoiceInput,
  Invoice,
  Customer,
  Product,
  Webhook,
  WebhookEvent,
  ApiKey,
  DomainErrorCode,
} from "@timbrix/sdk"

License

MIT

Keywords

timbrix

FAQs

Package last updated on 02 Oct 2026

Related posts