---
title: Authentication
description: API key management, service authentication, key rotation, and security best practices for the CodeSpar API.
---

import { Callout } from "fumadocs-ui/components/callout";

# Authentication

CodeSpar uses a two-layer authentication model. Your application authenticates to CodeSpar with an **API key** (bearer token), and CodeSpar handles authentication to individual MCP servers (payment providers, shipping carriers, fiscal services) on your behalf using **service auth** credentials you configure once in the dashboard.

This separation means your AI agent never touches raw provider credentials. It only needs the CodeSpar API key.

## API keys

API keys authenticate your application to the CodeSpar API. Every request to `api.codespar.dev` must include a valid key.

### Key formats

CodeSpar issues two types of keys, distinguished by prefix:

| Prefix | Environment | Behavior |
|--------|-------------|----------|
| `csk_live_` | Production | Connects to real provider APIs. Processes real transactions, issues real invoices, sends real messages. |
| `csk_test_` | Sandbox | Connects to mock servers. Returns realistic simulated data. No real money moves, no real documents are issued. |

<Callout type="warn">
The key prefix **must match** the environment of the project it operates on. A `csk_live_` key against a `test` project (or vice-versa) returns `401 unauthorized`. See [Projects → Environments](/docs/concepts/projects#environments-live-vs-test) for why this is enforced and how to debug the mismatch.
</Callout>

### Using API keys with the SDK

```typescript
import { CodeSpar } from "@codespar/sdk";

// Production -- real transactions
const codespar = new CodeSpar({
  apiKey: "csk_live_abc123def456ghi789...",
});

// Sandbox -- mock data, safe for development
const codespar = new CodeSpar({
  apiKey: "csk_test_abc123def456ghi789...",
});
```

### Using API keys with curl

Pass the key as a Bearer token in the `Authorization` header:

```bash
curl -X POST https://api.codespar.dev/v1/sessions \
  -H "Authorization: Bearer csk_live_abc123def456ghi789..." \
  -H "Content-Type: application/json" \
  -d '{"servers": ["stripe", "mercadopago"]}'
```

### Creating keys in the dashboard

1. Navigate to [Dashboard → API Keys](https://codespar.dev/dashboard/api-keys)
2. Click **Create Key**
3. Name the key (e.g., "Production Backend", "Staging", "CI/CD Pipeline")
4. The environment defaults to match the active project's `environment` field. To mint a key for a different environment than the active project (cross-project work), flip the explicit toggle on the modal.
5. Optionally restrict the key to specific [scopes](#key-scopes) (choose **Restricted** in the dashboard)
6. Click **Create** and copy the key immediately — it is only shown once

```bash
# Verify your key works
curl https://api.codespar.dev/v1/servers \
  -H "Authorization: Bearer csk_live_your_new_key..."
```

<Callout type="warn">
**Never expose API keys in client-side code.** Keys must stay on the server. Use `process.env.CODESPAR_API_KEY` or your framework's secret management (Vercel Environment Variables, AWS Secrets Manager, etc.).
</Callout>

### Key scopes

By default an API key has full access to your account. You can instead **restrict a key to a subset of operations** when you create it — in the dashboard, choose **Restricted** and check the scopes you want.

| Scope | What it allows |
|-------|---------------|
| `sessions:create` | Create new sessions |
| `sessions:read` | List and retrieve session details |
| `tools:execute` | Execute tool calls via `session.execute()` and `session.send()` |
| `servers:read` | Browse the server catalog |

**Example:** A key used only by a webhook handler that processes tool results carries only `tools:execute` and `sessions:read`.

A request whose key lacks the required scope returns `403 forbidden`:

```json
{
  "error": "forbidden",
  "message": "API key does not have the 'sessions:create' scope.",
  "status": 403
}
```

<Callout type="info">
Scopes cover the bearer-key surface — sessions, tools, and servers. Billing and API-key management are dashboard/operator-only and aren't reachable with an API key regardless of scope.
</Callout>

## Key rotation

You can create multiple API keys and rotate them without downtime. This is critical for production systems where you cannot afford an outage during credential updates.

### Rotation procedure

1. **Create a new key** in the dashboard with the same settings as the existing key
2. **Deploy the new key** to your application (update environment variables, redeploy)
3. **Verify** that the new key is working by checking API responses and session creation
4. **Revoke the old key** in the dashboard once all instances are using the new key

```bash
# Step 1: Create new key via the dashboard

# Step 2: Test the new key
curl https://api.codespar.dev/v1/servers \
  -H "Authorization: Bearer csk_live_new_key..."
# Should return 200 OK

# Step 3: Revoke the old key via the dashboard
```

<Callout type="warn">
Revoking a key **immediately terminates all active sessions** created with that key. Any in-flight tool calls will fail. Always ensure all traffic has migrated to the new key before revoking.
</Callout>

### Rotation schedule

For production systems, rotate API keys at least every 90 days. Set a calendar reminder or automate it with your secrets management system.

## Service authentication

**Service authentication** (also called **service auth**) is how CodeSpar connects to third-party commerce APIs on your behalf. You store your provider credentials once in the dashboard, and CodeSpar uses them automatically when a [session](/docs/concepts/sessions) connects to that server.

### How it works

1. You store provider credentials (e.g., Stripe secret key, Mercado Pago access token) in the CodeSpar dashboard under **Auth Configs**
2. When a session connects to that server, CodeSpar authenticates using those stored credentials
3. Your agent code never sees the provider credentials -- they stay encrypted at rest on CodeSpar's infrastructure

```typescript
// Your agent code does not handle provider auth
const session = await codespar.create("user_123", {
  servers: ["stripe"],
  // CodeSpar uses your stored Stripe credentials automatically
});

// This "just works" -- auth is handled by CodeSpar
const result = await session.execute("codespar_charge", {
  method: "pix",
  amount: 9990,
  currency: "BRL",
  description: "Pro Plan",
});
```

### Configuring provider credentials

Navigate to [codespar.dev/dashboard/auth-configs](https://codespar.dev/dashboard/auth-configs) and add credentials for each server you plan to use:

| Server | Auth type | What you provide |
|--------|-----------|-----------------|
| Stripe | API key | Secret key (`sk_live_...` or `sk_test_...`) |
| Asaas | API key | API key from the Asaas dashboard |
| Fiscal (NF-e/NFS-e) | API key | API token from the fiscal provider (no `Bearer` prefix on this one) |
| Melhor Envio | OAuth | Authorize once via dashboard; refresh handled automatically |
| Z-API | path_secret | Instance ID + token + Client-Token (see below) |
| Mercado Pago | OAuth token | Access token from the Mercado Pago dashboard |
| Twilio | API key + secret | Account SID + Auth Token |
| NF-e / SEFAZ | Certificate | A1 digital certificate (.pfx) + password |

CodeSpar normalizes provider auth into **eight `auth_type` values** so the dashboard wizard knows what fields to render and the proxy executor knows how to inject creds at request time:

| Auth type | Example providers | What it looks like on the wire |
|-----------|-------------------|-------------------------------|
| `api_key` | Asaas, Stripe, SendGrid | Single secret in a header (`Authorization`, `access_token`, etc) |
| `path_secret` | Z-API, Take Blip, Evolution API | One or more secrets embedded in the URL path, plus an optional companion HTTP header |
| `oauth` | Mercado Pago, Asaas (some configs), Melhor Envio | OAuth 2.0 authorization-code flow with managed callback; sandbox/prod hosts split per environment |
| `cert` | Banco do Brasil, Itaú, Bradesco, Santander, Caixa, Sicoob, Sicredi, C6, Original (9 BR banks, live) | mTLS with X.509 client certificate (PEM blob) — operator uploads cert + key + CA via the Provider Connect modal |
| `hmac_signed` | Foxbit (and future LATAM crypto exchanges with the same pattern) | Per-request signature over `timestamp + method + path + body`, derived from a shared key + secret pair |
| `jwt_ecdsa` | Coinbase Developer Platform (Trading / Wallets / Payments) | Per-request ES256 JWT signed with an ECDSA P-256 private key; attached as `Authorization: Bearer <jwt>` |
| `two_header` | Cielo, Transbank, Kushki, Payway | Two co-equal credential headers (e.g. `MerchantId` + `MerchantKey`), no `Authorization` Bearer |
| `none` | Brasil API, public endpoints | No credentials — public/free APIs |

#### `api_key` in detail

The most common shape across the catalog. The dashboard prompts for one secret, the vault stores it under a single ref, and the proxy injects it into the request header at execution time. Encodings vary per provider (some use `Authorization: Bearer`, some use a custom `access_token` header, some use HTTP Basic over a single token) — the catalog row records which.

#### `path_secret` in detail

Some Brazilian providers — Z-API is the canonical one — embed credentials directly in the request path: `https://api.z-api.io/instances/{instance_id}/token/{instance_token}/send-text`. There's also a **companion header** (`Client-Token`) that has to travel alongside, separate from the path-embedded secrets.

When you connect a `path_secret` provider, the dashboard prompts for each ref by name:

```
Z-API instance_id:    [3F20CB2B72E01124220962A6D661BA0A]
Z-API instance_token: [B132A8A5764B1007900F2C49]
Client-Token:         [F72499aef81b94f25bb41ec5d6016ae80S]
```

CodeSpar stores each secret in the vault under its own ref (`z-api.instance_id`, `z-api.instance_token`, `z-api.client_token`) and reassembles the path + header at proxy time. Your code never sees these values — you call `session.execute("z-api/send_text", { phone, message })` and the runtime handles the encoding.

#### `oauth` in detail

OAuth 2.0 authorization-code flow. CodeSpar hosts the redirect URL, exchanges the code for tokens, and stores access + refresh tokens in the vault. Your agent kicks the flow off via `session.authorize(serverId)`; once consent is recorded the session can transparently call any tool on that provider on the user's behalf. Refresh is automatic.

#### `cert` in detail

mTLS for BR open-banking corporate APIs — nine Brazilian banks run on the same `cert` runtime: Banco do Brasil, Itaú, Bradesco, Santander, Caixa, Sicoob, Sicredi, C6, and Original. The operator uploads a PEM-encoded client certificate, private key, and (where the provider requires it) a CA bundle via the Provider Connect modal. The proxy executor caches an `undici` `Agent` per connection and presents the cert on every outbound request.

#### `hmac_signed` in detail

Each request carries a signature derived from a `key` + `secret` pair. The signature covers `timestamp + method + path + body` (exact algorithm per provider; the catalog row records the recipe). Operators paste both halves into the connect modal; the dashboard runs a round-trip probe against `POST /v1/connections/hmac-validate` to verify the signature was accepted before persisting.

#### `jwt_ecdsa` in detail

Asymmetric per-request signing — the operator stores an ECDSA P-256 private key (PEM) plus an opaque `key_name`, and the proxy executor mints a fresh ES256 JWT for every outbound call. Coinbase Developer Platform (CDP) is the canonical example: a single CDP key authenticates against `coinbase-cdp-trading`, `coinbase-cdp-wallets`, and `coinbase-cdp-payments`. The JWT carries `nbf` / `exp` for replay protection (120-second window) and a `uri` claim that binds the token to the exact METHOD + path of the request. Operators validate the imported key via `POST /v1/connections/jwt-validate` before persisting. See [JWT-ECDSA auth](/docs/concepts/jwt-ecdsa-auth) for the full walkthrough.

#### `two_header` in detail

A handful of LATAM acquirers authenticate with **two co-equal static headers** instead of a single Bearer token — for example Cielo's `MerchantId` + `MerchantKey`, or Transbank's `Tbk-Api-Key-Id` + `Tbk-Api-Key-Secret`. The operator pastes both halves into the Provider Connect modal; CodeSpar stores each under its own vault ref and the proxy stamps both headers on every outbound request. Used today by Cielo, Transbank, Kushki, and Payway.

#### `none` in detail

Some catalog entries are public APIs that need no credentials — Brasil API is the canonical example. The connect modal still records a connection row so usage can be metered, but no secret is requested.

<Callout type="info">
Credentials are encrypted with AES-256 at rest and are never returned in API responses. When you view auth configs in the dashboard, only the last 4 characters of each credential are shown.
</Callout>

## Connect Links — end-user authentication

Service auth covers credentials **you** own (the platform's Stripe key, your Twilio account). For credentials **your end users** own — a merchant's Mercado Pago account, a customer's Shopify store — CodeSpar issues **Connect Links**: hosted OAuth pages that handle consent, token exchange, and vault storage without you building a UI.

```typescript
const { redirectUrl } = await session.authorize("stripe");
if (redirectUrl) window.location.href = redirectUrl;
```

Two integration patterns (in-chat and manual onboarding), the full `session.authorize()` signature, backend endpoints, branding customization, and troubleshooting live in the dedicated [Connect Links](/docs/concepts/connect-links) page.

## Security best practices

1. **Use test keys during development.** `csk_test_` keys connect to mock servers and never process real transactions. There is no reason to use live keys outside of production.
2. **Scope keys narrowly.** Restrict each key to only the operations it needs (Restricted mode), so a leaked key can't do more than its job. Issue a separate key per service.
3. **Rotate keys every 90 days.** Automate rotation where possible using your secrets manager.
4. **Never log API keys.** Sanitize logs and error reports to avoid accidentally exposing keys. Mask all but the last 4 characters.
5. **Use environment variables.** Store keys in `process.env`, not in source code. Add `.env` to `.gitignore`.
6. **Monitor key usage.** Check the dashboard regularly for unexpected usage patterns that might indicate a compromised key.
7. **Revoke compromised keys immediately.** If a key is exposed, revoke it in the dashboard and create a new one. Do not wait.

```typescript
// Good: key from environment variable
const codespar = new CodeSpar({
  apiKey: process.env.CODESPAR_API_KEY,
});

// Bad: hardcoded key (will end up in version control)
const codespar = new CodeSpar({
  apiKey: "csk_live_abc123def456...",
});
```

## Next steps

<NextStepsGrid items={[
  { label: "CONCEPT", title: "Connect Links", description: "Hosted OAuth flow for end-user provider connections.", href: "/docs/concepts/connect-links" },
  { label: "CONCEPT", title: "Sessions", description: "Session lifecycle and how stored credentials attach.", href: "/docs/concepts/sessions" },
  { label: "CONCEPT", title: "Billing", description: "How per-settled-transaction pricing is metered.", href: "/docs/concepts/billing" },
  { label: "REFERENCE", title: "Connections API", description: "Manage OAuth connections and re-authorize servers.", href: "/docs/api/connections" },
  { label: "FAQ", title: "FAQ", description: "Common security and compliance questions.", href: "/docs/faq" },
]} />
