Skip to content

SDKs

The official SDKs wrap the Flashpi API with types generated from the OpenAPI document, safe retries with an Idempotency-Key, typed errors (RFC 9457), pagination and webhook signature verification. There are seven: @flashpi/node for Node.js and TypeScript, flashpi for Python, flashpi/flashpi-php for PHP, ec.com.flashpi:flashpi-java for Java and Kotlin, Flashpi for C# and .NET, github.com/flashpi1/flashpi-go for Go and flashpi for Ruby. You don’t need one: the guides use plain HTTP, which works from any language, desktop systems in Visual FoxPro and Delphi included. In a language without an SDK, verify webhooks with any Standard Webhooks library or by hand (see Webhooks).

Each section shows the seven SDKs in tabs. Pick your language in any of them and the whole page follows it.

@flashpi/node is the official SDK for Node.js and TypeScript. Its source is on GitHub, under the MIT license.

  • It covers every operation of the API: the six documents you can issue, reading documents (XML, RIDE and email), issuers (including creating them from their .p12) with their establishments, emission points and certificates, webhook endpoints, events and the API status.
  • It runs on Node.js 22 or later, Bun, Deno and Cloudflare Workers, with import or require, and has no dependencies.
  • It runs on the server only: your API key is secret, so the SDK refuses to run in a browser.
Terminal window
npm install @flashpi/node

Or with pnpm add @flashpi/node, yarn add @flashpi/node or bun add @flashpi/node.

Create a test API key in the console with access to every issuer (scope account) and read and write access, and keep it in the FLASHPI_API_KEY environment variable (see Authentication and API keys). With the demo issuer you issue in the SRI test environment without a certificate of your own: copy its ID from the console’s Overview (see Sandbox and demo issuer).

import { Flashpi } from '@flashpi/node'
// Reads FLASHPI_API_KEY. You can also pass the key: new Flashpi('fp_test_...')
const flashpi = new Flashpi()
const invoice = await flashpi.invoices.create({
issuerId: '<demo issuer id>',
establishment: '001',
emissionPoint: '001',
customer: {
idType: 'ruc',
id: '1790000000001',
name: 'Supermercados La Pradera S.A.',
},
items: [
{
code: 'SRV-001',
description: 'Monthly accounting services',
quantity: '1',
unitPrice: '450.00',
taxes: [{ type: 'vat', rate: '15' }],
},
],
payments: [{ method: 'other_financial_system', amount: '517.50' }],
options: { waitForAuthorization: true },
})
console.log(invoice.status, invoice.accessKey)

Cloudflare Workers have no global environment variables: pass the key from env, with new Flashpi(env.FLASHPI_API_KEY). On Deno, reading the variable needs the --allow-env=FLASHPI_API_KEY permission.

Amounts and quantities are strings ('450.00'), never numbers, so no decimals are lost. With options.waitForAuthorization, the request waits for the SRI’s answer and returns authorized, rejected or failed; if the SRI takes longer, it returns processing or pending_sri and the document carries on. Without it, the answer comes back at once with the status processing and the SRI’s decision arrives by webhook (invoice.authorized or invoice.rejected). A failed document raises no event yet: look for it with flashpi.documents.list({ status: 'failed' }). See Sync and async modes and Document lifecycle.

The other documents follow the same pattern, each with its create(body): creditNotes, debitNotes, purchaseSettlements, waybills and withholdings.

Each API error is thrown as an APIError, as the subclass that matches its HTTP status when there is one (such as UnprocessableEntityError for 422). Branch on code, which is stable and in English. The message follows the language you ask for: new Flashpi({ language: 'es' }) gives the messages in Spanish.

import { APIError, UnprocessableEntityError } from '@flashpi/node'
try {
await flashpi.invoices.create(body)
} catch (error) {
if (error instanceof UnprocessableEntityError) {
console.log(error.code) // 'validation_failed', 'total_mismatch', ...
for (const field of error.errors) console.log(field.field, field.message)
} else if (error instanceof APIError) {
console.log(error.status, error.code, error.requestId) // share requestId with support
}
throw error
}

See the codes and what to do about each one in Errors.

Each SDK retries up to twice, waiting longer each time, on network errors, timeouts and 408, 429 and 5xx answers, except 501 and when the API asks to wait more than 60 seconds (as daily limits do). It does not retry 503 demo_issuer_unavailable either: a retry seconds later does not fix it.

Every document it issues carries an Idempotency-Key that its retries repeat, so a retry never issues twice: if the first attempt already created the document, the SDK returns that document.

To use your own key, such as the ID of your order, pass it in the call’s options:

await flashpi.invoices.create(body, { idempotencyKey: `order-${order.id}` })

See Idempotency.

A list returns one page.

With for await you walk through every page, which the SDK requests as you go:

for await (const document of flashpi.documents.list({ status: 'authorized' })) {
console.log(document.accessKey)
}

See Pagination.

unwrapWebhook verifies the signature against the body exactly as it arrived, unparsed, and the endpoint’s whsec_ secret, and returns the event with its type. It needs no API key. It works in Next.js, Hono, Cloudflare Workers and anywhere with Request; with Express, read the body with express.raw({ type: 'application/json' }).

import { unwrapWebhook, WebhookVerificationError } from '@flashpi/node'
export async function POST(request: Request): Promise<Response> {
let event
try {
event = await unwrapWebhook(
await request.text(),
request.headers,
process.env.FLASHPI_WEBHOOK_SECRET ?? '',
)
} catch (error) {
if (error instanceof WebhookVerificationError) return new Response(null, { status: 400 })
throw error
}
switch (event.type) {
case 'webhook_endpoint.verification':
// The endpoint's activation: answer the challenge.
return Response.json({ challenge: event.data.challenge })
case 'invoice.authorized':
console.log('Authorized', event.data.accessKey) // event.data is the document
break
}
return new Response(null, { status: 204 })
}

Store event.id (the same value as the webhook-id header) and, when it repeats, answer 2xx without doing the work again: an event can arrive more than once.

The example assumes the endpoint’s default full mode. In thin mode, events other than the verification come without data: use unwrapWebhook<ThinWebhookEvent>(...) (the type is also imported from @flashpi/node) and read the event with flashpi.events.get(event.id). See Webhooks and its Payloads.

The SDK’s README documents the rest: reading documents (XML, the RIDE as a PDF and resending the email), creating issuers from their .p12 and their certificates, the options of each call and how to read the full HTTP response.

  • A Laravel package, separate from the PHP SDK. Until then, flashpi/flashpi-php works in Laravel as it is: this page’s PHP tab shows how to register it in a service provider and how to receive webhooks in a controller.