---
title: Access APIs on Behalf of Users | Keycard
description: Build an app or custom MCP server that accesses APIs on behalf of your users without storing tokens.
---

Ask Claude to build an app for you. By the end you’ll have a running application, whether a custom MCP server or any backend service, where users sign in once and every API call to a third-party service (like Linear) uses a short-lived token scoped to that user. Tokens are minted per-request and never stored.

MCP ServerYour server running locally

User AuthUsers sign in once via Keycard

Per-Request TokensScoped, short-lived, never stored

Audit TrailEvery access logged in Console

## Prerequisites

- **Keycard session active**. If you haven’t set one up yet, complete the [Quickstart](/guides/quickstart/index.md) first (steps 1-4).
- Your `keycard run -- claude` session should be running

## Walkthrough

1. **Ask Claude to create your MCP server**

   Inside your Keycard session, send a prompt like:

   > Create a mcp server which will allows access to linear on behalf of users.

   The Keycard skill plans the execution and provides you with an action plan before proceeding.

   Tip

   You can ask questions about the planned setup before approving it — for example, ask Claude to explain the architecture or generate a flow diagram.

2. **Claude scaffolds the project and provisions Keycard resources**

   Claude generates the code, wires it to your Keycard instance, and provisions the resources needed for credential brokering through the Management API.

   Tip

   While Claude is building out the MCP server, this is a good time to read the [Patterns](#patterns) section below.

   Look at how the scaffolded code handles auth and resource access:

   - [Authentication](#tab-panel-58)
   - [Resource Access](#tab-panel-59)

   ```
   // server.ts — every request is authenticated before any tool runs
   const bearerAuth = requireBearerAuth({
     issuers: KEYCARD_URL,
     requiredScopes: ["mcp:tools"],
   });


   app.post("/mcp", bearerAuth, async (req, res) => { ... });
   ```

   ```
   // upstream.ts — per-request token exchange inside a tool handler
   const accessCtx = await authProvider.exchangeTokens(
     subjectToken,        // user's bearer token from the incoming request
     LINEAR_RESOURCE,     // the upstream API being accessed
   );
   const linearToken = accessCtx.access(LINEAR_RESOURCE).accessToken;
   ```

3. **Claude registers the server in Claude’s MCP config**

   Claude runs `claude mcp add` to register your server so it’s available in future sessions.

4. **Start the server**

   Claude will provide detailed instructions with the exact steps to start and test your server. In a new terminal, start your MCP server with Keycard:

   Terminal window

   ```
   cd <project-name>
   keycard run -- npm start
   ```

   `keycard run` injects short-lived credentials from your Keycard instance at startup. No secrets live in a file. Keep this terminal open.

   You may see a consent screen like this when the CLI requests access to your application’s credentials:

   ![Keycard consent screen](/_astro/keycard-consent-screen.nvATOWuZ_Z2hcbt9.webp)

5. **Connect and test**

   In another terminal, start a new Claude session from the same project directory:

   Terminal window

   ```
   keycard run -- claude
   ```

   Your MCP server is registered but needs you to authenticate before Claude can call its tools on your behalf. Type `/mcp` to open the server list, then select your server and complete the login flow:

   ```
   > /mcp


     Local MCPs
   ❯ <project-name> · △ needs authentication
   ```

   Select the server and authorize in your browser when prompted. You may see a consent screen from Linear that establishes trust between Keycard and Linear:

   ![Linear consent screen](/_astro/linear-consent-screen.D_6cq1Wi_Zk2Kck.webp)

   Once connected, you’ll see `✔ connected` next to it.

   Now try calling a tool:

   > List my Linear issues.

   Each tool call triggers a fresh credential exchange. The upstream token is minted per-request and never stored.

6. **Verify in Keycard Console**

   Open [Keycard Console](https://console.keycard.ai) → **Audit Log**. You should see entries for:

   |                      |                            |
   | -------------------- | -------------------------- |
   | `users:authenticate` | You logged in successfully |
   | `users:authorize`    | Your access was authorized |
   | `credentials:issue`  | Access token was issued    |

   These entries map directly to the [delegation chain](#ephemeral-resource-access) described below.

## Patterns

Your server is running. Here’s what Claude set up and why it matters when you build your own servers or debug this one.

### Application Authorization

Every request to your MCP server is authenticated before any tool runs. Whether the caller is a human or another agent, the flow is the same.

The Keycard SDK handles the protocol details: advertising how callers should authenticate, validating credentials, and passing the verified identity into your tool handlers.

The audit log entries from step 6 show this in action: `users:authenticate` confirms the caller’s identity was verified, `users:authorize` confirms their access was checked against [policy](/admin/access-policies/index.md).

For the full authorization model, see [How Keycard Works](/guides/how-keycard-works/index.md).

### Application Identity

Your MCP server needs its own identity to access upstream resources. Keycard provisions application credentials and stores them on the platform. Nothing lives locally or gets committed to source control.

At startup, `keycard run` resolves the credentials declared in `keycard.toml` and injects them into the runtime environment. Your server uses those credentials to authenticate itself to Keycard when requesting resources on behalf of callers.

How credentials are delivered depends on where you’re running. Locally, `keycard run` pulls them from the Keycard platform. In production, your service needs to authenticate itself. The [next guide](/guides/run-apps-without-static-secrets/index.md) covers how. See [Applications](/platform/concepts/applications/index.md) for the full identity and credential model.

### Ephemeral Resource Access

When a tool call needs to reach an upstream API, your server doesn’t hold a long-lived token. It uses delegated token exchange to get a short-lived, scoped token for that single request.

User

MCP Client

MCP Server

Resource

Keycard

ephemeral credential

User

authorize

MCP Client

authenticateKeycard

caller identity

MCP Server

exchangecaller identityKeycard

ephemeral credential

Resource

The `credentials:issue` audit log entry from step 6 is the result of this exchange. Every tool call repeats the same cycle. Nothing is stored. Every access is scoped and logged.

## What’s Next

Your application is running locally with per-user token exchange. In production, nobody is there to inject credentials at startup, so your application needs to authenticate itself. The next guide covers that:

- **[Run Apps Without Static Secrets](/guides/run-apps-without-static-secrets/index.md)**: deploy your service so it authenticates itself, no API keys or .env files

## Troubleshooting

Why am I seeing so many ITL prompts?

During the walkthrough, Claude provisions Keycard resources using `keycard agent api` commands. If your policy has a broad `@itl("prompt")` rule on `Bash`, every one of those provisioning calls triggers an approval prompt.

- Ask Claude to update your policy to allow `keycard` CLI commands without ITL.
- Check your active policy with: *“What’s my current Keycard policy?”*

`keycard agent api` POST returns “Unsupported Media Type” or 400

Provisioning commands run inside a `keycard run` session can fail with a 400 or `"Unsupported Media Type"` error.

- Prompt Claude to look up the Keycard API reference and retry the command with the correct invocation.
