A thin API gateway that lets AI agents call SaaS APIs (GitHub, Slack, Google Workspace) on behalf of users. Agents never see tokens — the gateway handles OAuth, encrypted token storage, and request proxying. Every action gets a signed, gap-free receipt that anyone can verify offline, without AgentGate's secret key.
Five steps, no host Go toolchain required — Docker builds and runs everything.
git clone https://github.com/Clawdlinux/agentgate.git
cd agentgate
docker compose up -d --buildThe container bootstraps one agent API key on first boot and logs it once:
docker compose logs agentgate | grep agent_key
# {"agent_key":"ag_live_..."} — save this, it is never shown againConnect GitHub. Register a GitHub OAuth App once at
github.com/settings/developers
(callback URL http://localhost:8080/auth/callback/github), then set
GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET in a .env file (see
.env.example) before docker compose up. Get the authorization link and
open it in a browser to grant access:
curl -s -X POST http://localhost:8080/admin/link \
-H "X-Admin-Secret: admin-dev-secret-change-me!!" \
-H "Content-Type: application/json" \
-d '{"user_id":"demo-user","service":"github"}'
# open the returned authorize_url, click AuthorizeCall an action and it returns a real response:
curl -s -X POST http://localhost:8080/v1/act \
-H "Authorization: Bearer <agent-api-key-from-above>" \
-H "Content-Type: application/json" \
-d '{"service":"github","action":"list_repos","on_behalf_of":"demo-user","params":{"per_page":1}}'Verify its receipt — run this inside the container so it reads the same view of the database the gateway just wrote, avoiding a bind-mount read lag some Docker Desktop setups have when reading a live SQLite file from the host:
docker compose exec agentgate sh -c '
wget -qO- http://localhost:8080/v1/receipts/pubkey > /tmp/trust.json
agentgate-verify --source sqlite --path /data/agentgate.db --trust-root /tmp/trust.json
'
# PASS: 1 receipts verified, head seq=1 hash=...Stop and restart the container (docker compose restart agentgate) and
run the verify command again — the signing identity and the receipt are
still there, from the bind-mounted ./data/agentgate.db.
To verify from the host instead, stop the container first (a running
container's WAL writes can lag behind what a separate host process sees
on Docker Desktop's virtual filesystem) or copy the file out; then run
make build-verify && ./bin/agentgate-verify --source sqlite --path ./data/agentgate.db --trust-root trust.json.
Agent → POST /v1/act → [Auth MW] → [Registry] → [Vault: get token] → [Proxy: call upstream] → Response
┌─────────────────────────────────────────────────────┐
│ AI AGENT │
│ POST http://localhost:8080/v1/act │
│ { service, action, on_behalf_of, params } │
└──────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ AGENTGATE GATEWAY │
│ │
│ Auth MW → Registry → Vault → Proxy → Upstream │
│ Rate Limiter │ Audit Logger │ OAuth Callbacks │
└──────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ UPSTREAM SaaS APIs │
│ GitHub │ Slack │ Google Workspace │
└─────────────────────────────────────────────────────┘
Execute a SaaS API action on behalf of a user.
Request:
{
"service": "github",
"action": "list_repos",
"on_behalf_of": "user-42",
"params": {"type": "owner", "sort": "updated"}
}Headers: Authorization: Bearer <agent-api-key>
Response (success):
{
"status": 200,
"body": [{"id": 1, "name": "my-repo"}],
"latency_ms": 142
}Response (error):
{
"error": "no token for user user-42 on service github",
"code": "token_missing"
}List available services.
Describe a service and its actions.
Health check endpoint.
All admin endpoints require X-Admin-Secret header.
Create a new agent API key.
{"name": "my-agent", "allowed_services": ["github", "slack"], "allowed_users": ["user-42"]}Revoke an API key.
Get OAuth authorization URL for user account linking.
{"user_id": "user-42", "service": "github"}List linked services for a user (no token values exposed).
OAuth redirect handler — exchanges code for tokens, stores encrypted in vault.
import "github.com/Clawdlinux/agentgate/pkg/sdk"
client := sdk.NewClient("http://localhost:8080", "ag_live_...")
// Call any service
resp, err := client.Act(ctx, sdk.ActRequest{
Service: "github",
Action: "list_repos",
OnBehalfOf: "user-42",
})
// Convenience helpers
resp, err := client.GitHub(ctx, "user-42", "list_repos", nil)
resp, err := client.Slack(ctx, "user-42", "post_message", map[string]interface{}{"channel": "#general", "text": "Hello"})
resp, err := client.Act(ctx, sdk.ActRequest{Service: "google_workspace", Action: "list_labels", OnBehalfOf: "user-42"})
// Stripe remains fully functional though unfeatured at launch:
resp, err := client.Stripe(ctx, "user-42", "list_invoices", map[string]interface{}{"limit": 10})
// Error handling
if sdk.IsTokenMissing(err) {
// User needs to link their account
}
if sdk.IsRateLimited(err) {
// Back off and retry
}- Agent keys are scoped (service × user). Agents can only access what's explicitly granted.
- Tokens encrypted at rest. AES-256-GCM with 32-byte key from environment.
- No token exposure. Agents never see OAuth tokens — only the gateway touches them.
- Signed receipts. Every authenticated action attempt commits one Ed25519-signed, hash-chained receipt before the response is returned — verify offline with
agentgate-verify, no gateway state or private key needed. - Rate limiting. Per-(agent, service) token bucket prevents runaway API usage.
- OAuth state encrypted with AES-256-GCM and expires after 10 minutes.
| Variable | Description | Required |
|---|---|---|
AGENTGATE_VAULT_KEY |
32-byte encryption key for the token vault and the receipt signing key | Yes |
AGENTGATE_ADMIN_SECRET |
Secret for admin API access | Yes |
AGENTGATE_PUBLIC_URL |
Base URL used to build the OAuth callback (default http://localhost:8080) |
No |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET |
GitHub OAuth credentials | For GitHub OAuth |
SLACK_CLIENT_ID / SLACK_CLIENT_SECRET |
Slack OAuth credentials | For Slack OAuth |
GOOGLE_WORKSPACE_CLIENT_ID / GOOGLE_WORKSPACE_CLIENT_SECRET |
Google OAuth credentials, requesting only the narrow gmail.labels scope |
For Google Workspace OAuth |
STRIPE_CLIENT_ID / STRIPE_CLIENT_SECRET |
Stripe OAuth credentials (Stripe remains configured and functional, just not a featured launch connector) | For Stripe OAuth |
Agent API keys are bootstrapped automatically on first boot and logged once — there is no env var for a pre-supplied key (they are bcrypt-hashed in SQLite; see POST /admin/keys to create more).
Service configurations are YAML files in configs/services/. See configs/services/google_workspace.yaml for an example. The gateway itself loads the merged configs/services.yaml.
- Go 1.22+ — single binary, no runtime dependencies
- SQLite — embedded database for keys, tokens, audit log
- AES-256-GCM — token encryption at rest
- bcrypt — API key hashing
- Docker — containerized deployment
# Build
make build
# Run tests
make test
# Run locally
make run
# Lint
make lintContributions are welcome. See CONTRIBUTING.md for
prerequisites, build/test/lint instructions, focused pull request
guidance, and DCO sign-off. Issues labeled good first issue
are scoped to be independently testable without needing secrets or
maintainer context.
AgentGate is licensed under the Apache License 2.0. See LICENSE.