Skip to content

Server proxy

The browser talks to your backend and your backend talks to Cord: no key ever reaches the page.

Cord Elements works in two ways: with a publishable key (pk_) straight to Cord, or with proxyUrl, where your own backend serves the requests and calls Cord with a server key. Use the proxy when you want the Builder to read your client directory, email the quote or set negotiated prices: things a pk_ key can never do.

The proxy lives in @flouviahq/node, the server SDK. That package replaces the old @flouviahq/elements/server, which still works but no longer receives changes.

Mount it

npm install @flouviahq/node
// app/api/cord/[...path]/route.ts (Next.js App Router)
import { createElementsProxy } from '@flouviahq/node';

const proxy = createElementsProxy({
  secretKey: process.env.CORD_SECRET_KEY!,
  authorizeClients: async (req) => Boolean(await getSession(req)),
});

export const GET = proxy;
export const POST = proxy;
<CordProvider proxyUrl="/api/cord">
  <CordBuilder />
</CordProvider>

getSession is your own session function. createElementsProxy returns a (Request) => Promise<Response> handler, so it works the same in Next.js, Astro, Remix, Hono, Bun or Cloudflare Workers. cord init creates it for you in a Next.js project.

What it serves

Browser request Calls Cord Condition
GET {proxyUrl}/elements/config GET /api/v1/elements/config Always.
GET {proxyUrl}/productos GET /api/v1/productos Always.
POST {proxyUrl}/cotizaciones POST /api/v1/cotizaciones Always, with the body sanitized.
POST {proxyUrl}/elements/ai-draft POST /api/v1/elements/ai-draft Always. The AI stream is forwarded as is; files up to 4 MB.
GET {proxyUrl}/clientes GET /api/v1/clientes?limit=200 Only if authorizeClients returns true. Otherwise 403 with code: "clients_require_proxy".

Any other route or method returns 404. The proxy doesn’t retry: the browser already retries on its own.

Options

Option Detail
secretKey Required. An sk_ or rk_ key.
allowedOrigins Origins allowed to use the proxy. By default, only the request’s own origin: a proxy open to any site is the same as publishing your key.
authorizeClients(request) Authorizes reading the client directory. Without it, it’s never read.
authorizeSellerFields(request) Authorizes seller fields on creation: cliente_id, send (email it), fiscal_currency, and on each line precio_negociado and costo_unitario. Without it, they’re dropped and the quote is created as a draft, just like with a pk_ key.
baseUrl Cord’s origin. Default https://cordhq.app.
fetch For tests.
const proxy = createElementsProxy({
  secretKey: process.env.CORD_PROXY_KEY!,
  allowedOrigins: ['https://portal.yourcompany.com'],
  authorizeClients: async (req) => (await getSession(req))?.role === 'seller',
  authorizeSellerFields: async (req) => (await getSession(req))?.role === 'seller',
});

The proxy forwards the browser’s Idempotency-Key (the Builder sends one per submission attempt), and returns Cord’s errors with their code, request_id and doc_url.

Give the proxy a restricted key

The proxy doesn’t need a Secret Key with every permission. Create a restricted key (rk_) in Developers › API keys with only this:

Resource Level What for
elements write Configuration and AI line filling
productos read The Builder’s catalog
cotizaciones write Creating the quote
clientes read Only if you use authorizeClients
"New LIVE API key" dialog with the Restricted (Backend) type selected and the Elements proxy permissions per resource: Quotes and Elements with write access, Products and Customers with read access, everything else with no access; the MCP read and draft writing tools, without actions with external effects; plus one allowed IP and a 90-day expiration.

If your server is compromised, that key can’t read invoices, collections or webhooks. You can also pin your server’s IPs and set an expiration. See Authentication.

Debugging

Every call from the proxy to Cord shows up in the Workbench’s Developers › Logs with its request_id, method, route, status, duration and IP. The same request_id reaches the browser in the Cord-Request-Id header and in an error’s body.