# Account

URL: https://docs.hanzo.ai/docs/openapi/account

> Your own account: API keys you mint and revoke, and org onboarding.

Also for this capability: **API** · [CLI](https://docs.hanzo.ai/docs/cli/account) · [MCP](https://docs.hanzo.ai/docs/mcp-tools/account) · [SDKs](https://docs.hanzo.ai/docs/sdks)

Your own account: API keys you mint and revoke, and org onboarding.

> [All API references →](https://docs.hanzo.ai/docs/openapi) · [Get started →](https://docs.hanzo.ai/docs/guides)

|                |                                        |
| -------------- | -------------------------------------- |
| **Base URL**   | `https://api.hanzo.ai`                 |
| **Operations** | 11                                     |
| **Auth**       | `Authorization: Bearer $HANZO_API_KEY` |

## Specification

> **HIP-1200 · Account — The Caller's Own Surface** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1200-account.md)

`account` is the signed-in caller's own self-service surface: the API keys they
mint and revoke, the call that creates their org, their appearance preference,
their profile photo, the anti-CSRF token their browser echoes on money writes,
and the embed-entitlement probe the console's data-product modules ask. It is
`apps/account` in `hanzoai/cloud` — the server work a statically-exported
console cannot do itself, run as the confidential console client against the
caller's own IAM record (`apps/account/account.go:1-15`).

This HIP is the capability; four facets carry their own deep specifications —
appearance (HIP-1040), avatar (HIP-1042), CSRF (HIP-1043) and org creation
(HIP-1045) — and nothing here overrides them.

### Motivation

Every route here exists because several browser surfaces would otherwise each
grow a privileged writer against one IAM row, and because the concept "my API
key" once had four addresses of which the only honest one 404'd
(`apps/account/account.go:180-184`). One subsystem, one registration, every
subject pinned to the validated caller.

### Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted
as in RFC 2119.

#### §1 Addresses

The capability answers under `/v1/account` and nowhere else:

| operation                                                                 | what                                                                                                                        |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `GET/POST/DELETE /v1/account/keys`                                        | the caller's keys — one noun; the key type (`publishable` \| `secret`) is a field, and a minted secret returns exactly once |
| `POST /v1/account/orgs`                                                   | create the caller's org; first run also moves them in and mints the org's first credential (HIP-1045)                       |
| `GET/POST /v1/account/appearance`                                         | the caller's appearance preference (HIP-1040)                                                                               |
| `POST /v1/account/avatar`, `GET /v1/account/avatar/{org}/{user}/{digest}` | profile photo upload and its credential-free content-addressed read (HIP-1042)                                              |
| `GET /v1/account/csrf`                                                    | mint the token ambient-cookie writes echo (HIP-1043)                                                                        |
| `GET /v1/account/embed`                                                   | brand-app embed entitlement + reachability probe (`apps/account/embed.go`)                                                  |

Today's router still serves each at the bare root (`/v1/keys`, `/v1/orgs`,
`/v1/appearance`, `/v1/avatar`, `/v1/csrf`, `/v1/embed`); those pairs are
carried by `hanzoai/cloud` `openapi/misfiled.txt` and close by fold. The two
`/v1/commerce/topup/*` prefixes on the manifest row
(`manifest/apps.go:61`) name routes the package deleted — the crypto top-up
credited an address no app ever registered
(`apps/account/account.go:216-234`) — and MUST come off the row rather than
be folded anywhere.

Every operation is typed except the two avatar ops — multipart in, raw image
bytes out — which are declared with prose and held as the closed exception
list in `apps/account/typed_wire_test.go`.

#### §2 Store

The capability owns no store. Keys, org membership and the appearance
preference are rows in IAM, written through the confidential `hanzo-console`
client (`apps/account/iam.go`); avatar bytes live in the shared `deps.VFS`
blob seam under this subsystem's own `account/avatars/` prefix
(`apps/account/avatar.go:58`); the CSRF token is a stateless keyed MAC.

#### §3 Tenancy

The subject is the validated principal and MUST NOT be nameable: org and user
come only from gateway-minted, IAM-verified identity
(`principal.Validated`), and the IAM id targeted is derived from those claims,
never from a body or query (`apps/account/account.go:64-70`). A caller can
only mint, revoke, onboard or decorate themselves. When the confidential
client is unwired the surface answers 501 — honestly not configured, never a
fabricated key or org.

Money writes are additionally gated by the CSRF middleware and a per-IP rate
cap (`apps/account/account.go:172-175`); a decorator-applied gate would drop
at registration, so the gate is composed into the registration pipeline.

#### §4 Metering, events, observability, stage

The capability is free (`plugin/account/main.go:22`, `cloud.Free`); no debit
lands through any plane. It publishes no events on the bus and so delivers
nothing to customer webhooks. Beyond the request span every route gets, it
emits only its mount line and warn-level degradation logs
(`apps/account/account.go:136-137`). Its stage is `ga` — the manifest row
carries no stage, which HIP-0139 §8 reads as `ga`.

#### §5 Upstream

The capability derives from no forked, embedded or mirrored OSS project. Two
non-standard-library imports are facts worth naming:
`github.com/hanzoai/account` v0.3.3 (MIT OR Apache-2.0 — the billing-account
rule as a dependency-free library) and `github.com/luxfi/crypto` v1.20.5
(Lux Ecosystem License 1.2 — the keyed-BLAKE3 MAC the CSRF token uses,
`apps/account/csrf.go`).

### Rationale

The alternative was the one the console had: BFF proxy routes and a catch-all
forwarder whose admin service token satisfied commerce's mint gate, so
forwarding was authorization (`apps/account/account.go:18-29`). Serving each
route natively on its real domain, subject-pinned, removed the forwarder and
the allowlist that was the only thing between a signed-in member and the mint.

### Security Considerations

The wrong implementation here hands over spend authority: a minted secret key
is money, so the mint MUST require the validated caller, return the plaintext
once, and rate-limit off-gateway reach. The IAM write path is whole-row
read-merge-write and a partial submission blanks credential material — a
write that cannot first read its row MUST refuse (HIP-1040 §2). The CSRF mint
guards every ambient-cookie money write in the process, including co-resident
commerce writes, off one process-wide key (HIP-1043 §3). The avatar read is a
public door whose safety is entirely in what it can address (HIP-1042 §5).
Org creation is a tenancy-boundary write with its own failure modes
(HIP-1045).

## Four surfaces

| Surface  | Reaches this capability as                      | Coverage                                                                  |
| -------- | ----------------------------------------------- | ------------------------------------------------------------------------- |
| **REST** | `account` at its own prefix                     | 11 operations                                                             |
| **CLI**  | `hanzo account …`                               | 9 of 11 — the CLI pins the document on its own clock                      |
| **SDK**  | `AccountApi` in every published client          | 11 methods                                                                |
| **MCP**  | tool `account` on `https://api.hanzo.ai/v1/mcp` | 6 operations, 3 under the document's own id — ask `describe` for the rest |

## Quickstart

```bash
export HANZO_API_KEY=sk-...   # console.hanzo.ai → API keys
```

Then the first call — a read that needs nothing but the key. `GET /v1/account/csrf`, operation `get_account_csrf`:

**CLI**

```bash
hanzo account csrf
```

**SDK**

**.ts**

```ts
import { Configuration, AccountApi } from 'hanzoai';

const api = new AccountApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getAccountCsrf();
```

**.py**

```python
from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import AccountApi

client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = AccountApi(client).get_account_csrf()
```

**.go**

```go
cfg := hanzoai.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := hanzoai.NewAPIClient(cfg)

resp, _, err := client.AccountAPI.GetAccountCsrf(context.Background()).Execute()
if err != nil {
	return err
}
```

**.rs**

```rust
use hanzo_client::apis::{configuration::Configuration, account_api};

let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();

let result = account_api::get_account_csrf(&cfg, Default::default()).await?;
```

**.java**

```java
import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.AccountApi;

ApiClient client = new ApiClient();
client.setBearerToken(System.getenv("HANZO_API_KEY"));

var result = new AccountApi(client).getAccountCsrf();
```

**HTTP**

```bash
curl https://api.hanzo.ai/v1/account/csrf \
  -H "Authorization: Bearer $HANZO_API_KEY"
```

**MCP**

Tool `account`, op `get_account_csrf` — POST the JSON-RPC envelope to `https://api.hanzo.ai/v1/mcp`.

```bash
curl -X POST https://api.hanzo.ai/v1/mcp \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "jsonrpc": "2.0",
       "id": 1,
       "method": "tools/call",
       "params": {
         "name": "account",
         "arguments": {
           "op": "get_account_csrf",
           "input": {}
         }
       }
     }'
```

Answers `200` with `object` — ok.

## Endpoints

| Endpoint                                                                                                                                 | What it does                                                                                                                                                                                             |
| ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`GET /v1/account/appearance`](https://docs.hanzo.ai/docs/openapi/account/get-account-appearance)                                        | Returns the signed-in caller's own appearance preference — text size, density and accent — read from their IAM account so it is the same on every device and every Hanzo surface.                        |
| [`POST /v1/account/appearance`](https://docs.hanzo.ai/docs/openapi/account/post-account-appearance)                                      | Stores the caller's appearance preference on their IAM account, preserving every other field of the row.                                                                                                 |
| [`GET /v1/account/avatar/{org}/{user}/{digest}`](https://docs.hanzo.ai/docs/openapi/account/get-account-avatar-by-org-by-user-by-digest) | Fetch a profile photo                                                                                                                                                                                    |
| [`POST /v1/account/avatar`](https://docs.hanzo.ai/docs/openapi/account/post-account-avatar)                                              | Set your profile photo                                                                                                                                                                                   |
| [`GET /v1/account/csrf`](https://docs.hanzo.ai/docs/openapi/account/get-account-csrf)                                                    | Mints the anti-forgery token a browser echoes as X-CSRF-Token on every change it asks for.                                                                                                               |
| [`GET /v1/account/embed`](https://docs.hanzo.ai/docs/openapi/account/get-account-embed)                                                  | Reports whether one of this brand's shared embedded apps (cms, erp, help) may be framed by the caller and is actually running, so a console module can choose between the embed and the provision panel. |
| [`PATCH /v1/account/keys/{id}`](https://docs.hanzo.ai/docs/openapi/account/patch-account-keys-by-id)                                     | Changes one key's name, permissions, budget, rate or expiry.                                                                                                                                             |
| [`DELETE /v1/account/keys/{id}`](https://docs.hanzo.ai/docs/openapi/account/delete-account-keys-by-id)                                   | Revokes exactly one API key, by id.                                                                                                                                                                      |
| [`GET /v1/account/keys`](https://docs.hanzo.ai/docs/openapi/account/get-account-keys)                                                    | Returns the API keys the caller may see, newest first: their own, or every key the org holds when the caller administers it.                                                                             |
| [`POST /v1/account/keys`](https://docs.hanzo.ai/docs/openapi/account/post-account-keys)                                                  | Creates a NEW API key and returns its secret ONCE.                                                                                                                                                       |
| [`POST /v1/account/orgs`](https://docs.hanzo.ai/docs/openapi/account/post-account-orgs)                                                  | Creates the caller's organization.                                                                                                                                                                       |

---

[All Hanzo APIs](https://docs.hanzo.ai/docs/openapi) · [Interactive reference](https://docs.hanzo.ai/docs/openapi/reference)
