Farm.js

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 --ui

Config-first setup

src/lib/integrations.ts
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

Checkout
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

AreaDetails
ProductsPublic products for pricing pages.
StatusCurrent plan, subscription, trial, features, limits, and entitlements.
CheckoutAttach an Autumn product and redirect users when payment or confirmation is required.
PortalOpen the customer portal from a typed caller.
UsageMeter usage, report usage, check balance, and read current charges.
WebhooksVerify 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, and APP_BASE_URL.
  • Keep Farm product IDs separate from provider IDs when you want a stable app contract.
  • Use check before expensive operations and reportUsage after successful work.
  • reportUsage adds to Autumn's stored usage with track, so parallel reports all count. When a meter guard sets a hard cap, a report that pushes usage past it is taken back out and answered with state: "hard_limit_reached". last meters set the reported value instead.
  • A meter whose feature is billed as a prepaid quantity is bought, not reported: reportUsage answers 409 for 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 no Origin (your own server, a script) are unaffected. If your frontend runs on another origin, list it in allowedOrigins, using the same syntax as serverActions.allowedOrigins.
  • Treat webhooks as the source of truth for subscription state.
  • Test free-plan reads, checkout redirects, portal redirects, usage limits, and webhook sync.