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:
- Scans your source code for MCP tool calls (patterns like
doable.mcp.call("tool_name", ...)) - 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)
- Stores the key as
VITE_DOABLE_PROJECT_KEY: an encrypted env var available during builds - Stores the API URL as
VITE_DOABLE_API_URL, so the SDK knows where to send requests - Links the latest SDK into the project's
node_modules - 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
postMessagefrom the editor - In published mode: Reads
VITE_DOABLE_PROJECT_KEYandVITE_DOABLE_API_URLfromimport.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.mewildcard - Blocked: Requests from
curlor 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:
- Go to Workspace Settings, AI, Connectors, Add MCP server
- The connector tools become available to the AI agent immediately
- The AI can generate code that calls those tools via
doable.mcp.call() - On publish, the auto-provisioner scans the code, detects the tool calls, and scopes the key
The flow is:
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¶
- The pipeline re-scans your source for all
doable.mcp.call(...)patterns - If new tools are detected, the existing key's scope is expanded (not recreated)
- The latest
@doable/sdkis linked into the project - Vite rebuilds with the current
VITE_DOABLE_PROJECT_KEYandVITE_DOABLE_API_URL - 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:
- Origin binding prevents the key from being used outside the published domain
- Tool scoping limits what the key can access
- Rate limiting prevents abuse even if somehow bypassed
- Credentials never reach the browser: the proxy decrypts MCP server credentials server-side
- 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:
- Key exists: Project, Settings, Security should show an active
dpk_c_*key - Env var stored: The
VITE_DOABLE_PROJECT_KEYenv var should exist for theproductiontarget - API URL set: The
VITE_DOABLE_API_URLenv var should point to your API domain - 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.