Zum Inhalt springen

Node-SDK

@flouviahq/node: die vollständige API v1 und die Webhook-Verifizierung von Ihrem Server aus, ohne Abhängigkeiten.

Offizielle SDKs und Tools

Paket Wofür Status
@flouviahq/node API v1 von Ihrem Server, Webhooks, Elements-Proxy Veröffentlicht (1.1.0)
@flouviahq/elements Angebotsrechner, Rechnung und Steuerformular im Browser Veröffentlicht (2.0.2). Siehe Cord Elements.
@flouviahq/cli Anmeldung, Simulatoren und Webhooks auf Ihrem localhost Veröffentlicht (1.2.1). Siehe CLI.
Python und PHP — Noch nicht veröffentlicht. Es gibt kein Paket auf PyPI oder Packagist.

Solange es die SDKs für Python und PHP nicht gibt, rufen Sie die REST-API direkt auf. Um Webhooks in Laravel, Django, Flask oder FastAPI zu verifizieren, legt cord init einen Signaturprüfer ohne Abhängigkeiten in Ihrem Projekt an; Sie können auch den Verifizierungsalgorithmus übernehmen.

Installation

npm install @flouviahq/node

Das SDK läuft auf Node 20 oder höher, Bun, Deno, Cloudflare Workers und Vercel. Es wird als ESM und CommonJS veröffentlicht, mit Typen.

import { Cord } from '@flouviahq/node';

const cord = new Cord(process.env.CORD_SECRET_KEY!);

Das SDK akzeptiert einen geheimen Schlüssel (sk_live_, sk_test_) oder einen eingeschränkten (rk_live_, rk_test_). Einen veröffentlichbaren Schlüssel (pk_) lehnt es ab, und es verweigert die Ausführung im Browser: Ein geheimer Schlüssel auf einer Webseite liegt offen.

Konfiguration

const cord = new Cord(process.env.CORD_SECRET_KEY!, {
  apiVersion: '2026-10-01',
  timeoutMs: 15000,
  maxRetries: 3,
  appInfo: { name: 'my-erp', version: '4.2.0' },
});
Option Standard Was sie tut
apiVersion 2026-10-01 (CORD_API_VERSION) Wird im Header Cord-Version jeder Anfrage gesendet. Siehe Versionen.
baseUrl https://cordhq.app Origin von Cord. Das SDK hängt /api/v1 an.
timeoutMs 30000 Bricht jeden Versuch ab.
maxRetries 2 (maximal 5) Wiederholungen bei Netzwerkausfall, 408, 425, 429, 5xx und 409 idempotency_in_progress.
fetch globales fetch Fügen Sie Ihr eigenes fetch ein (Tests, Proxys).
appInfo — Ergänzt Ihre App im User-Agent (cord-node/1.1.0 my-erp/4.2.0). Hilfreich bei Supportanfragen.

cord.mode ist je nach Präfix des Schlüssels 'test' oder 'live'.

Ressourcen und Methoden

Jede Methode entspricht einer Route der API v1 und behält die Feldnamen des Servers bei. Methoden, die ein Objekt lesen oder schreiben, geben das data der Antwort zurück; die list()-Methoden geben { data, meta } zurück.

Ressource Methoden Route
cord.me() — GET /me
cord.quotes list, listAll, retrieve, create, send, resend, approve, reject, markPaid, del, draftFromText /cotizaciones, /cotizaciones/{id}, /cotizaciones/ia
cord.clients list, listAll, retrieve, create, update /clientes, /clientes/{id}
cord.products list, listAll, create /productos
cord.invoices list, listAll, retrieve, create, finalize, send, void, recordPayment, creditNote /facturas, /facturas/{id}
cord.collections retrieve GET /cobranza (ab dem Professional-Tarif)
cord.events list, listAll GET /events
cord.tasks create POST /tareas
cord.webhookEndpoints list, create, del /webhooks, /webhooks/{id}
cord.elements config GET /elements/config
cord.testHelpers fiscal.setNextOutcome, quotes.view, quotes.expire, webhooks.trigger /test_helpers/* (nur sk_test_)
cord.webhooks constructEvent, signPayload Lokale Verifizierung, ohne Netzwerk
const quote = await cord.quotes.create({
  cliente: { empresa: 'Acme', email: 'purchasing@acme.com' },
  items: [{ descripcion: 'Installation', cantidad: 1, precio_unitario: 12500 }],
  base_currency: 'MXN',
  send: true,
});
// quote: { id, token, folio, link_publico, needs_approval, motivo, email }

await cord.quotes.markPaid(quote.id, { payment_method: 'transferencia' });

const factura = await cord.invoices.create({
  cliente_id: '0e6f2b8a-91c4-4d7e-b3a5-6c2d8f1e4a90',
  items: [{ descripcion: 'Monthly maintenance', cantidad: 1, precio_unitario: 4500 }],
  currency: 'MXN',
  due_date: '2026-11-06',
});
await cord.invoices.finalize(factura.id); // stellt die Rechnung aus: unumkehrbar
await cord.invoices.recordPayment(factura.id, { monto: 5220, moneda: 'MXN', metodo: 'transferencia' });

quotes.del() löscht nur Entwürfe. invoices.create() erstellt einen Entwurf; finalize() stellt ihn aus. recordPayment() verlangt monto (Betrag) und moneda (Währung) gemeinsam.

Listen und automatische Paginierung

const page = await cord.quotes.list({ status: 'sent', limit: 50 });
console.log(page.meta.total, page.meta.next_cursor);

for await (const invoice of cord.invoices.listAll({ estado: 'open' })) {
  console.log(invoice.numero, invoice.saldo, invoice.moneda);
}

listAll() fordert Seiten mit 200 Einträgen an und folgt next_cursor, bis null zurückkommt: Es werden keine Datensätze übersprungen oder wiederholt, auch wenn während des Durchlaufs neue angelegt werden. Rechnungen und Ereignisse werden nur per Cursor paginiert.

Wenn Sie noch 1.0.0 verwenden

In 1.0.0 durchlaufen quotes.listAll(), clients.listAll() und products.listAll() die Daten per offset, und list() dieser drei Ressourcen akzeptiert keinen cursor. Seit 1.1.0 laufen sie per Cursor, und clients.list() akzeptiert q und email. Aktualisieren Sie mit npm install @flouviahq/node@latest.

Idempotenz und Wiederholungen

Jede schreibende Operation (POST, PATCH, DELETE) trägt einen Idempotency-Key. Wenn Sie keinen übergeben, erzeugt das SDK eine UUID pro Aufruf und wiederholt sie bei jeder Wiederholung dieses Aufrufs: Ein Netzwerkabbruch erzeugt nie zwei Angebote und erfasst nie zwei Zahlungen. So binden Sie die Operation an eine eigene Kennung, die einen Neustart Ihres Prozesses übersteht:

await cord.invoices.recordPayment(
  facturaId,
  { monto: 5000, moneda: 'MXN', referencia: 'SPEI-88213' },
  { idempotencyKey: `payment-${paymentId}` },
);

Die Optionen pro Aufruf sind idempotencyKey, timeoutMs und signal (ein AbortSignal). Wiederholungen warten so lange, wie Retry-After angibt (bis zu 60 s), oder nutzen einen exponentiellen Backoff mit Jitter.

Typisierte Fehler

Jede Fehlerantwort wird zu einem CordError:

Eigenschaft Inhalt
status HTTP-Statuscode. 0, wenn keine Antwort kam (Netzwerkausfall oder Timeout).
code Der stabile code der API (invalid_request, insufficient_permissions, invalid_state…). network_error, wenn keine Antwort kam.
message Die Meldung aus dem Feld error.
requestId Die Cord-Request-Id (req_…). Suchen Sie sie unter Entwickler › Protokolle.
docUrl Link zur Erklärung des Codes.
raw Der vollständige Body, mit zusätzlichen Feldern wie limit oder plan_required.
import { Cord, CordError } from '@flouviahq/node';

try {
  await cord.quotes.approve(quoteId);
} catch (err) {
  if (err instanceof CordError && err.code === 'invalid_state') {
    // Das Angebot war weder gesendet noch angesehen: Es wurde nichts angewendet.
  } else {
    throw err;
  }
}

Die vollständige Liste der Codes finden Sie unter Fehlerbehandlung.

Webhooks: constructEvent

import { constructEvent, CordWebhookSignatureError } from '@flouviahq/node';

export async function POST(req: Request) {
  let event;
  try {
    event = await constructEvent(await req.text(), req.headers, process.env.CORD_WEBHOOK_SECRET!);
  } catch (err) {
    if (err instanceof CordWebhookSignatureError) return new Response(err.code, { status: 400 });
    throw err;
  }
  switch (event.event) {
    case 'invoice.paid':
      console.log(event.data.numero, event.data.saldo, event.data.moneda);
      break;
    case 'payment.partial':
      console.log(event.data.tipo, event.data.monto, event.data.saldo_pendiente);
      break;
  }
  return new Response('ok');
}

constructEvent(payload, headers, secret, options?) verwendet WebCrypto und ist daher in jeder Umgebung dieselbe Funktion:

  • payload ist der rohe Body (string, Uint8Array oder ArrayBuffer). Ein bereits geparstes und wieder serialisiertes Objekt passt nicht zur Signatur.
  • headers akzeptiert ein Headers-Objekt oder ein einfaches Objekt wie req.headers in Node.
  • Die Funktion verlangt X-Cord-Signature-V1 und lehnt eine Zustellung mit mehr als 300 Sekunden Abweichung ab (tolerance, in Sekunden). Während einer Secret-Rotation kommen zwei Signaturen an: Es genügt, wenn eine passt.
  • Die Legacy-Signatur X-Cord-Signature ohne Zeitstempel wird nur mit { allowLegacySignature: true } akzeptiert.
  • Sie gibt CordWebhookEvent zurück, eine nach event unterschiedene Union mit dem exakten data der 41 Ereignisse und von ping.
CordWebhookSignatureError.code Ursache
missing_signature Es kam keine Signatur an, oder das Secret fehlt.
invalid_signature_header Der Header hat nicht die Form t=<unix>,v1=<hex>.
timestamp_out_of_tolerance Die Signatur ist älter (oder neuer) als die Toleranz: möglicher Replay.
signature_mismatch Die Signatur passt nicht zu diesem Secret.
legacy_signature_rejected Es kam nur die Legacy-Signatur an, und Sie haben sie nicht akzeptiert.
invalid_payload Der Body ist kein JSON der Form { id, event, created_at, data }.

signPayload(body, secret, timestamp?) signiert einen Body so, wie Cord es tut, für Ihre Tests:

import { signPayload, constructEvent } from '@flouviahq/node';

const body = JSON.stringify({ id: 'evt_test', event: 'quote.sent', created_at: new Date().toISOString(), data: {} });
const header = await signPayload(body, 'whsec_test');
const event = await constructEvent(body, { 'x-cord-signature-v1': header }, 'whsec_test');

Außerdem exportiert das SDK WEBHOOK_EVENT_TYPES, WEBHOOK_EVENT_OBJECTS und isWebhookEventType() mit dem Ereigniskatalog.

Testmodus

Mit einem sk_test_-Schlüssel löst cord.testHelpers aus, was in der Produktion ein Dritter tut, über denselben Code wie in der Produktion. Mit einem Live-Schlüssel wirft es einen Fehler, bevor Cord aufgerufen wird.

await cord.testHelpers.fiscal.setNextOutcome('pac_caido');
await cord.testHelpers.quotes.view(quoteId);
await cord.testHelpers.quotes.expire(quoteId);
await cord.testHelpers.webhooks.trigger('invoice.paid');

Um die Webhooks auf Ihrem Rechner zu empfangen, verwenden Sie cord listen aus der CLI.

Weitere Hilfsfunktionen

  • createElementsProxy(): das Backend für den proxyUrl-Modus von Cord Elements. Siehe Server-Proxy.
  • validateFiscalReceptor(), validRfc(), validSpainTaxId() und validEin(): dieselbe steuerliche Validierung, die Cord beim Speichern anwendet, damit Ihr Server vor dem API-Aufruf validieren kann. Siehe Steuerformular.
  • newIdempotencyKey() und CORD_API_VERSION.

Änderungen nach Version

Der vollständige Verlauf steht in der CHANGELOG.md des Pakets. Eine veröffentlichte Version wird nie überschrieben: Jede Änderung erhöht die Versionsnummer.