Unkey Integration
Create, verify, revoke, update, and delete API keys, plus protect routes with key verification and rate-limit checks.
Configure Unkey
import { defineConfig } from "@farm.js/core";import { unkey } from "@farm.js/integrations/unkey";export default defineConfig({ integrations: { keys: unkey({ rootKey: process.env.UNKEY_ROOT_KEY, apiId: process.env.UNKEY_API_ID, }), },});
Choose client ownership
Let Farm construct Unkey
The configuration above is the default path. When instance is omitted, Farm creates its Unkey
client from rootKey, apiId, baseUrl, and an optional custom fetch, supplied directly or
through environment variables where supported.
Provide an application-owned instance
Use instance when client construction belongs to application code or a shared dependency
container. It takes precedence over credentials passed directly to the integration.
import { createUnkeyClient, unkey } from "@farm.js/integrations/unkey";const unkeyClient = createUnkeyClient({ rootKey: process.env.UNKEY_ROOT_KEY, apiId: process.env.UNKEY_API_ID,});export const keys = unkey({ instance: unkeyClient,});
The previous client option remains supported as a deprecated alias, making this change
backward-compatible. Routes and protection settings still belong in unkey(...) in either mode.
Create and verify keys
const created = await api.keys.create.post({ body: { name: "Production key", permissions: ["documents.read"], },});const verified = await api.keys.verify.post({ body: { key: created.data!.key, permissions: "documents.read", },});
Best fit
- API products where customers need their own keys.
- Internal platform keys for service-to-service requests.
- Route protection where key validity, permissions, credits, or rate limits matter.
What Unkey adds
| Area | Details |
|---|---|
| Create | Server-only route for creating customer or service keys. |
| Verify | Server-only route for checking validity, permissions, credits, and rate limits. |
| Update | Server-only route for changing metadata, roles, permissions, credits, and limits. |
| Revoke | Server-only route for disabling a key without deleting its record. |
| Delete | Server-only route for deleting a key when the app no longer needs it. |
| Middleware | Optional protected route matcher that verifies request keys before app code runs. |
Server-only callers
Unkey mutation and verification callers are marked as server-only. Use api from createIntegrations, not apiClient in browser components.
import { api } from "../lib/api";export async function createCustomerKey(userId: string) { const created = await api.keys.create.post({ body: { externalId: userId, permissions: ["documents.read"], ratelimits: [ { name: "documents", limit: 1000, duration: 60_000, }, ], }, }); return created.data;}
Protect routes
unkey({ rootKey: process.env.UNKEY_ROOT_KEY, apiId: process.env.UNKEY_API_ID, protectedRoutes: ["/api/public/[...path]"], protection: { header: "authorization", permissions: ["documents.read"], },});
When a request matches protectedRoutes, the integration verifies the key before the route handler runs.
Production notes
- Keep
UNKEY_ROOT_KEYserver-only. - Use server callers for key creation and verification.
- Prefer permissions and ratelimits over one global "valid key" check.
- Revoke keys before deleting them when users may need an audit trail.
- Test expired keys, missing permissions, exhausted credits, and rate-limit failures.