Binds installations the App ALREADY holds to the org the caller is acting in —…
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/claim
| Address | https://api.hanzo.ai/v1/integrations/github/claim |
| Method | POST |
| Operation | post_integrations_github_claim |
| Auth | Authorization: Bearer $HANZO_API_KEY |
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.
An installation IS the grant: GitHub recorded the consent when the App was installed, and our connection row is bookkeeping that never got written because nobody came through our callback. This writes that row from the App's own view, so 23 accounts granted straight from GitHub stop reading as nothing.
The org is taken from the VALIDATED PRINCIPAL and never from the body, because it is the one part GitHub cannot tell us. An installation carries an account login, a type and a repository selection — nothing that names a Hanzo org. So the binding cannot be DERIVED, only asserted, and the only unforgeable assertion available is the org the caller is already acting in. Inferring one from the account name would be a guess the store cannot catch: its key is (org,provider,owner), so a wrong org is a valid row, and a valid row is a mirror pointed at the wrong tenant.
SUPER ADMIN only, for that same reason. A tenant's proof that an account is theirs is GitHub's own consent screen — the connect flow — and without it any org could claim any account the App holds. Platform sudo is already the scope that reads the whole install list, so it is the scope that may bind from it; giving a tenant this verb would hand it every other tenant's repositories.
Idempotent: the row is keyed (org,provider,owner) and connected_at survives an
upsert, so claiming twice rebinds the same account to the same org and reports
it under already. Re-claiming also REFRESHES the installation id, so an
account reinstalled on GitHub — new id, same login — self-heals instead of
minting tokens against a dead installation.
Claiming an account another org holds ADDS this org's row and leaves theirs standing, so no org loses an integration it is using.
Request
2 fields, body application/json (required).
| Field | In | Type | Required | Description |
|---|---|---|---|---|
accounts | body | string[] | — | Accounts names GitHub logins the App is installed on ("hanzoai"). |
all | body | boolean | — | All binds every account the App holds, instead of naming them. |
Response
| Status | Body | Meaning |
|---|---|---|
200 | githubClaimOut | ok |
200 body — 2 fields.
| Field | In | Type | Always | Description |
|---|---|---|---|---|
already | body | string[] | — | Already were bound before the call and are unchanged by it. |
claimed | body | string[] | — | Claimed are the accounts this call bound. |
Failure carries the platform error shape — see Errors.
Examples
hanzo integrations github claimimport { Configuration, IntegrationsApi } from 'hanzoai';
const api = new IntegrationsApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.postIntegrationsGithubClaim({ accounts: ["<accounts>"], all: false });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).post_integrations_github_claim(accounts=["<accounts>"], all=False)cfg := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.IntegrationsAPI.PostIntegrationsGithubClaim(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::post_integrations_github_claim(&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).postIntegrationsGithubClaim();curl -X POST https://api.hanzo.ai/v1/integrations/github/claim \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accounts": [
"<accounts>"
],
"all": false
}'The door reaches integrations through the integrations tool, which names its 45 operations with its own verbs — this one among them, under a name only the door 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"
}
}
}'How is this guide?