Integrations
How your org connects third-party accounts like Slack, and revokes them.
Also for this capability: API · CLI · SDKs
How your org connects third-party accounts like Slack, and revokes them.
| Base URL | https://api.hanzo.ai |
| Operations | 50 |
| Auth | Authorization: Bearer $HANZO_API_KEY |
Specification
HIP-1250 · Integrations — The Connection Registry — Draft · read the specification →
An integration is a connection to a provider the platform does not own — Slack,
GitHub, Google, Stripe — held on behalf of a tenant, with the credential in
custody and the connection state in one registry. hanzoai/cloud
apps/integrations is that registry, and /v1/integrations is its address.
The capability has two audiences over the one store: the org plane, where an admin connects a provider for the whole org, and the user plane, where a person links their own accounts. HIP-0126 fixed the vocabulary and HIP-1065 specifies the user plane's custody rules; this HIP is the capability declaration — the store, the target address, the operations, and what the surface refuses.
Motivation
Provider connections were the platform's most duplicated concern: each consumer that needed a token — automations, channels, marketing — was one bad refactor away from its own OAuth path and its own secret row. One registry with one custody exit is what makes "is this org connected to X" a fact askable in one place, and a token something no peer ever stores.
Specification
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
§1 The store
The capability owns one store: the integrations SQLite database
(apps/integrations/store.go:73, sqlpool.Open("integrations", dir)). One row
per (org, provider, owner); tenancy is the org column, which is part of the
primary key. Rows hold non-secret state only. Credentials live solely in the
key store (HIP-0027), sealed under a path built from the tenant — org
connections under the org, user connectors under the (org, user) pair
(apps/integrations/integrations.go:1564). A row MUST NOT carry a secret and a
custody failure MUST NOT leave a row behind.
§2 The address
Every route answers under /v1/integrations: the provider lifecycle
(/{provider}, /{provider}/connect, /callback, /verify, /disconnect),
the provider-specific doors (Slack, GitHub, Discord, Teams, Telegram, GitLab,
OpenRouter), and the user plane at /v1/integrations/connectors. Today the
user plane is served at a second root, /v1/connectors; that pair is carried by
cloud's openapi/misfiled.txt:34 and closes by fold — one store, so per
HIP-0139 §7.1 there is no boundary to split on. The generated clients re-point
on regeneration; the installed CLI's interactive device and PKCE flows
(cli/src/commands/product/generated.rs:794) hard-code the old root and a flow
in flight when the fold lands polls a dead path, so the fold MUST ship in a CLI
release that re-points them.
§3 Operations, typed and declared
The connectors plane is typed end to end (apps/integrations/connectors.go:55):
list, providers, token read, device start/poll, credential intake, refresh,
delete. The org plane's OAuth callbacks, link flows and provider webhooks are
declared with prose beside the route (openapi.Describe,
apps/integrations/integrations.go) because none can be a value: a callback
answers a redirect the provider dictates, a link flow renders HTML, and a
webhook's authentication is the provider's signature over the raw body — the
signature check IS the authentication and it fails closed
(apps/integrations/integrations.go:602).
§4 Tenancy
A request becomes a tenant through the validated principal's org (HIP-0026,
principal.Org); a client-forged org header is refused. Org-plane writes that
change what the whole org is connected to require the caller's own-org admin
bit (principal.IsOrgAdmin — never SuperAdmin,
apps/integrations/integrations.go:1096). User-plane rows are keyed by the
(org, user) pair per HIP-1065, with no admin gate. Provider webhooks arrive
with no principal and are admitted by signature alone.
§5 Metering, events, telemetry, stage
The capability is free, said in those words: plugin/integrations/main.go:29
declares Price: cloud.Free and no spend table names it. It publishes no
events on the bus. Peers do not read its store: whether an org is connected is
asked over the internal plane (apps/integrations/connection_rpc.go:30), and
token handoff to in-process consumers goes through the same seam, never the
address. It emits nothing to observability beyond the request span every route
gets. Stage: ga.
§6 Upstream
The capability derives from none. Every provider client is hand-rolled Go over
net/http against the provider's public API; no vendor SDK is imported and no
third-party project is forked, embedded or mirrored.
Rationale
The alternative to one registry with two planes is two capabilities — org integrations and user connectors — which reads well until the store is drawn: both would open the same database, the defect HIP-0106 names. The two audiences differ in key, not in kind, so they are one capability whose address says which plane a route is on.
Security Considerations
The wrong implementation hands an attacker credentials. The exposures, each
closed in code: a secret in a row or log (custody is the only holder, one
operation returns a token); a cross-tenant read (org in the primary key, the
(org, user) pair as the user-plane row key); a forged provider webhook
(signature verification, fail closed); a concurrent refresh destroying the
credential it refreshes (single-flight with adoption,
apps/integrations/refresh.go:20); and custody-path smuggling (the path is
validated before use).
Four surfaces
| Surface | Reaches this capability as | Coverage |
|---|---|---|
| REST | integrations at its own prefix | 50 operations |
| CLI | hanzo integrations … | 50 of 50 |
| SDK | IntegrationsApi in every published client | 50 methods |
| MCP | tool integrations on https://api.hanzo.ai/v1/mcp | 45 operations, 0 under the document's own id — ask describe for the rest |
Quickstart
export HANZO_API_KEY=sk-... # console.hanzo.ai → API keysThen the first call — a read that needs nothing but the key. GET /v1/integrations, operation get_integrations:
hanzo integrations listimport { Configuration, IntegrationsApi } from 'hanzoai';
const api = new IntegrationsApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getIntegrations();from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import IntegrationsApi
client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = IntegrationsApi(client).get_integrations()cfg := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.IntegrationsAPI.GetIntegrations(context.Background()).Execute()
if err != nil {
return err
}use hanzo_cloud::apis::{configuration::Configuration, integrations_api};
let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();
let result = integrations_api::get_integrations(&cfg, Default::default()).await?;import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.IntegrationsApi;
ApiClient client = new ApiClient();
client.setRequestInterceptor(b -> b.header("Authorization", "Bearer " + System.getenv("HANZO_API_KEY")));
var result = new IntegrationsApi(client).getIntegrations();curl https://api.hanzo.ai/v1/integrations \
-H "Authorization: Bearer $HANZO_API_KEY"MCP reaches integrations through the integrations tool, which names its 45 operations with its own verbs — this one among them, under a name only MCP declares. describe explains any of them:
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "describe",
"arguments": {
"op": "list_connectors"
}
}
}'Answers 200 with object — ok.
Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/integrations/{provider}/callback | OAuth return for any connector |
POST /v1/integrations/{provider}/connect | Acquires the org's credential for one provider. |
POST /v1/integrations/{provider}/disconnect | Revokes (best-effort) and forgets an org's connection: it deletes every custodied KMS secret and the connection row. |
POST /v1/integrations/{provider}/verify | Re-checks a CONNECTED apikey connector's stored credential against the provider, live (hanzo connector verify). |
GET /v1/integrations/{provider} | Returns ONE provider with this org's connection status — the same view list carries, for a single id. |
POST /v1/integrations/connectors/{id}/refresh | Forces a token rotation for a connected connector, ahead of the automatic rotation a token read would do inside the expiry window. |
GET /v1/integrations/connectors/{id}/token | Hands the custodied access token to its owner — the ONE place custody exits. |
DELETE /v1/integrations/connectors/{id} | Forgets a connector: every custodied secret, then the row. |
POST /v1/integrations/connectors/{provider}/credential | Is the direct intake path: a customer-held token/setup-token (Verify) or an externally obtained OAuth bundle from the CLI's local PKCE (Adopt). |
POST /v1/integrations/connectors/{provider}/device/{flow}/poll | Advances a device sign-in. |
POST /v1/integrations/connectors/{provider}/device | Begins a device sign-in and returns the code to show the user plus how to poll for completion. |
GET /v1/integrations/connectors/providers | Lists the user-scoped provider cards — the catalog of what a user can connect, and how. |
GET /v1/integrations/connectors | Lists the caller's OWN connectors across every provider — the set hanzo connector ls prints. |
POST /v1/integrations/discord/interactions | Discord interactions endpoint |
GET /v1/integrations/discord/link/callback | Complete the Discord account link |
GET /v1/integrations/discord/link/discord | Discord sign-in return leg |
GET /v1/integrations/discord/link | Begin linking a Hanzo account from Discord |
POST /v1/integrations/github/claim | Binds installations the App ALREADY holds to the org the caller is acting in — the reconciliation for a grant that happened outside our connect flow. |
POST /v1/integrations/github/fork | Forks a granted repository. |
GET /v1/integrations/github/installations | Lists the GitHub accounts the caller may see the App installed on, each confirmed against the App's own list, plus where to add another. |
POST /v1/integrations/github/issues/backfill | Seeds the native todo with the EXISTING issues across the org's granted repos (default state=open); the webhook keeps them live thereafter. |
POST /v1/integrations/github/repos/{repo}/pages/builds | Requests a Pages rebuild and returns the queued build's status. |
GET /v1/integrations/github/repos/{repo}/pages | Returns the repo's Pages status, live URL, custom domain and build source. |
POST /v1/integrations/github/repos/{repo}/pages | Creates the repo's Pages site and answers 201 Created with it. |
PUT /v1/integrations/github/repos/{repo}/pages | Sets or clears the custom domain (cname) and updates HTTPS enforcement, build type, or source. |
DELETE /v1/integrations/github/repos/{repo}/pages | Deletes the repo's Pages site. |
POST /v1/integrations/github/repos/import | Imports the selected (or all) granted repos into git.hanzo.ai. |
GET /v1/integrations/github/repos | Lists the org's granted GitHub repositories, each annotated with its native import + sync status from the git object plane. |
POST /v1/integrations/github/search | Finds repositories on GitHub. |
POST /v1/integrations/github/webhook | GitHub App webhook |
GET /v1/integrations/gitlab/projects | Lists the projects the org's GitLab connection can reach — membership projects, most recently active first. |
POST /v1/integrations/openrouter/webhook | Receive OpenRouter Broadcast traces as usage rows |
POST /v1/integrations/slack/commands | Slack slash command webhook |
POST /v1/integrations/slack/events | Slack Events API webhook |
GET /v1/integrations/slack/install | Install the Hanzo app into a Slack workspace |
GET /v1/integrations/slack/link/callback | Complete the Slack account link |
GET /v1/integrations/slack/link/slack | Slack sign-in return leg |
GET /v1/integrations/slack/link | Begin linking a Hanzo account from Slack |
POST /v1/integrations/teams/events | Microsoft Teams Bot Framework webhook |
GET /v1/integrations/teams/link/aad | Microsoft sign-in return leg |
GET /v1/integrations/teams/link/callback | Complete the Teams account link |
GET /v1/integrations/teams/link | Begin linking a Hanzo account from Teams |
POST /v1/integrations/telegram/connect | Mints a short, single-use deep-link code bound to the caller's org and returns the t.me link the console navigates to. |
GET /v1/integrations/telegram/link/auth | Telegram Login Widget return leg |
GET /v1/integrations/telegram/link/callback | Complete the Telegram account link |
GET /v1/integrations/telegram/link | Begin linking a Hanzo account from Telegram |
POST /v1/integrations/telegram/webhook | Telegram Bot API webhook |
GET /v1/integrations/whatsapp/webhook | WhatsApp Cloud API subscription challenge |
POST /v1/integrations/whatsapp/webhook | WhatsApp Cloud API webhook |
GET /v1/integrations | Returns every registered integration provider together with THIS org's connection status for it — the catalog the console's Integrations page renders. |
How is this guide?