A small contract.
A complete connection.

Give your product billing without making billing part of your product.

01Register your product

An operator issues your product key. Keep it server-side. Register the product’s return origin and callback URL, then store the key and signing secret in the product’s server environment.

One product, one boundary. A product key can only address that product’s accounts. Product registration is a deliberate operator action.

02Connect the SDK

Create a server-side client. The SDK reads SPECIE_URL and SPECIE_KEY from your environment.

import { createSpecieClient } from '@specie/sdk';

const billing = createSpecieClient();

await billing.upsertAccount('studio-halfmoon', {
  displayName: 'Halfmoon VFX',
  billingEmail: 'billing@halfmoon.example',
});

03Declare a catalog

Keep your prices and entitlements beside the product code. Validate and review a catalog before applying it. Every publication creates an immutable version.

pnpm specie check
pnpm specie publish
# Review the dry run before applying:
pnpm specie publish --apply

Subscriptions stay pinned to the catalog version they purchased. A new price never silently changes existing terms.

04Receive snapshots

Mount a callback in your product. The SDK verifies the signature, rejects snapshots for another product, and only stores versions that move forward.

// app/api/specie/events/route.ts
import { specieCallback } from '@specie/sdk/next';
import { drizzleStore } from '@specie/sdk/drizzle';
import catalog from '@/billing/catalog';
import { db } from '@/db';

export const POST = specieCallback({
  product: catalog.product,
  store: drizzleStore(db()),
});

Set SPECIE_SIGNING_SECRET on the server. Include the SDK’s entitlement table in the product’s database schema.

05Enforce access locally

Your product makes access decisions from its own signed snapshot. The client pulls only when there is no snapshot or its validUntil has passed. A failed pull denies access; it never invents a grant.

import { createEntitlements } from '@specie/sdk';
import { drizzleStore } from '@specie/sdk/drizzle';
import { createSpecieClient } from '@specie/sdk';
import catalog from '@/billing/catalog';
import { db } from '@/db';

const access = createEntitlements({
  catalog,
  store: drizzleStore(db()),
  client: createSpecieClient(),
});

await access.requireEntitlement(
  'studio-halfmoon', 'submission'
);
Our failure is never theirs. When Specie is unavailable, communicate a billing outage. Do not tell a customer that their payment failed.

See the contract at work.

Explore the sample portfolio, inspect an access snapshot, and follow it back to the event that created it.

Explore the console