Skip to content

Project API Keys & Auto-Provisioning

When a Doable-generated app uses MCP tools or integrations, it needs secure authorization to call the connector proxy at runtime. Doable handles this automatically. No manual key creation is required for published apps.

How it works

sequenceDiagram
    participant User
    participant Pipeline as Deploy Pipeline
    participant DB as PostgreSQL
    participant Vite as Vite Build
    participant Site as Published Site
    participant API as Connector Proxy

    User->>Pipeline: Click "Publish"
    Pipeline->>Pipeline: Scan source for MCP tool calls
    Pipeline->>DB: Create scoped API key (dpk_c_*)
    Pipeline->>DB: Store VITE_DOABLE_PROJECT_KEY (encrypted)
    Pipeline->>DB: Store VITE_DOABLE_API_URL (encrypted)
    Pipeline->>Pipeline: Link latest @doable/sdk
    Pipeline->>Vite: Run production build
    Note over Vite: import.meta.env.VITE_* replaced<br/>with actual values at compile time
    Vite-->>Pipeline: Bundle with key baked in
    Pipeline->>Site: Deploy static files
    Site->>API: fetch(API_URL/connector-proxy/mcp/tool, {Bearer: key})
    API->>API: Validate key hash, origin, tool scope
    API-->>Site: MCP tool response

Auto-provisioning on publish

When you publish a project for the first time, the deploy pipeline automatically:

  1. Scans your source code for MCP tool calls (patterns like doable.mcp.call("tool_name", ...))
  2. Creates a client-tier API key (dpk_c_*) scoped to:
    • Only the tools your app actually uses
    • Only requests from your published domain (origin-bound)
  3. Stores the key as VITE_DOABLE_PROJECT_KEY: an encrypted env var available during builds
  4. Stores the API URL as VITE_DOABLE_API_URL, so the SDK knows where to send requests
  5. Links the latest SDK into the project's node_modules
  6. Builds the project: Vite replaces import.meta.env.VITE_* with literal values

On subsequent publishes, the existing key is reused (not recreated).

The @doable/sdk auto-detection

Generated apps use the SDK like this:

import { createDoableClient } from "@doable/sdk";

const doable = createDoableClient();
const result = await doable.mcp.call("mcp_hpca_list_custodians", { limit: 50 });

No configuration needed. The SDK automatically:

  • In preview mode (editor iframe): Receives a short-lived JWT via postMessage from the editor
  • In published mode: Reads VITE_DOABLE_PROJECT_KEY and VITE_DOABLE_API_URL from import.meta.env (baked in at build time)

This means the same code works identically in preview and production without any changes.

Key tiers

Tier Prefix Use case Origin check Tool scoping
Client dpk_c_ Published frontend apps Required (browser only) Restricted to detected tools
Server dpk_s_ Backend-to-backend calls Skipped Optional

Client keys are auto-provisioned on publish. Server keys are created manually via the Security tab.

Security layers

Origin binding

Client-tier keys require a browser Origin header. This means:

  • Allowed: Requests from your published site (https://my-app.doable.me)
  • Allowed: Requests from *.doable.me wildcard
  • Blocked: Requests from curl or Postman (no Origin)
  • Blocked: Requests from attacker sites (https://evil.com)

Even if someone extracts the key from the JS bundle, they cannot abuse it from a different origin or from server-side tools.

Tool scoping

Auto-provisioned keys can only call the specific tools your app uses. If your app calls mcp_hpca_list_custodians, the key is restricted to that tool. Calling any other tool returns 403 TOOL_NOT_ALLOWED.

Rate limiting

Context Limit
Preview (JWT) 600 requests/minute per project
Deployed (API key) 1,200 requests/minute per project

Operators can customize per-project limits via the Settings, Security tab.

Audit trail

Every connector proxy call is logged to the connector_audit table:

  • Timestamp, project ID, tool name, user ID
  • Success/failure status
  • Response time

Instant revocation

Keys can be revoked immediately from Project, Settings, Security, API Keys. Once revoked, all requests using that key return 401 within milliseconds.

Managing keys (Security tab)

Navigate to Project, Settings, Security to:

  • View all API keys (prefix, tier, creation date, last used)
  • See which tools and origins each key is authorized for
  • Create new keys (manual provisioning)
  • Revoke compromised keys

Fresh server setup

On a new Doable installation (via deployment/server-setup.sh), everything works automatically:

Requirement How it's handled
ENCRYPTION_KEY Generated randomly during setup (openssl rand -hex 32)
NEXT_PUBLIC_API_URL Set during setup to https://api.<your-domain>
JWT_SECRET Generated randomly during setup
DOABLE_DOMAIN Set during setup to your domain
pgcrypto extension Enabled by deployment/server-setup.sh for pgp_sym_encrypt

No additional configuration is needed for the key flow to work.

Environment variables involved

Variable Where Purpose
ENCRYPTION_KEY Server .env Encrypts/decrypts env vars in PostgreSQL
NEXT_PUBLIC_API_URL Server .env Public API URL (becomes VITE_DOABLE_API_URL for projects)
VITE_DOABLE_PROJECT_KEY Per-project env var The API key baked into the JS bundle
VITE_DOABLE_API_URL Per-project env var API base URL baked into the JS bundle

How MCP connectors integrate

When you add an MCP server to your workspace:

  1. Go to Workspace Settings, AI, Connectors, Add MCP server
  2. The connector tools become available to the AI agent immediately
  3. The AI can generate code that calls those tools via doable.mcp.call()
  4. On publish, the auto-provisioner scans the code, detects the tool calls, and scopes the key

The flow is:

Add MCP -> AI generates code using it -> Publish -> Key auto-scoped -> Published site works

No manual key configuration is ever needed.

When you need to re-publish

Re-publish required after these changes

The API key and its tool scope are determined at build time. If you change what your app can access, you must click Publish again.

Re-publish is required when you:

Change Why re-publish is needed
Add a new MCP connector to your workspace The code using the new tool isn't in the bundle yet
Remove an MCP connector The old key scope still allows the removed tool
Change which MCP tools the app calls The key scope needs to match the new tool list
Update the @doable/sdk version The SDK source is copied fresh on each publish
Change workspace integrations New integration calls need to be scanned and scoped

Re-publish is NOT needed when:

  • The MCP server's data changes (e.g. new records in a database)
  • You revoke and recreate a key manually (the new key is stored, next publish picks it up)
  • The MCP server is temporarily offline (the connector-proxy handles retries)

What happens on re-publish

  1. The pipeline re-scans your source for all doable.mcp.call(...) patterns
  2. If new tools are detected, the existing key's scope is expanded (not recreated)
  3. The latest @doable/sdk is linked into the project
  4. Vite rebuilds with the current VITE_DOABLE_PROJECT_KEY and VITE_DOABLE_API_URL
  5. The new bundle is deployed, live within seconds

Tip

You don't lose your URL or break existing bookmarks. Re-publishing updates the site in-place.

Architecture decision: why client keys in the bundle?

The dpk_c_* key is intentionally visible in the compiled JavaScript, similar to how Firebase API keys work. This is safe because:

  1. Origin binding prevents the key from being used outside the published domain
  2. Tool scoping limits what the key can access
  3. Rate limiting prevents abuse even if somehow bypassed
  4. Credentials never reach the browser: the proxy decrypts MCP server credentials server-side
  5. Revocation is instant: compromised keys can be killed immediately

The key is a capability token that says "this origin can call these specific tools at this rate", not a secret that unlocks everything.

Troubleshooting

Published site shows "Loading..." indefinitely

Check:

  1. Key exists: Project, Settings, Security should show an active dpk_c_* key
  2. Env var stored: The VITE_DOABLE_PROJECT_KEY env var should exist for the production target
  3. API URL set: The VITE_DOABLE_API_URL env var should point to your API domain
  4. Re-publish: If you added MCP after first publish, re-publish to get a fresh build with the key

Key not being created on publish

The auto-provisioner only creates keys for production environment deploys. Preview deploys use short-lived JWTs instead.

CORS errors in browser console

Ensure NEXT_PUBLIC_API_URL in your .env points to the correct public API domain. The connector-proxy allows cross-origin requests from *.doable.me subdomains automatically.