{"slug": "show-hn-agentgate-signed-receipts-for-ai-agent-saas-actions", "title": "Show HN: AgentGate – signed receipts for AI agent SaaS actions", "summary": "Clawdlinux released AgentGate, an API gateway that lets AI agents call SaaS APIs on behalf of users without exposing tokens, and provides signed, gap-free receipts for every action, including failed attempts. The gateway handles OAuth, encrypted token storage, and request proxying, and its receipts can be verified offline by anyone with a copy of the SQLite log and a pinned trust file. The project is available as a Docker image on GHCR and includes a standalone verifier tool.", "body_md": "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.\n\n**Run the released image.** No clone, no build, no Go toolchain — just the\nimage published to GHCR on every tagged release:\n\n```\nmkdir -p data\ndocker run -d --name agentgate \\\n  -p 8080:8080 \\\n  -e AGENTGATE_VAULT_KEY=dev-key-change-in-production-32b \\\n  -e AGENTGATE_ADMIN_SECRET=admin-dev-secret-change-me!! \\\n  -v $(pwd)/data:/data \\\n  ghcr.io/clawdlinux/agentgate:latest\n```\n\nIt bootstraps one agent API key on first boot and logs it once:\n\n```\ndocker logs agentgate | grep agent_key\n# {\"agent_key\":\"ag_live_...\"} — save this, it is never shown again\n```\n\n**Call an action.** Without a linked account this returns `token_missing`\n\n—\nthe point being that a receipt is still committed for the *attempt*, not\njust for successful calls, so the audit trail can't have quiet gaps:\n\n```\ncurl -s -X POST http://localhost:8080/v1/act \\\n  -H \"Authorization: Bearer <agent-key-from-above>\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"service\":\"github\",\"action\":\"list_repos\",\"on_behalf_of\":\"demo-user\",\"params\":{\"per_page\":1}}'\n# {\"error\":\"no token for user demo-user on service github — user must connect their account first\",\"code\":\"token_missing\"}\n```\n\n**Connect a real account.** Register a GitHub OAuth App once at\n[github.com/settings/developers](https://github.com/settings/developers)\n(callback URL `http://localhost:8080/auth/callback/github`\n\n), pass\n`GITHUB_CLIENT_ID`\n\n/`GITHUB_CLIENT_SECRET`\n\nas extra `-e`\n\nflags on the `docker run`\n\nabove, then get the authorization link and open it in a browser:\n\n```\ncurl -s -X POST http://localhost:8080/admin/link \\\n  -H \"X-Admin-Secret: admin-dev-secret-change-me!!\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"user_id\":\"demo-user\",\"service\":\"github\"}'\n# open the returned authorize_url, click Authorize, then re-run the /v1/act call above\n```\n\n**Inspect the receipt ledger.** Open\n[ http://localhost:8080/dashboard/](http://localhost:8080/dashboard/) and\nenter the gateway URL, then the value of\n\n`AGENTGATE_ADMIN_SECRET`\n\n. The\ndashboard checks the visible receipt chain locally. Use the standalone\nverifier below for full cryptographic proof.Verification never depends on the gateway, Docker, or a Go toolchain — it\nreads the SQLite receipt log and a pinned trust file directly. Anyone with a\ncopy of `agentgate.db`\n\nand a trust file can run this, including someone who\nhas never seen this repo:\n\n```\n# while the gateway is up, once: save the trust root as a local file\ncurl -s http://localhost:8080/v1/receipts/pubkey -o trust.json\n\n# download the verifier archive matching your OS/arch from\n# https://github.com/Clawdlinux/agentgate/releases/latest — no other install step\ntar xzf agentgate_<version>_<os>_<arch>.tar.gz\n\n./agentgate-verify --source sqlite --path ./data/agentgate.db --trust-root ./trust.json\n# PASS: 1 receipts verified, head seq=1 hash=...\n```\n\nNo network call happens during verification itself — `trust.json`\n\nis a\npinned local file, and `agentgate-verify`\n\nonly reads the local SQLite file.\nPass `--expected-head <seq>:<hash>`\n\n(from a checkpoint recorded separately,\ne.g. at handoff to an auditor) to also assert completeness, not just chain\nintegrity. For scripts, add `--format json`\n\nto receive one machine-readable\nresult object while keeping the same exit codes. Add `--quiet`\n\n(or `-q`\n\n) to\ntext output to print only the `PASS:`\n\nsummary on successful verification.\n\nTo build from source instead of pulling the release image — useful when\niterating on the gateway itself — `docker compose`\n\nstill works and rebuilds\nthe image on every `up`\n\n:\n\n```\ngit clone https://github.com/Clawdlinux/agentgate.git\ncd agentgate\ndocker compose up -d --build\n```\n\nIt reads OAuth credentials from a `.env`\n\nfile (see `.env.example`\n\n) instead\nof inline `-e`\n\nflags, and the same `/v1/act`\n\nand `/admin/link`\n\ncalls above\nwork against it unchanged.\n\nThe compose image is also built `FROM scratch`\n\n(no shell, no package\nmanager — see the comment in `Dockerfile`\n\n), so verifying \"inside\" the\ncontainer isn't an option here either. Use the same standalone flow from\nthe Auditing section above, pointed at compose's bind-mounted\n`./data/agentgate.db`\n\n: `curl`\n\nthe trust root, download or `make build-verify`\n\nthe verifier, and run it against the host path directly.\n\nStop and restart the container (`docker compose restart agentgate`\n\n) and\nrun the verify command again — the signing identity and the receipt are\nstill there, from the bind-mounted `./data/agentgate.db`\n\n.\n\n```\nAgent → POST /v1/act → [Auth MW] → [Registry] → [Vault: get token] → [Proxy: call upstream] → Response\n┌─────────────────────────────────────────────────────┐\n│                    AI AGENT                          │\n│  POST http://localhost:8080/v1/act                   │\n│  { service, action, on_behalf_of, params }           │\n└──────────────────────┬──────────────────────────────┘\n                       │\n                       ▼\n┌─────────────────────────────────────────────────────┐\n│                 AGENTGATE GATEWAY                    │\n│                                                      │\n│  Auth MW → Registry → Vault → Proxy → Upstream       │\n│  Rate Limiter │ Audit Logger │ OAuth Callbacks        │\n└──────────────────────┬──────────────────────────────┘\n                       │\n                       ▼\n┌─────────────────────────────────────────────────────┐\n│               UPSTREAM SaaS APIs                     │\n│      GitHub  │  Slack  │  Google Workspace           │\n└─────────────────────────────────────────────────────┘\n```\n\nExecute a SaaS API action on behalf of a user.\n\n**Request:**\n\n```\n{\n  \"service\": \"github\",\n  \"action\": \"list_repos\",\n  \"on_behalf_of\": \"user-42\",\n  \"params\": {\"type\": \"owner\", \"sort\": \"updated\"}\n}\n```\n\n**Headers:** `Authorization: Bearer <agent-api-key>`\n\n**Response (success):**\n\n```\n{\n  \"status\": 200,\n  \"body\": [{\"id\": 1, \"name\": \"my-repo\"}],\n  \"latency_ms\": 142\n}\n```\n\n**Response (error):**\n\n```\n{\n  \"error\": \"no token for user user-42 on service github\",\n  \"code\": \"token_missing\"\n}\n```\n\nList available services.\n\nDescribe a service and its actions.\n\nHealth check endpoint.\n\nAll admin endpoints require `X-Admin-Secret`\n\nheader.\n\nCreate a new agent API key.\n\n```\n{\"name\": \"my-agent\", \"allowed_services\": [\"github\", \"slack\"], \"allowed_users\": [\"user-42\"]}\n```\n\nRevoke an API key.\n\nGet OAuth authorization URL for user account linking.\n\n```\n{\"user_id\": \"user-42\", \"service\": \"github\"}\n```\n\nConnect a bearer token for Slack, Stripe, or Calendly. The token is encrypted in the vault and never returned.\n\n```\n{\"user_id\": \"user-42\", \"service\": \"stripe\", \"access_token\": \"<stripe-token>\"}\n```\n\nList linked services for a user (no token values exposed).\n\nOAuth redirect handler — exchanges code for tokens, stores encrypted in vault.\n\n```\nimport \"github.com/Clawdlinux/agentgate/pkg/sdk\"\n\nclient := sdk.NewClient(\"http://localhost:8080\", \"ag_live_...\")\n\n// Call any service\nresp, err := client.Act(ctx, sdk.ActRequest{\n    Service:    \"github\",\n    Action:     \"list_repos\",\n    OnBehalfOf: \"user-42\",\n})\n\n// Convenience helpers\nresp, err := client.GitHub(ctx, \"user-42\", \"list_repos\", nil)\nresp, err := client.Slack(ctx, \"user-42\", \"post_message\", map[string]interface{}{\"channel\": \"#general\", \"text\": \"Hello\"})\nresp, err := client.Act(ctx, sdk.ActRequest{Service: \"google_workspace\", Action: \"list_labels\", OnBehalfOf: \"user-42\"})\n\n// Stripe remains fully functional though unfeatured at launch:\nresp, err := client.Stripe(ctx, \"user-42\", \"list_invoices\", map[string]interface{}{\"limit\": 10})\n\n// Error handling\nif sdk.IsTokenMissing(err) {\n    // User needs to link their account\n}\nif sdk.IsRateLimited(err) {\n    // Back off and retry\n}\n```\n\n**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`\n\n, 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.**OAuth refresh.** Configured OAuth providers refresh tokens expiring within 5 minutes before dispatch. A failed refresh returns`token_expired`\n\nwithout calling the SaaS API.\n\n| Variable | Description | Required |\n|---|---|---|\n`AGENTGATE_VAULT_KEY` |\n32-byte encryption key for the token vault and the receipt signing key | Yes |\n`AGENTGATE_ADMIN_SECRET` |\nSecret for admin API access | Yes |\n`AGENTGATE_PUBLIC_URL` |\nBase URL used to build the OAuth callback (default `http://localhost:8080` ) |\nNo |\n`GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` |\nGitHub OAuth credentials | For GitHub OAuth |\n`SLACK_CLIENT_ID` / `SLACK_CLIENT_SECRET` |\nSlack OAuth credentials | For Slack OAuth |\n`GOOGLE_WORKSPACE_CLIENT_ID` / `GOOGLE_WORKSPACE_CLIENT_SECRET` |\nGoogle OAuth credentials, requesting only the narrow `gmail.labels` scope |\nFor Google Workspace OAuth |\n`STRIPE_CLIENT_ID` / `STRIPE_CLIENT_SECRET` |\nStripe OAuth credentials (Stripe remains configured and functional, just not a featured launch connector) | For Stripe OAuth |\n\nAgent 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`\n\nto create more).\n\nService configurations are YAML files in `configs/services/`\n\n. See `configs/services/google_workspace.yaml`\n\nfor an example. The gateway itself loads the merged `configs/services.yaml`\n\n.\n\n**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\n\n```\n# Build\nmake build\n\n# Run tests\nmake test\n\n# Run locally\nmake run\n\n# Lint\nmake lint\n```\n\nContributions are welcome. See [CONTRIBUTING.md](/Clawdlinux/agentgate/blob/main/CONTRIBUTING.md) for\nprerequisites, build/test/lint instructions, focused pull request\nguidance, and DCO sign-off. Issues labeled [ good first issue](https://github.com/Clawdlinux/agentgate/labels/good%20first%20issue)\nare scoped to be independently testable without needing secrets or\nmaintainer context.\n\nAgentGate is licensed under the Apache License 2.0. See [LICENSE](/Clawdlinux/agentgate/blob/main/LICENSE).", "url": "https://wpnews.pro/news/show-hn-agentgate-signed-receipts-for-ai-agent-saas-actions", "canonical_source": "https://github.com/Clawdlinux/agentgate", "published_at": "2026-08-30 07:00:55+00:00", "updated_at": "2026-08-30 07:22:41.473888+00:00", "lang": "en", "topics": ["ai-agents", "ai-infrastructure", "developer-tools", "ai-safety"], "entities": ["Clawdlinux", "AgentGate", "GitHub", "Slack", "Google Workspace", "GHCR", "SQLite"], "alternates": {"html": "https://wpnews.pro/news/show-hn-agentgate-signed-receipts-for-ai-agent-saas-actions", "markdown": "https://wpnews.pro/news/show-hn-agentgate-signed-receipts-for-ai-agent-saas-actions.md", "text": "https://wpnews.pro/news/show-hn-agentgate-signed-receipts-for-ai-agent-saas-actions.txt", "jsonld": "https://wpnews.pro/news/show-hn-agentgate-signed-receipts-for-ai-agent-saas-actions.jsonld"}}