Autumn Integration
Autumn fits apps that want product-led billing without building every plan, entitlement, and usage event by hand.
Install from the CLI
farm add integration autumn --uiConfig-first setup
import { autumn } from "@farm.js/autumn";
export const integrations = {
billing: autumn({
secretKey: process.env.AUTUMN_SECRET_KEY,
billing: {
resolveOwner(ctx) {
const userId = ctx.req.get<string>("user.id");
return userId ? { id: userId, kind: "user" } : null;
},
},
}),
};AUTUMN_WEBHOOK_SECRET is read from the environment when webhook routes are configured. Use the
endpoint's signing secret exactly as Autumn provides it, including the whsec_ prefix. Autumn
delivers webhooks through Svix, and the prefix tells Farm to decode the key the way Svix signed it.
Choose SDK ownership
Let Farm construct Autumn
The config-first example is the default path. When instance is omitted, Farm creates the Autumn
SDK from secretKey (or AUTUMN_SECRET_KEY) and the optional serverURL option.
Provide an application-owned instance
import { Autumn } from "autumn-js";
import { autumn } from "@farm.js/autumn";
const autumnClient = new Autumn({
secretKey: process.env.AUTUMN_SECRET_KEY,
});
export const billing = autumn({
instance: autumnClient,
billing: {
resolveOwner: () => null,
},
});The instance wins if a secret key is also supplied. Billing, webhook, route, product, and meter settings remain integration options in either mode.
Usage
const checkout = await api.billing.checkout.post({
body: {
productId: "pro",
successPath: "/dashboard",
},
});The Autumn customer always comes from billing.resolveOwner; a request body cannot choose it.
Billing state lives in Autumn: the routes read it through the Autumn API, and Farm stores no billing
records of its own.
What Autumn adds
| Area | Details |
|---|---|
| Products | Public products for pricing pages. |
| Status | Current plan, subscription, trial, features, limits, and entitlements. |
| Checkout | Attach an Autumn product and redirect users when payment or confirmation is required. |
| Portal | Open the customer portal from a typed caller. |
| Usage | Meter usage, report usage, check balance, and read current charges. |
| Webhooks | Verify Autumn events and pass them to your onEvent handlers. |
Common callers
const products = await api.billing.products.get();
const status = await api.billing.status.get();
const allowed = await api.billing.check.post({
body: {
key: "ai-generations",
amount: 1,
},
});Checkout flow
const checkout = await api.billing.checkout.post({
body: {
productId: "pro",
successPath: "/dashboard",
metadata: {
source: "pricing",
},
},
});
if (checkout.data?.redirectTo) {
window.location.href = checkout.data.redirectTo;
}Checkout and portal return paths must be same-origin, root-relative application paths. Protocol-relative paths such as //example.com are rejected.
Owner and entitlements
Autumn needs a billing owner when it checks or attaches customer state. Use the owner resolver to connect Farm auth/session data with Autumn customers.
autumn({
secretKey: process.env.AUTUMN_SECRET_KEY,
billing: {
async resolveOwner(ctx) {
const organizationId = ctx.req.get<string>("organization.id");
return organizationId
? {
id: organizationId,
kind: "organization",
email: ctx.req.get<string>("user.email"),
}
: null;
},
},
});Farm names the owner's Autumn customer <kind>_<id> when the id only uses letters, digits, _,
and - (and does not start or end with -), such as the random ids auth providers issue. Any other
id, such as an email or a non-ASCII handle, is encoded as <kind>_-<base64url of the id> so no two
owners share a customer. Return your own id from billing.resolveExternalCustomerId(owner) to
choose it yourself.
Before this, such ids were lossy: a.b and a-b both became user_a-b, and ids with no ASCII
letters became user_unknown. If your owner ids are emails or handles, their owners now get new
Autumn customers. To keep the existing ones while you move them, return
legacyAutumnExternalCustomerId(owner) (from @farm.js/autumn) from
resolveExternalCustomerId; owners that collided under it still share one customer.
Production notes
- Set
AUTUMN_SECRET_KEY,AUTUMN_WEBHOOK_SECRET, andAPP_BASE_URL. - Keep Farm product IDs separate from provider IDs when you want a stable app contract.
- Use
checkbefore expensive operations andreportUsageafter successful work. reportUsageadds to Autumn's stored usage withtrack, so parallel reports all count. When a meterguardsets a hard cap, a report that pushes usage past it is taken back out and answered withstate: "hard_limit_reached".lastmeters set the reported value instead.- A meter whose feature is billed as a prepaid quantity is bought, not reported:
reportUsageanswers409for it, and the quantity changes through checkout. - The billing POST routes act for the signed-in caller from its session cookie, so a request a
browser sends from another origin is refused with
403: a page on another site cannot report usage, start a checkout or open the portal for your visitor. Calls with noOrigin(your own server, a script) are unaffected. If your frontend runs on another origin, list it inallowedOrigins, using the same syntax asserverActions.allowedOrigins. - Treat webhooks as the source of truth for subscription state.
- Test free-plan reads, checkout redirects, portal redirects, usage limits, and webhook sync.
Add subscription and usage billing with Autumn while keeping Farm's integration and database APIs stable.