Authentication
API key management, service authentication, key rotation, and security best practices for the CodeSpar API.
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. |
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 for why this is enforced and how to debug the mismatch.
Using API keys with the SDK
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:
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
- Navigate to Dashboard → API Keys
- Click Create Key
- Name the key (e.g., "Production Backend", "Staging", "CI/CD Pipeline")
- The environment defaults to match the active project's
environmentfield. To mint a key for a different environment than the active project (cross-project work), flip the explicit toggle on the modal. - Optionally restrict the key to specific scopes (choose Restricted in the dashboard)
- Click Create and copy the key immediately: it is only shown once
# Verify your key works
curl https://api.codespar.dev/v1/servers \
-H "Authorization: Bearer csk_live_your_new_key..."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.).
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:
{
"error": "forbidden",
"message": "API key does not have the 'sessions:create' scope.",
"status": 403
}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.
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
- Create a new key in the dashboard with the same settings as the existing key
- Deploy the new key to your application (update environment variables, redeploy)
- Verify that the new key is working by checking API responses and session creation
- Revoke the old key in the dashboard once all instances are using the new key
# 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 dashboardRevoking 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.
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 connects to that server.
How it works
- You store provider credentials (e.g., Stripe secret key, Mercado Pago access token) in the CodeSpar dashboard under Auth Configs
- When a session connects to that server, CodeSpar authenticates using those stored credentials
- Your agent code never sees the provider credentials; they stay encrypted at rest on CodeSpar's infrastructure
// 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 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, hmac_signed, jwt_ecdsa in detail
These three schemes carry cryptographic key material rather than a pasted token, and each runs a validation step before anything is persisted. They get a dedicated subsection under Provider auth schemes below.
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.
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.
Provider auth schemes
Three auth_type values involve cryptographic key material instead of a static token: cert, hmac_signed, and jwt_ecdsa. They share a pattern: the operator supplies key material in the Provider Connect modal, the dashboard validates it before persisting, and the proxy executor produces the wire credential at request time. Per-provider specifics (header names, signing recipes, claim shapes) live on the catalog row, so onboarding a new provider with the same shape is a catalog change, not a runtime change.
mTLS / X.509 (cert)
Mutual TLS authenticates both sides of the HTTPS handshake: the server presents its certificate as usual, and the client presents one signed by a CA the server trusts. There is no bearer token on top; the handshake itself is the credential. Nine Brazilian banks run on this runtime (Banco do Brasil, Itaú, Bradesco, Santander, Caixa, Sicoob, Sicredi, C6, Original). BCB's open-banking spec mandates mTLS for corporate (B2B) endpoints, and the cert is issued to your CNPJ during onboarding with each bank.
The operator uploads three files in the Provider Connect modal:
| Field | Format | What it does |
|---|---|---|
cert | PEM client certificate (.pem, .crt) | Identifies your application to the bank. Must match the CN/CNPJ registered during onboarding. |
key | PEM private key (.key, .pem), RSA or ECDSA | Pair to the client cert. Never leaves the vault, never logged, never echoed back through the API. |
ca | PEM CA bundle (optional) | The bank's CA chain. Only needed when the issuer is not in the system trust store; most BR institutions issue chains rooted in ICP-Brasil. |
The catalog row marks each of these secrets with kind: "cert", which tells the dashboard to render a file uploader instead of a text input and tells the vault to store a multi-line PEM blob.
Validation. On upload, the backend parses the certificate with node:crypto's X509Certificate and stores the subject CN, issuer, validFrom / validTo, and SHA-256 fingerprint as cert_metadata on the connection. The dashboard reads that metadata to surface expiry warnings: amber at 30 days, red at 7 days, critical when expired. The cert + key pair stays bonded; re-uploading one without the other invalidates the connection.
Runtime. The proxy executor caches an undici Agent per (org, server, environment fingerprint) tuple. The first outbound call parses the PEM, builds the TLS context, and opens the keepalive pool; every later call reuses it, so the handshake is paid once, not per request. Rotation is a full re-upload: the fingerprint changes, which invalidates the cached agent automatically. Deleting the connection wipes the vault entry and drops the agent, and in-flight calls fail closed.
Companion header (Bradesco style). Some banks layer a developer key on top of mTLS: the cert proves the corporate entity, the key identifies the calling application. The catalog row declares it with path_secret_header_ref plus auth_header_name (for Bradesco, X-Bradesco-Developer-Key); the dashboard renders one extra text input below the file uploaders and the proxy stamps the header once the TLS connection is up. Debugging rule of thumb: TLS handshake failed means a cert/key/CA mismatch, while a 401 after a successful handshake means this companion key is missing or wrong.
HMAC per-request (hmac_signed)
Every outbound request carries a freshly computed HMAC signature alongside the public key, so the credential is a per-call proof rather than a reusable token. The signature covers timestamp, method, path, and body, which means a captured request cannot be replayed against a different endpoint or outside the timestamp window. Foxbit is the canonical provider; other exchanges with the same shape onboard through catalog rows.
The operator stores two values:
| Field | Visibility | What it is |
|---|---|---|
access_key | Visible (last 4 in the dashboard) | Public identifier of the key pair, sent in cleartext on every request. Foxbit calls this KEY. |
secret | Masked entirely | Private signing secret. Never sent on the wire; used only to compute the HMAC. Foxbit calls this SECRET. |
Validation round-trip. Before persisting, the operator clicks Validate and the dashboard fires POST /v1/connections/hmac-validate, which signs a noop request with the pasted secret and sends it to the provider's echo or health endpoint. A 2xx returns { ok: true }; a failure surfaces the provider's exact error (401 signature mismatch, 401 key invalid) inline, so a bad pair never reaches the vault.
Runtime. On every call the proxy builds the signing string from the catalog row's recipe (for Foxbit: timestamp + method + path + body, timestamp in unix milliseconds), computes hmac_sha256(secret, signing_string), and stamps the provider's headers (Foxbit: X-FB-API-KEY, X-FB-API-TIMESTAMP, X-FB-API-SIGNATURE). There is no token to refresh. Replay protection comes from the timestamp tolerance (typically around 5 seconds), which also means clock drift on the proxy host shows up as intermittent 401 signature mismatch; check NTP before suspecting the key. Header names, concatenation order, timestamp unit, and algorithm all live in the catalog row's hmac block, so adding a new exchange is a catalog drop, not a backend change.
JWT-ES256 (jwt_ecdsa)
Every outbound request carries a freshly minted, short-lived JWT signed with an ECDSA P-256 private key. This is asymmetric: the provider holds only the public half, so there is no shared secret to leak. Signing happens at the application layer, not at the TLS handshake (compare cert); the wire credential is Authorization: Bearer <jwt> on a normal HTTPS call. Coinbase Developer Platform (CDP) is the canonical provider: a single CDP key authenticates against coinbase-cdp-trading, coinbase-cdp-wallets, and coinbase-cdp-payments, so you connect once and the runtime reuses the same vault entry across all three catalog rows.
The operator stores two values:
| Field | Visibility | What it is |
|---|---|---|
key_name | Visible | The provider's identifier for the key. For CDP: organizations/<org>/apiKeys/<id>. Sent in cleartext as the JWT's sub and kid. |
private_key_pem | Masked entirely | PEM-encoded ECDSA P-256 private key. Never leaves the vault; used only to sign the per-request JWT. |
Validation round-trip. Validate key fires POST /v1/connections/jwt-validate, which imports the PEM into a Node crypto.KeyObject and mints a sample JWT against the provider's expected claim shape. It returns { ok: true } on a clean import, or the exact import error (invalid_pem, unsupported_curve) on failure. CDP issues PKCS8-encoded P-256 keys; P-384, secp256k1, and RSA keys will not import. Convert older OpenSSL-format keys with openssl pkcs8 -topk8 -nocrypt.
Runtime. The imported KeyObject is cached per (org, server, environment fingerprint) tuple, mirroring the cert agent cache, so the PEM parse is paid once. For each request the proxy mints a JWT with claims sub and kid from key_name, iss: "cdp", aud: ["cdp_service"], nbf: now, exp: now + 120, and a uri claim of <METHOD> <host><path> that binds the token to the exact request. The JWT header carries alg: "ES256", typ: "JWT", and a random 16-byte nonce. The token is valid for 120 seconds, so clock drift beyond that window produces clustered 401s that look like a key problem but are almost always time. A 401 on one specific tool usually means the uri claim missed: check the catalog row's base_url and the tool path. Per-provider claim shapes (issuer, audience, TTL) live in the catalog row's jwt block.
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.
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 page.
Security best practices
- 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. - 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.
- Rotate keys every 90 days. Automate rotation where possible using your secrets manager.
- Never log API keys. Sanitize logs and error reports to avoid accidentally exposing keys. Mask all but the last 4 characters.
- Use environment variables. Store keys in
process.env, not in source code. Add.envto.gitignore. - Monitor key usage. Check the dashboard regularly for unexpected usage patterns that might indicate a compromised key.
- Revoke compromised keys immediately. If a key is exposed, revoke it in the dashboard and create a new one. Do not wait.
// 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
Triggers
Triggers are CodeSpar's outbound webhook subscriptions. Your app receives signed HTTP callbacks when asynchronous events settle (payment succeeded, invoice issued, notification delivered) or when the platform itself changes state.
Connect Links
Hosted OAuth flow that lets your end users connect their own Stripe, Mercado Pago, Shopify, and other provider accounts, without you building or maintaining a connection UI.