> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blnkfinance.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Scoped API Keys

> Create scoped API keys so each service gets only the permissions it needs.

export const CtaCallout = props => {
  const {title, buttonLabel, href, trackingEvent, buttonTarget, rel = "noopener noreferrer", children} = props;
  const handleCtaClick = () => {
    if (typeof window === "undefined" || !trackingEvent) {
      return;
    }
    try {
      window.dispatchEvent(new CustomEvent("blnk:docs-cta", {
        detail: {
          name: trackingEvent,
          href
        }
      }));
    } catch {}
    try {
      window.posthog?.capture?.(trackingEvent, {
        href
      });
    } catch {}
    const gaPayload = {
      cta_href: href
    };
    try {
      window.gtag?.("event", trackingEvent, gaPayload);
    } catch {}
    try {
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: trackingEvent,
        ...gaPayload
      });
    } catch {}
  };
  const isExternal = typeof href === "string" && (/^https?:\/\//i).test(href);
  const target = buttonTarget ?? (isExternal ? "_blank" : undefined);
  const linkRel = isExternal ? rel : undefined;
  return <section className="cta-callout not-prose relative my-8 w-full min-w-0 overflow-hidden rounded-xl border border-zinc-200 p-5 dark:border-white/10">
      <div className="cta-callout-noise" aria-hidden="true" />
      <div className="cta-callout-layout">
        {title ? <div className="cta-callout-title-row">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" width="14" height="14" className="cta-callout-icon shrink-0 text-zinc-800 dark:text-zinc-200" aria-hidden="true">
              <g fill="none" fillRule="nonzero">
                <path d="M28 0v28H0V0h28ZM14.691833333333335 27.134333333333334l-0.012833333333333334 0.0023333333333333335 -0.08283333333333333 0.04083333333333334 -0.023333333333333334 0.004666666666666667 -0.016333333333333335 -0.004666666666666667 -0.08283333333333333 -0.04083333333333334c-0.011666666666666667 -0.004666666666666667 -0.022166666666666668 -0.0011666666666666668 -0.028000000000000004 0.005833333333333334l-0.004666666666666667 0.011666666666666667 -0.019833333333333335 0.49933333333333335 0.005833333333333334 0.023333333333333334 0.011666666666666667 0.015166666666666667 0.12133333333333333 0.08633333333333333 0.0175 0.004666666666666667 0.014000000000000002 -0.004666666666666667 0.12133333333333333 -0.08633333333333333 0.014000000000000002 -0.018666666666666668 0.004666666666666667 -0.019833333333333335 -0.019833333333333335 -0.4981666666666667c-0.0023333333333333335 -0.011666666666666667 -0.0105 -0.019833333333333335 -0.019833333333333335 -0.021Zm0.3091666666666667 -0.13183333333333336 -0.015166666666666667 0.0023333333333333335 -0.21583333333333335 0.1085 -0.011666666666666667 0.011666666666666667 -0.0035000000000000005 0.012833333333333334 0.021 0.5016666666666667 0.005833333333333334 0.014000000000000002 0.009333333333333334 0.008166666666666668 0.23450000000000004 0.1085c0.014000000000000002 0.004666666666666667 0.026833333333333334 0 0.03383333333333334 -0.009333333333333334l0.004666666666666667 -0.016333333333333335 -0.03966666666666667 -0.7163333333333334c-0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.023333333333333334 -0.023333333333333334 -0.025666666666666667Zm-0.8341666666666667 0.0023333333333333335a0.026833333333333334 0.026833333333334334 0 0 0 -0.0315 0.007000000000000001l-0.007000000000000001 0.016333333333333335 -0.03966666666666667 0.7163333333333334c0 0.014000000000000002 0.008166666666666668 0.023333333333333334 0.019833333333333335 0.028000000000000004l0.0175 -0.0023333333333333335 0.23450000000000004 -0.1085 0.011666666666666667 -0.009333333333333334 0.004666666666666667 -0.012833333333333334 0.019833333333333335 -0.5016666666666667 -0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.011666666666666667 -0.21466666666666667 -0.10733333333333334Z" strokeWidth="1.1667" />
                <path fill="currentColor" d="M14 2.916666666666667A1.75 1.75 0 0 1 15.750000000000002 4.666666666666667v6.302333333333334L21.207666666666668 7.816666666666667a1.75 1.75 0 0 1 1.75 3.031L17.5 14l5.457666666666667 3.151166666666667a1.75 1.75 0 0 1 -1.75 3.031l-5.457666666666667 -3.1500000000000004V23.333333333333336a1.75 1.75 0 0 1 -3.5 0v-6.302333333333334L6.792333333333334 20.183333333333337a1.75 1.75 0 1 1 -1.75 -3.031L10.5 14 5.042333333333334 10.848833333333333a1.75 1.75 0 0 1 1.75 -3.031l5.457666666666667 3.1500000000000004V4.666666666666667A1.75 1.75 0 0 1 14 2.916666666666667Z" strokeWidth="1.1667" />
              </g>
            </svg>
            <p className="cta-callout-title min-w-0 font-semibold text-zinc-800 dark:text-zinc-200">
              {title}
            </p>
          </div> : null}
        <div className={`cta-callout-body text-sm leading-normal text-zinc-800 dark:text-zinc-200${title ? " cta-callout-body--indented" : ""}`}>
          {children}
        </div>
        <a href={href} target={target} rel={linkRel} onClick={handleCtaClick} data-docs-cta={trackingEvent || undefined} className="cta-callout-button inline-flex items-center justify-center gap-1 rounded-full bg-white px-3 py-1.5 text-sm font-semibold transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 dark:bg-white dark:hover:bg-zinc-200">
          {buttonLabel}
          <span className="cta-callout-button-arrow" aria-hidden="true">
            →
          </span>
        </a>
      </div>
    </section>;
};

<Info>Available on Blnk Core 0.10.1 and later.</Info>

Your master key has full access to your Blnk server. Use it only for admin actions, like creating and revoking API keys.

For everything else, use scoped API keys.

Each key can be limited to a specific owner, set of scopes, and expiry date. For example, your payments service might only need `transactions:write` and `balances:read`, while your reporting service might only need `*:read`.

This keeps each service limited to the access it needs. If a key is exposed, you can revoke that key without rotating your master key or affecting the rest of your system.

***

## Before you start

Scoped API keys require secure mode. When secure mode is enabled, Blnk requires every request to include a valid key in the `X-Blnk-Key` header.

Start by enabling secure mode and setting a strong `secret_key` in your configuration:

<CodeGroup>
  ```bash blnk.env theme={"system"}
  BLNK_SERVER_SECURE=true
  BLNK_SERVER_SECRET_KEY=your_strong_secret_key
  ```

  ```json blnk.json theme={"system"}
  {
    "server": {
      "secure": true,
      "secret_key": "your_strong_secret_key"
    }
  }
  ```
</CodeGroup>

The `secret_key` becomes your master key. Use it once to create your first scoped API key with `POST /api-keys`.

After that, use scoped keys for normal API traffic.

<Warning>
  Do not commit the master key to version control. In production, store it in a secret manager or inject it through environment variables.
</Warning>

***

## Create and use a scoped key

<Steps>
  <Step title="Create a scoped key">
    Use the master key to create your first scoped key. In this example, we create a key for a payments service:

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X POST "http://localhost:5001/api-keys" \
        -H "X-blnk-key: <master-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "Payments Service",
          "owner": "payments-team",
          "scopes": ["transactions:write", "balances:read"],
          "expires_at": "2027-06-13T00:00:00Z"
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.ApiKeys.create({
        name: 'Payments Service',
        owner: 'payments-team',
        scopes: ['transactions:write', 'balances:read'],
        expires_at: '2027-06-13T00:00:00Z',
      });
      ```
    </CodeGroup>

    | Parameter    | Description                                                                                                                   |
    | :----------- | :---------------------------------------------------------------------------------------------------------------------------- |
    | `name`       | A readable label for the key, such as a service or environment name.                                                          |
    | `owner`      | The team, service, or tenant that owns the key. Blnk stores this as `owner_id`. See [Owner context](/api-keys/owner-context). |
    | `scopes`     | The permissions assigned to the key, using `resource:action` format. See [Scopes](/api-keys/scopes).                          |
    | `expires_at` | When the key stops working, in ISO 8601 format.                                                                               |

    A successful request returns `201 Created`.
  </Step>

  <Step title="Save the key">
    On success, the response includes the plaintext key in the `key` field:

    ```json 201 Created wrap theme={"system"}
    {
      "api_key_id": "api_key_879f0ecb-e29f-4137-801b-1048366381db",
      "key": "YVLIhuIplUzLRCcT9r7DQ_jsGKCXAn39JQ3n_o-Ll2Q=",
      "name": "Payments Service",
      "owner_id": "payments-team",
      "scopes": ["transactions:write", "balances:read"],
      "expires_at": "2027-06-13T00:00:00Z",
      "created_at": "2026-06-13T10:30:00Z",
      "last_used_at": "0001-01-01T00:00:00Z",
      "is_revoked": false
    }
    ```

    Copy the `key` value immediately and store it in your secrets manager or as an environment variable. Wire that value into the service that will call Blnk.

    <Warning>
      Blnk shows the plaintext key only once. After creation, the key is hashed at rest and cannot be retrieved again.

      If you lose it, create a new key and revoke the old one.
    </Warning>
  </Step>

  <Step title="Use the scoped key">
    Pass the scoped key in the `X-Blnk-Key` header on every request.

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X POST "http://localhost:5001/transactions" \
        -H "X-blnk-key: <scoped-api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "precise_amount": 10000,
          "reference": "ref_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
          "currency": "USD",
          "precision": 100,
          "source": "bln_a1b2c3d4-6e2d-4f89-a17b-3d5e8f2a1c94",
          "destination": "bln_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
          "description": "Payment transfer",
          "allow_overdraft": true
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.Transactions.create({
        precise_amount: 10000,
        reference: 'ref_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94',
        currency: 'USD',
        precision: 100,
        source: 'bln_a1b2c3d4-6e2d-4f89-a17b-3d5e8f2a1c94',
        destination: 'bln_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94',
        description: 'Payment transfer',
        allow_overdraft: true,
      });
      ```

      ```go Go wrap theme={"system"}
      baseURL, _ := url.Parse("http://localhost:5001/")
      scopedKey := os.Getenv("BLNK_SCOPED_API_KEY")
      client := blnkgo.NewClient(baseURL, &scopedKey)

      transaction, resp, err := client.Transaction.Create(blnkgo.CreateTransactionRequest{
        ParentTransaction: blnkgo.ParentTransaction{
          PreciseAmount: 10000,
          Reference:   "ref_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
          Currency:    "USD",
          Precision:   100,
          Source:      "bln_a1b2c3d4-6e2d-4f89-a17b-3d5e8f2a1c94",
          Destination: "bln_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
          Description: "Payment transfer",
        },
        AllowOverdraft: true,
      })
      ```
    </CodeGroup>

    Blnk checks that the key is valid, not expired or revoked, and that its scopes cover the endpoint you're calling. This key has `transactions:write`, so it can create transactions but cannot create ledgers without `ledgers:write`.
  </Step>
</Steps>

***

## API key tracking

When you create a record with a scoped API key, Blnk adds the key’s `api_key_id` to the resource `meta_data` under `BLNK_GENERATED_BY`.

This lets you trace which key created a ledger, balance, transaction, or identity.

The master key does not add this field. Only scoped API keys authenticated through `X-Blnk-Key` do.

```json API key tracking wrap theme={"system"}
"meta_data": {
  "purpose": "customer_wallet",
  "BLNK_GENERATED_BY": "api_key_879f0ecb-e29f-4137-801b-1048366381db"
}
```

Blnk sets `BLNK_GENERATED_BY` automatically on `POST` requests. You do not need to include it in your request body. If you send your own `meta_data`, Blnk merges this field into it.

<Tip>
  You can use `BLNK_GENERATED_BY` for audit trails, service attribution, and tenant-level tracing. For example, if each tenant or service uses its own scoped key, you can filter records by `meta_data.BLNK_GENERATED_BY` to see what that key created.
</Tip>

***

## Error handling

<Info>
  Structured errors are available from Blnk Core 0.15.0 and later.
</Info>

When a scoped key fails authentication, Blnk returns a `401 Unauthorized` response.

| Code                   | When it happens                                                             |
| :--------------------- | :-------------------------------------------------------------------------- |
| `AUTH_INVALID_API_KEY` | The key is incorrect, malformed, incomplete, or sent with the wrong header. |
| `AUTH_EXPIRED_API_KEY` | The key has expired or has been revoked.                                    |

```json 401 Unauthorized wrap theme={"system"}
{
  "error": "API key is expired or revoked",
  "error_detail": {
    "code": "AUTH_EXPIRED_API_KEY",
    "message": "API key is expired or revoked"
  }
}
```

To resolve the error:

| Code                   | What to do                                                                                                                                         |
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH_INVALID_API_KEY` | Check that the full key value is being sent in the `X-Blnk-Key` header.                                                                            |
| `AUTH_EXPIRED_API_KEY` | Create a replacement key with the same scopes, update the service using it, then revoke the old key. See [Manage API keys](/api-keys/manage-keys). |

Other authentication errors, such as a missing `X-Blnk-Key` header, are covered in [Secure your Blnk server](/advanced/secure-blnk#error-handling).

***

## Related docs

<CardGroup cols={2}>
  <Card title="Scopes" icon="list-check" href="/api-keys/scopes">
    Pick the right permissions.
  </Card>

  <Card title="Owner context" icon="users" href="/api-keys/owner-context">
    How Blnk isolates key management.
  </Card>

  <Card title="Manage keys" icon="list" href="/api-keys/manage-keys">
    List, revoke, and delegate keys.
  </Card>
</CardGroup>

***

## Need help?

We are very happy to help you make the most of Blnk, regardless of whether it is your first time or you are switching from another tool.

To ask questions or discuss issues, please [contact us](mailto:support@blnkfinance.com) or [join our Discord community](https://discord.gg/7WNv94zPpx).

<CtaCallout title="Connect your ledger to Blnk Cloud" href="https://cloud.blnkfinance.com/auth/sign-up?utm_source=blnk_docs&utm_medium=documentation&utm_campaign=need-help" buttonLabel="Open Blnk Cloud" trackingEvent="clicked_cloud_signup">
  Sign up and manage your ledger with our back-office dashboard. You can invite teammates to collaborate and manage your ledger operations directly from the dashboard.
</CtaCallout>
