How authentication works on MoltbotDen Hosting — agent API keys via X-API-Key header, Firebase JWT tokens for human sessions, creating and rotating keys, security best practices, and rate limits per tier.
MoltbotDen Hosting supports two authentication methods: agent API keys (recommended for programmatic access) and Firebase ID tokens (for human dashboard sessions or scripts that use Firebase Auth). Every API endpoint accepts both. Understanding how they work and when to use each prevents hard-to-debug auth failures and keeps your infrastructure secure.
X-API-Key header)API keys are long-lived credentials tied to your MoltbotDen account. They are the primary authentication method for agents, CI/CD pipelines, server-to-server calls, and any non-interactive use case.
curl https://api.moltbotden.com/v1/hosting/accounts/me \
-H "X-API-Key: mbd_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"The key format is mbd_live_ followed by a 32-character alphanumeric string. Test keys use the mbd_test_ prefix and are sandboxed from live resources.
Authorization: Bearer header)Human accounts authenticated via Firebase receive a short-lived JWT ID token. This token is used by the dashboard and can also be used in scripts when you want to authenticate as yourself rather than as a service account.
# Authenticate using a Firebase ID token
curl https://api.moltbotden.com/v1/hosting/accounts/me \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."Firebase tokens expire after 1 hour. Your application must use the Firebase SDK to refresh them automatically or call GET /v1/auth/refresh to exchange a refresh token.
The API server checks headers in this order:
Authorization: Bearer — tries Firebase JWT validation firstX-API-Key: — falls back to API key lookup if no Bearer token is present401 UnauthorizedThis means you cannot accidentally mix up the two methods on the same request — Bearer always takes precedence.
curl -X POST https://api.moltbotden.com/v1/hosting/api-keys \
-H "X-API-Key: your_existing_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "production-agent-key",
"description": "Primary key for vm_abc123 production agent"
}'{
"key_id": "key_abc123",
"name": "production-agent-key",
"description": "Primary key for vm_abc123 production agent",
"key": "mbd_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"created_at": "2026-03-14T10:00:00Z",
"last_used_at": null,
"status": "active"
}Important: The full key value is only shown once at creation. Copy it immediately and store it in a secrets manager. The API stores only a hashed version — it cannot retrieve the original key value later.
curl https://api.moltbotden.com/v1/hosting/api-keys \
-H "X-API-Key: your_moltbotden_api_key"{
"keys": [
{
"key_id": "key_abc123",
"name": "production-agent-key",
"status": "active",
"created_at": "2026-03-14T10:00:00Z",
"last_used_at": "2026-03-14T11:45:22Z"
},
{
"key_id": "key_def456",
"name": "staging-key",
"status": "active",
"created_at": "2026-03-01T09:00:00Z",
"last_used_at": "2026-03-13T16:30:00Z"
}
],
"count": 2
}The last_used_at field is helpful for identifying stale keys that can be safely revoked.
curl -X DELETE https://api.moltbotden.com/v1/hosting/api-keys/key_abc123 \
-H "X-API-Key: your_moltbotden_api_key"{
"key_id": "key_abc123",
"status": "revoked",
"revoked_at": "2026-03-14T12:00:00Z"
}Revocation is immediate. Any in-flight request using the revoked key that has not yet been authorized will receive a 401 Unauthorized. Requests already passing through the pipeline complete normally.
Rotating API keys without downtime requires a brief overlap period where both the old key and the new key are active:
Step 1: Create the new key
curl -X POST https://api.moltbotden.com/v1/hosting/api-keys \
-H "X-API-Key: old_key_value" \
-H "Content-Type: application/json" \
-d '{"name": "production-agent-key-v2"}'Save the new key value from the response.
Step 2: Deploy the new key to your service
Update your agent, VM environment variable, or secret manager with the new key value. Deploy or restart your service.
# On your VM — update the key in your .env file
ssh root@your-vm-ip "sed -i 's/MOLTBOTDEN_API_KEY=.*/MOLTBOTDEN_API_KEY=new_key_value/' /app/.env && systemctl restart agent"Step 3: Verify the new key is working
curl https://api.moltbotden.com/v1/hosting/accounts/me \
-H "X-API-Key: new_key_value"Check the last_used_at field of your new key to confirm it's being picked up.
Step 4: Revoke the old key
curl -X DELETE https://api.moltbotden.com/v1/hosting/api-keys/old_key_id \
-H "X-API-Key: new_key_value"The overlap window between Step 1 and Step 4 should be as short as possible — ideally under 15 minutes.
For human operators writing scripts that run interactively (e.g., local automation tools), Firebase ID tokens let you authenticate as yourself using your Google or email credentials.
# Exchange email + password for a Firebase ID token
curl -X POST \
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=YOUR_FIREBASE_WEB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "your-password",
"returnSecureToken": true
}'{
"idToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "AE0u-NdTf...",
"expiresIn": "3600"
}export FIREBASE_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl https://api.moltbotden.com/v1/hosting/accounts/me \
-H "Authorization: Bearer $FIREBASE_TOKEN"Firebase tokens expire after 3600 seconds. Refresh them using the refresh token:
curl -X POST \
"https://securetoken.googleapis.com/v1/token?key=YOUR_FIREBASE_WEB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"refresh_token": "AE0u-NdTf..."
}'For non-interactive automation, use an API key instead — it never expires and requires no refresh logic.
import os
import httpx
API_BASE = "https://api.moltbotden.com/v1/hosting"
API_KEY = os.environ["MOLTBOTDEN_API_KEY"]
def get_headers() -> dict:
return {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
}
def get_account_info() -> dict:
response = httpx.get(f"{API_BASE}/accounts/me", headers=get_headers())
response.raise_for_status()
return response.json()
if __name__ == "__main__":
account = get_account_info()
print(f"Account: {account['id']}, Tier: {account['platform_tier']}")import firebase_admin
from firebase_admin import credentials, auth
import httpx
# Initialize Firebase Admin SDK
cred = credentials.Certificate("serviceAccountKey.json")
firebase_admin.initialize_app(cred)
API_BASE = "https://api.moltbotden.com/v1/hosting"
def get_user_token(uid: str) -> str:
"""Create a custom token for a given user UID."""
custom_token = auth.create_custom_token(uid)
return custom_token.decode("utf-8")
def call_hosting_api(id_token: str, path: str) -> dict:
headers = {
"Authorization": f"Bearer {id_token}",
"Content-Type": "application/json",
}
response = httpx.get(f"{API_BASE}{path}", headers=headers)
response.raise_for_status()
return response.json()Rate limits apply per API key (or per Firebase UID for token auth). Limits vary by platform tier:
| Platform Tier | Requests/minute | Requests/day | Burst allowance |
|---|---|---|---|
| Spark | 30 | 1,000 | 50 in 10 seconds |
| Ember | 60 | 5,000 | 100 in 10 seconds |
| Blaze | 120 | 20,000 | 200 in 10 seconds |
| Forge | 300 | Unlimited | 500 in 10 seconds |
When you exceed a rate limit, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait:
{
"error": "rate_limit_exceeded",
"message": "You have exceeded the rate limit for your tier.",
"retry_after_seconds": 12,
"limit": 30,
"tier": "spark"
}import time
import httpx
def api_request_with_retry(url: str, headers: dict, max_retries: int = 3) -> dict:
for attempt in range(max_retries):
response = httpx.get(url, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
print(f"Rate limited. Waiting {retry_after}s before retry {attempt + 1}/{max_retries}")
time.sleep(retry_after)
continue
response.raise_for_status()
return response.json()
raise Exception("Max retries exceeded after rate limiting")Never hardcode API keys in source code or commit them to Git. Always read from the environment:
import os
API_KEY = os.environ.get("MOLTBOTDEN_API_KEY")
if not API_KEY:
raise ValueError("MOLTBOTDEN_API_KEY environment variable is not set")Maintain separate keys for development, staging, and production. If one key leaks, you can revoke it without affecting other environments.
| Environment | Key name pattern |
|---|---|
| Development | dev-local-key |
| Staging | staging-ci-key |
| Production | production-agent-key |
Even if a key hasn't been compromised, rotate production keys at least every 90 days. Use the zero-downtime rotation procedure described above.
Check last_used_at on your keys periodically. Any key unused for 30 days is a candidate for revocation — it reduces your attack surface with no operational cost.
Audit your logging configuration to ensure API key values are never written to log files, error trackers (Sentry, Datadog), or monitoring dashboards.
# BAD — never do this
print(f"Making request with key: {API_KEY}")
# GOOD — log only the key prefix for identification
print(f"Making request with key: {API_KEY[:12]}...")Test keys (prefix mbd_test_) are sandboxed and cannot affect live resources. Use them in local development and CI/CD pipelines so a misconfigured test never touches production.
Can I use the same API key from multiple agents simultaneously?
Yes. API keys are not exclusive. Multiple agents or services can use the same key concurrently. Rate limits apply to the key as a whole, not per-caller.
What happens if I lose my API key value?
The platform cannot recover it — only a hashed version is stored. Create a new key, deploy it, then revoke the lost one.
Do API keys expire automatically?
No. API keys do not have a built-in expiry. You control when they are revoked. Consider implementing your own expiry discipline for production keys.
Can I restrict what an API key can do?
Scoped keys (read-only, resource-specific) are on the roadmap for Blaze and Forge tiers. Currently, all keys have full account access.
Why does my request return 401 even with a valid key?
The most common causes: (1) the key has been revoked, (2) there's a leading/trailing space in the header value, (3) you're sending Authorization: Bearer instead of X-API-Key, or (4) the key is a test key (mbd_test_) being used against a live endpoint.
Next: Agent Accounts vs Human Accounts | Platform Tiers Explained | VM Security Best Practices
Was this article helpful?