Teams & API keys
Multi-key teams for hosted MailAgent — invite CI bots and teammates without Stripe.
Manage keys in the Console dashboard or via
/v1/team.
Create a team (first key)
Self-host or ops with Neon access:
npm run issue:key:db -- acme-qa
Save the printed key and team_id. Use the key in
dashboard, MCP, or CI.
Invite another key (dashboard)
- Open Console with an admin team key
- Scroll to Team keys
- Fill label, optional prefix, read-only → Create key
- Copy the key from the one-time banner — it is not shown again
Invite via API
POST /v1/team/keys
Authorization: Bearer <ADMIN_TEAM_KEY>
Content-Type: application/json
{ "label": "ci-bot" }
Scoped CI key: see Scoped keys.
List & revoke
GET /v1/team
DELETE /v1/team/keys/KEY_ID
You cannot revoke the last key of a team. Scoped keys cannot create or revoke other keys.
Plans (no Stripe)
| Plan | Team keys | Active inboxes |
|---|---|---|
| free | 5 | 10 |
| pro | 20 | 100 |
npm run team:plan -- TEAM_ID pro
Legacy hosted key
A single wrangler API_KEY has no team (403 team_required on
/v1/team). Limit: 500 active inboxes. Migrate with
issue:key:db when you need invites or scoped keys.
Team event webhook
One HTTPS URL for every inbound message on all team inboxes — Slack,
PagerDuty, or your own fan-out. Per-inbox callbackUrl still works; team
webhook fires in addition when the inbox was created with a registered team key.
GET /v1/team/webhooks
PUT /v1/team/webhooks
POST /v1/team/webhooks
DELETE /v1/team/webhooks
Authorization: Bearer <ADMIN_TEAM_KEY>
Content-Type: application/json
{ "url": "https://hooks.example.com/mailagent" }
GET returns configured, masked urlMasked, and
events: ["message.received"]. Admin scope required for write.
Payload (POST to your URL): same shape as per-inbox callback, plus
teamId and source: "team_webhook" — see
QA callbacks.
curl -sS https://api.webmailagent.com/v1/team/webhooks \
-H "Authorization: Bearer $MAILAGENT_API_KEY" | jq .