Autopopulate API keys

Allow users to log in and automatically populate their API keys in the API Explorer.
View as Markdown
Pro and Enterprise feature

This feature is available only for the Pro and Enterprise plans. To get started, reach out to support@buildwithfern.com.

Fern can integrate with your authentication flow, allowing users to log in and have their API key automatically populated with the click of a button.

User credentials are stored only in browser cookies and never transmitted to Fern’s servers. Learn more in the Security overview.

The fern payload

API key injection works by reading a fern claim from a JWT stored in the user’s browser. The fern claim contains a playground object that tells the API Explorer what values to pre-fill.

The top-level structure is:

1{
2 "fern": {
3 "playground": {
4 "initial_state": { ... },
5 "env_state": { ... }
6 }
7 }
8}
FieldDescription
initial_stateDefault values applied to all API Explorer requests.
env_statePer-environment overrides that are merged on top of initial_state when the user selects a matching environment.

Auth types

The initial_state.auth object supports bearer token and basic authentication.

1{
2 "fern": {
3 "playground": {
4 "initial_state": {
5 "auth": {
6 "bearer_token": "eyJhbGciOiJIUzI1c"
7 }
8 }
9 }
10 }
11}

Custom headers and parameters

You can pre-fill headers, path parameters, and query parameters in addition to (or instead of) auth credentials. Values are applied to any matching fields in the API Explorer.

1{
2 "fern": {
3 "playground": {
4 "initial_state": {
5 "auth": {
6 "bearer_token": "eyJhbGciOiJIUzI1c"
7 },
8 "headers": {
9 "API-Version": "2024-02-02"
10 },
11 "path_parameters": {
12 "plantId": "plant_1234"
13 },
14 "query_parameters": {
15 "sort": "DESCENDING"
16 }
17 }
18 }
19 }
20}

Multiple API keys

To provide multiple API keys (for example, one per application), set bearer_token to a JSON-encoded array of key-value objects. Each object maps an application name to its key.

1{
2 "fern": {
3 "playground": {
4 "initial_state": {
5 "auth": {
6 "bearer_token": "[{\"Greenhouse app\": \"sk-greenhouse-abc123\"}, {\"Garden app\": \"sk-garden-def456\"}]"
7 }
8 }
9 }
10 }
11}

The API Explorer displays a dropdown so the user can choose which application key to use for requests.

Per-environment keys

Use env_state to provide different credentials for each environment (for example, production vs. staging). Each key in env_state is matched against the currently selected environment URL using substring matching.

1{
2 "fern": {
3 "playground": {
4 "initial_state": {
5 "auth": {
6 "bearer_token": "default-token"
7 }
8 },
9 "env_state": {
10 "prod": {
11 "auth": {
12 "bearer_token": "prod-token-abc123"
13 }
14 },
15 "staging": {
16 "auth": {
17 "bearer_token": "staging-token-def456"
18 }
19 }
20 }
21 }
22 }
23}

When the user selects an environment containing prod (such as https://api.prod.example.com), the API Explorer uses prod-token-abc123. The env_state values are merged on top of initial_state: auth is replaced entirely, while headers, path parameters, and query parameters are shallow-merged.

You can combine multiple API keys with per-environment keys:

1{
2 "fern": {
3 "playground": {
4 "env_state": {
5 "prod": {
6 "auth": {
7 "bearer_token": "[{\"Greenhouse app\": \"sk-prod-abc\"}, {\"Garden app\": \"sk-prod-def\"}]"
8 }
9 },
10 "dev": {
11 "auth": {
12 "bearer_token": "[{\"Greenhouse app\": \"sk-dev-abc\"}, {\"Garden app\": \"sk-dev-def\"}]"
13 }
14 }
15 }
16 }
17 }
18}

Integrating with your auth flow

API key injection requires a JWT containing the fern payload described above to be stored in the user’s browser as a cookie called fern_token. There are two ways to set this up:

  • JWT: You handle the entire auth flow and set the fern_token cookie yourself.
  • OAuth: You give Fern access, and Fern initiates the OAuth handshake process directly.
  1. When a user clicks the Login button in the API Explorer, they are redirected to your authentication page.
  2. After successful authentication, your system sets a cookie called fern_token in the user’s browser.
  3. This token is a JWT signed with a secret key from Fern. The JWT payload contains the fern claim with the user’s API key.

Set up

To enable API key injection with JWT:

  1. Reach out to Fern to get your secret key.
  2. Send Fern the URL of your authentication page. This is where users are redirected after clicking the Login button in the API Explorer.
  3. Add logic to your service to set the fern_token cookie when a user logs in.

This Next.js endpoint handles the callback from your authentication page. It reads the state parameter to determine where to redirect the user, mints a fern_token JWT using jose, sets it as a cookie, and redirects the user back to the docs.

app/api/fern-token/route.ts
1import { SignJWT } from "jose";
2import { cookies } from "next/headers";
3import { type NextRequest, NextResponse } from "next/server";
4
5export async function GET(req: NextRequest): Promise<NextResponse> {
6 const domain = getDomain(req); // your logic to determine the docs domain
7
8 // use the state param to determine redirect location
9 const returnTo = req.nextUrl.searchParams.get("state");
10 const redirectLocation = returnTo ?? `https://${domain}`;
11
12 // fetch the user's API key and secret (from your config or database)
13 const apiKey = await getApiKeyForUser();
14 const secret = await getSecretForDomain(domain);
15
16 if (!apiKey || !secret) {
17 // redirect with an error if credentials are missing
18 const url = new URL(redirectLocation);
19 url.searchParams.set("error", "missing_credentials");
20 return NextResponse.redirect(url);
21 }
22
23 // mint the JWT using the secret key
24 const fernToken = await mintFernToken({ secret, apiKey });
25
26 if (!fernToken) {
27 const url = new URL(redirectLocation);
28 url.searchParams.set("error", "token_creation_failed");
29 return NextResponse.redirect(url);
30 }
31
32 // set the fern_token as a cookie on the docs domain
33 const cookieJar = await cookies();
34 cookieJar.set("fern_token", fernToken, {
35 httpOnly: true,
36 secure: true,
37 sameSite: "lax",
38 domain,
39 });
40
41 // redirect the user back to the docs
42 return NextResponse.redirect(redirectLocation);
43}
44
45const encoder = new TextEncoder();
46
47async function mintFernToken({
48 secret,
49 apiKey,
50 payload,
51}: {
52 secret: string;
53 apiKey: string;
54 payload?: Record<string, unknown>;
55}): Promise<string> {
56 const jwtPayload = payload ?? {
57 fern: {
58 playground: {
59 initial_state: {
60 auth: {
61 bearer_token: apiKey,
62 },
63 },
64 },
65 },
66 };
67
68 return await new SignJWT(jwtPayload)
69 .setProtectedHeader({ alg: "HS256", typ: "JWT" })
70 .setIssuedAt()
71 .setExpirationTime("1d") // set to any value
72 .setIssuer("https://buildwithfern.com")
73 .sign(encoder.encode(secret)); // sign using the secret provided by Fern
74}