API routes
Learn about HTTP endpoints under your extension's namespace.
API routes let external services (or your own UI) talk to your extension.
File location
Files at src/api/<path>.ts auto-register as HTTP handlers at /<path> — mounted under your extension’s namespace. The HTTP verb is the export name (GET, POST, PUT, PATCH, DELETE).
| File | URL |
|---|---|
src/api/tax-summary.ts | /tax-summary |
src/api/sync/customers.ts | /sync/customers |
src/api/webhooks/stripe.ts | /webhooks/stripe |
A minimal route
// src/api/tax-summary.ts
declare const TaxRecord: any;
export const GET = async function (ctx: {
searchParams: Record<string, string>;
}) {
const { start, end } = ctx.searchParams;
if (!start || !end) {
return {
__api_response: true,
status: 400,
body: { error: "Missing required query parameters: start, end (YYYY-MM-DD)" },
};
}
const records = TaxRecord.query({
where: { _created_at: { gte: start, lte: end } },
});
return { period: { start, end }, records };
};
The handler signature
The runtime injects a context with the parsed request:
{
searchParams: Record<string, string | string[]>;
params: Record<string, string | string[]>; // path parameters where supported
body: any;
rawBody: string;
headers: Record<string, string>;
context: { tenant: { id: string }, extension: { key: string } };
}
Returning responses
Plain object → 200 JSON
return { ok: true, count: 42 };
Explicit response object
return {
__api_response: true,
status: 400,
body: { error: "Missing param" },
};
The api helper
The same shape, written more naturally:
import { api } from "@backfill-io/sdk";
export const POST = api(async (request) => {
if (!request.body?.email) {
return api.badRequest("Email required");
}
return api.json({ ok: true });
});
api exposes:
api(handler) // wraps a handler with type inference
api.json(data, { status }) // 200 (or status) JSON
api.badRequest(message, data?) // 400
api.notFound(message) // 404
api.forbidden(message) // 403
api.unauthorized(message) // 401
api.error(message, { status }) // generic
Auth modes
Routes declare an auth mode via an exported config:
export const config = { auth: "api_key" };
For plain extensions, two modes are accepted:
| Mode | Effect |
|---|---|
api_key | Default. The platform requires a workspace API token in Authorization: Bearer <token>. |
public | No auth. The handler is responsible for any verification (use sparingly). |
A third mode (stripe_signature, etc.) is used by connector webhook routes, gated to connectors.
Method exports
A single route file can declare multiple verbs:
export const config = { auth: "api_key" };
export const GET = async (ctx) => { ... };
export const POST = api(async (req) => { ... });
At least one of GET, POST, PUT, PATCH, DELETE must be exported, or deploy fails:
route must export at least one HTTP method handler (e.g. export const POST = api(...))