Masky API keys are generated at https://masky.ai/developer. No key? See "First: get an API key" in https://masky.ai/skill.md — an AI assistant can connect its user's account with one link, no developer page needed.

# Login with Masky (SSO)

Masky is also an **OAuth 2.0 identity provider**. Add a "Login with Masky" button to your own site: the user signs in and picks **which of their avatars represents them on your site** — the avatar's name + photo become the identity you receive, and (if they grant the `generate` scope) you can call this API to render avatars/turns **on their credits**, as that avatar.

**1. Register your app** (one-time). Use a first-party `mky_` key from https://masky.ai/developer:
```bash
curl -s -X POST https://masky.ai/api/oauth/clients \
  -H "Authorization: Bearer $MASKY_API_KEY" -H "Content-Type: application/json" \
  -d '{"name":"My App","redirectDomains":["myapp.com"],"scopes":["profile","generate"]}'
# -> { "clientId": "mkc_…", "clientSecret": "mks_…", … }   # secret shown ONCE
```
Scopes: `profile` (identity via userinfo), `avatars:read`, `generate` (spend the user's credits on images/turns), `tribes` (tribe membership queries + credit-paid join), `donations` (propose user→user credit gifts; each needs the user's confirmation on masky.ai).

**2. Send the user to consent.** Open in their browser:
```
https://masky.ai/oauth-authorize.html?client_id=mkc_…&redirect_uri=https://myapp.com/callback&scope=profile%20generate&state=<csrf>
```
`redirect_uri`'s host must be in your `redirectDomains` (https only, except localhost). They approve and Masky redirects to `redirect_uri?code=…&state=…`. PKCE (`code_challenge`/S256) is supported for public clients.

**3. Exchange the code for a token:**
```bash
curl -s -X POST https://masky.ai/api/oauth/token \
  -H "Content-Type: application/json" \
  -d '{"grant_type":"authorization_code","code":"<code>","redirect_uri":"https://myapp.com/callback","client_id":"mkc_…","client_secret":"mks_…"}'
# -> { "access_token":"mky_…", "token_type":"Bearer", "scope":"profile generate", "avatar":{ "id","name","picture" } }
```
The `access_token` **is a scoped `mky_` key** — use it as the `Authorization: Bearer` on any endpoint above (subject to its scopes; rendering needs `generate`). Codes are single-use and expire in 5 minutes.

**4. Get the identity:**
```bash
curl -s https://masky.ai/api/oauth/userinfo -H "Authorization: Bearer <access_token>"
# -> { "sub":"ava_…", "name":"Aphrodite", "picture":"https://…", "avatar_id":"…", "scope":"profile generate" }
```
`sub` is a stable pseudonym per (user, your app, avatar) — never the raw Masky uid, and un-correlatable across sites. Treat it as the user's account id on your side.

Users can review and revoke your app anytime from https://masky.ai/developer (Connected apps). MCP users get the same flow via the `register_oauth_client` + `get_sso_integration_guide` tools.

### App / service identities (client_credentials)

**There are no standalone application accounts on Masky.** Every identity — human or app — is an avatar on a user's account, and every identity you accept downstream must resolve through `/oauth/userinfo`. **Never invent local identities in your own system (e.g. `app_yourname`) for a Masky-integrated app** — that breaks the "every user is a verifiable Masky avatar" invariant for every site consuming Masky SSO.

When your app itself needs to act (a backend minting content, an agent running 24/7), give it a **service avatar**: create an avatar for the app on the owner's account, attach it to your client, and use `client_credentials`:

```bash
# Attach a service avatar to your client (owner's mky_ key; also accepted at registration time)
curl -s -X PATCH https://masky.ai/api/oauth/clients/mkc_… \
  -H "Authorization: Bearer $MASKY_API_KEY" -H "Content-Type: application/json" \
  -d '{"serviceAvatarId":"<one of your avatarIds>"}'

# Get a token that acts AS that avatar — no user, no consent; the secret is the credential
curl -s -X POST https://masky.ai/api/oauth/token \
  -H "Content-Type: application/json" \
  -d '{"grant_type":"client_credentials","client_id":"mkc_…","client_secret":"mks_…"}'
# -> { "access_token":"mky_…", "token_type":"Bearer", "scope":"…", "avatar":{ "id","name","picture" } }
```

The token behaves exactly like a user's SSO token: `/oauth/userinfo` returns a normal `ava_` sub with the avatar's name and picture, generation bills the client owner's credits, and the owner can revoke it from Connected apps. Tokens are long-lived — **request one and store it; don't mint a new token per call.**

## Tribes & credit gifts (SSO)

With an SSO token you can integrate a creator's **tribe** (their community with a credit-priced join) and send **credit gifts** between users. `{user}` params accept a Masky uid or twitchUsername. None of these are public: membership queries only answer for the token holder's own tribe or own membership.

```bash
# Tribe info (`tribes` scope) — name, join cost, member count, whether the token's user is a member
curl -s https://masky.ai/api/tribes/<user> -H "Authorization: Bearer <access_token>"
# -> { "user":"…", "tribeName":"…", "joinCost":10, "memberCount":42, "isMember":false, "isOwner":false }

# Membership check — is <member> in <user>'s tribe? Caller must be one of the two.
curl -s "https://masky.ai/api/tribes/<user>/membership?member=<other>" -H "Authorization: Bearer <access_token>"
# -> { "tribeOwner":"…", "member":true, "joinedAt":1750000000000 }

# Join — charges joinCost from the user's credits (free for the creator's active Twitch subs)
curl -s -X POST https://masky.ai/api/tribes/<user>/join -H "Authorization: Bearer <access_token>"
# -> { "success":true, "isSubscriber":false, "charged":10 }
```

Credit gifts are **two-phase** (`donations` scope): your token can only *propose* — the transfer executes only after the user confirms on a masky.ai page with their own session, so a delegated token can never move credits by itself.

```bash
# 1) Propose a gift
curl -s -X POST https://masky.ai/api/donations/intents \
  -H "Authorization: Bearer <access_token>" -H "Content-Type: application/json" \
  -d '{"to":"<user>","amount":5,"note":"great stream!"}'
# -> { "intentId":"…", "status":"pending", "confirmUrl":"https://masky.ai/donate-confirm.html?intent=…", "expiresAt":… }

# 2) Open confirmUrl in a popup — the user clicks "Send credits" there.
#    The page postMessages {type:"masky:gift", status, intentId} to the opener (a hint);
#    poll for the authoritative status. Intents expire after 10 minutes.
curl -s https://masky.ai/api/donations/intents/<intentId> -H "Authorization: Bearer <access_token>"
# -> status: "pending" | "confirmed" | "cancelled" | "expired"

# 3) Gifts this user has sent to someone (totals + history)
curl -s "https://masky.ai/api/donations/sent?to=<user>" -H "Authorization: Bearer <access_token>"
# -> { "to":"…", "total":12.5, "count":3, "gifts":[{ "amount":5, "note":"…", "createdAt":… }] }
```

Gifted credits arrive as global Masky credits. The sender's transferable balance excludes credits locked to a creator's page or already spent, so a propose/confirm can fail with `Insufficient transferable credits`.


Full HTTP API (images, avatars, conversations, rendering — everything an SSO token with `generate` can call): **https://masky.ai/masky-api.md** · entry point: https://masky.ai/skill.md

Generate or manage your Masky API key any time at https://masky.ai/developer
