# UpGrow auth.md

You are an agent. This page tells you how to get access to **UpGrow** (https://www.upgrow.com), an AI-powered Instagram growth service, on behalf of a user. Follow the steps in order.

UpGrow has two kinds of data:

- **Public data** (growth plans, the one-time shop, free tools, blog search): no credential needed. REST endpoints such as <https://www.upgrow.com/api/public/v1/plans> (index: <https://www.upgrow.com/api/public/v1>, OpenAPI: <https://www.upgrow.com/openapi.json>), MCP at <https://www.upgrow.com/mcp>.
- **The user's own account** (scope `account:read`): read your UpGrow account summary: connected accounts, growth numbers and plan status. Read-only: nothing can change the account, start or stop growth, or buy anything. Prices, payment and card details are never returned.

## Step 1 - Discover

- Protected resource metadata (RFC 9728): GET <https://www.upgrow.com/.well-known/oauth-protected-resource> (account REST API) and, for the account MCP server, GET <https://www.upgrow.com/.well-known/oauth-protected-resource/mcp/account>
- Authorization server metadata (RFC 8414): GET <https://www.upgrow.com/.well-known/oauth-authorization-server>. Its `agent_auth` block lists the registration endpoints below.
- Signing keys: GET <https://www.upgrow.com/.well-known/jwks.json>

A protected endpoint you call without a token answers `401` and names its own metadata:

```http
GET https://www.upgrow.com/api/public/v1/me
-> WWW-Authenticate: Bearer resource_metadata="https://www.upgrow.com/.well-known/oauth-protected-resource", scope="account:read"

POST https://www.upgrow.com/mcp/account
-> WWW-Authenticate: Bearer resource_metadata="https://www.upgrow.com/.well-known/oauth-protected-resource/mcp/account", scope="account:read"
```

## Step 2 - Pick a method

1. **You are an MCP client or any OAuth 2.1 client** and can open a browser for the user: use [OAuth](#oauth-for-mcp-clients). The user signs in and approves on UpGrow; you get a 1-hour access token and a refresh token.
2. **You cannot run an OAuth redirect** (CLI agent, background job): use **anonymous registration** (Step 3). You get an API key at once; the user claims it later with one click to unlock `account:read`.

## Step 3 - Register (anonymous)

```http
POST https://www.upgrow.com/api/agents/auth/register
Content-Type: application/json

{ "type": "anonymous", "requested_credential_type": "api_key" }
```

Response `200`:

```json
{
  "registration_id": "ugreg_...",
  "registration_type": "anonymous",
  "credential_type": "api_key",
  "credential": "uga_...",
  "credential_expires": "2026-12-05T00:00:00.000Z",
  "scopes": ["public:read"],
  "claim_url": "https://www.upgrow.com/api/agents/auth/claim",
  "claim_token": "ugclm_...",
  "claim_token_expires": "2026-11-06T00:00:00.000Z",
  "post_claim_scopes": ["public:read", "account:read"]
}
```

Keep `credential` secret. `claim_token` is returned once: hold it until the claim is done, then drop it. An unclaimed credential lasts 30 days, a claimed one 90 days from the claim; the claim window is 24 hours.

## Step 4 - Claim (when the user is available)

Ask UpGrow for a claim link:

```http
POST https://www.upgrow.com/api/agents/auth/claim
Content-Type: application/json

{ "registration_id": "ugreg_...", "claim_token": "ugclm_..." }
```

(You may send `"credential"` instead of `"registration_id"`.) Response `200`:

```json
{ "status": "pending", "nonce": "ugn_...", "claim_url": "https://www.upgrow.com/agent/claim?nonce=ugn_...", "expires_in": 900, "interval": 5 }
```

Give the user the `claim_url`, for example: "Open this link to connect me to your UpGrow account: <claim_url>". They sign in to UpGrow and approve. The link works for 15 minutes.

To know when they are done, repeat the same POST every `interval` seconds (stop after about 5 minutes):

- `200` with a new nonce: still pending. Ignore the new nonce; the link you gave the user stays valid.
- `409 claimed_or_in_flight`: claimed. Your credential now carries `account:read` for that user's account. Stop polling.
- `410 claim_expired`: the claim window closed. Register again.

## Step 5 - Use the credential

```http
GET https://www.upgrow.com/api/public/v1/me
Authorization: Bearer <credential or access_token>
```

Returns the account summary as JSON. Before the claim, a credential gets `403` with `error="insufficient_scope"` here.

The account MCP server takes the same header:

```http
POST https://www.upgrow.com/mcp/account
Authorization: Bearer <credential or access_token>
Content-Type: application/json
Accept: application/json, text/event-stream

{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }
```

Tools: `get_account_summary`, `list_connected_accounts`, `get_growth_stats` (all read-only).

## OAuth for MCP clients

Standard OAuth 2.1 with PKCE (S256), public clients, no client secret.

1. Register your client (RFC 7591):

```http
POST https://www.upgrow.com/api/oauth/register
Content-Type: application/json

{ "client_name": "My agent", "redirect_uris": ["https://agent.example/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none" }
```

Redirect URIs must be https, http on 127.0.0.1, [::1] or localhost, or a private-use scheme for a native app (RFC 8252, for example `cursor://...` or `com.example.app:/callback`). They are matched exactly. PKCE is required for every client.

2. Send the user to <https://www.upgrow.com/oauth/authorize> with `response_type=code`, `client_id`, `redirect_uri`, `scope=account:read`, `state`, `code_challenge`, `code_challenge_method=S256` and, optionally, the RFC 8707 `resource` parameter: <https://www.upgrow.com/mcp/account> for the account MCP server or <https://www.upgrow.com> for the REST API (a token only works at the resource it was issued for; send none and it works at both). The user signs in and approves. You get `code`, `state` and `iss` (<https://www.upgrow.com>) on your redirect URI. Codes last 60 seconds and work once.

3. Exchange the code (form-encoded):

```http
POST https://www.upgrow.com/api/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=...&code_verifier=...&client_id=...&redirect_uri=...
```

Response: `{ "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "ugrt_...", "scope": "account:read" }`. The access token is an ES256 JWT issued by <https://www.upgrow.com>; verify it with the JWKS if you need to.

4. Refresh with `grant_type=refresh_token&refresh_token=...&client_id=...`. Every refresh returns a new refresh token; the old one stops working. Using an old refresh token again revokes the whole grant. A grant lasts at most 90 days from the user's approval; after that, start the OAuth flow again.

## Errors

| Code | Where | What to do |
|---|---|---|
| `invalid_request` | any | Fix the request body or parameters. |
| `unsupported_identity_type` | /api/agents/auth/register | Send `"type": "anonymous"`. |
| `unsupported_credential_type` | /api/agents/auth/register | Send `"requested_credential_type": "api_key"`. |
| `invalid_claim_token` (401/404) | /api/agents/auth/claim | Wrong claim_token or unknown registration. Register again. |
| `claimed_or_in_flight` (409) | /api/agents/auth/claim | Already claimed. Stop polling and use the credential. |
| `claim_expired` (410) | /api/agents/auth/claim | The 24-hour claim window closed. Register again. |
| `invalid_client` (401) | /api/oauth/token | Unknown client_id: register first. |
| `invalid_grant` | /api/oauth/token | Code or refresh token invalid, expired or used. Start the OAuth flow again. |
| `unsupported_grant_type` | /api/oauth/token | Use `authorization_code` or `refresh_token`. |
| `invalid_token` (401) | /api/public/v1/me, /mcp/account | Token invalid, expired or revoked. Refresh it, or start again. |
| `insufficient_scope` (403) | /api/public/v1/me, /mcp/account | The credential is not claimed yet. Do Step 4. |
| `rate_limited` (429) | any | Wait for `Retry-After` seconds, then retry. |

## Revocation

- Agent credential: POST <https://www.upgrow.com/api/agents/auth/revoke> with `{ "credential": "uga_..." }`. Always `200`; the credential stops working at once.
- OAuth tokens (RFC 7009): POST <https://www.upgrow.com/api/oauth/revoke> form-encoded `token=<access or refresh token>`. Always `200`. Revoking a refresh token ends the whole grant.

Human-readable docs: <https://www.upgrow.com/developers>. Users see and revoke what they approved under Settings, Security, Connected AI agents.
