# IndexerPal auth.md

You are an agent. IndexerPal indexes web pages and backlinks in Google, Bing and Yandex and verifies them in Google. This document tells you how to get credentials for a user's IndexerPal account. Pick one of the three ways below, then call the API.

- MCP server: `https://indexerpal.com/mcp`
- REST API: `https://indexerpal.com/api/v1` (OpenAPI: `https://indexerpal.com/openapi.json`)
- A2A agent: `https://indexerpal.com/a2a`

## Step 1: Discover

A request without credentials gets `401` with:

```http
WWW-Authenticate: Bearer realm="IndexerPal", resource_metadata="https://indexerpal.com/.well-known/oauth-protected-resource/mcp"
```

- Protected resource metadata (RFC 9728): `https://indexerpal.com/.well-known/oauth-protected-resource` (also `/mcp`, `/api/v1`, `/a2a` suffixes).
- Authorization server metadata (RFC 8414): `https://indexerpal.com/.well-known/oauth-authorization-server`. Its `agent_auth` block describes agent registration.

Scopes:

- `read`: See your balance, projects, link statuses, orders and usage.
- `index`: Create indexing projects and index checks (spends credits), cancel unsent links, read sitemaps.
- `purchase`: Start credit purchases: checkout links for a person to pay, or direct payments where enabled.

Bearer tokens go in the `Authorization` header only (`bearer_methods_supported: ["header"]`).

## Step 2: Pick a method

1. **You are an MCP or OAuth client and can open a browser for the user**: OAuth 2.1 (Step 3a).
2. **The user gives you an API key**: use it as a bearer token (Step 3b).
3. **You only have the user's email**: agent registration, `service_auth` (Step 3c). Anonymous and identity-assertion (ID-JAG) registration are not offered.

## Step 3a: OAuth 2.1 (authorization code with PKCE)

1. Register your client (RFC 7591), once:

```http
POST https://indexerpal.com/oauth/register
Content-Type: application/json

{"client_name": "Your app", "redirect_uris": ["https://your.app/callback"], "token_endpoint_auth_method": "none"}
```

   Redirect URIs: https anywhere, http only on localhost or 127.0.0.1, or your app's own scheme.

2. Send the user to `https://indexerpal.com/oauth/authorize` with `response_type=code`, `client_id`, `redirect_uri`, `code_challenge` (S256), `code_challenge_method=S256`, `scope` (space-separated) and `state`. Optionally `resource=https://indexerpal.com/mcp`. They sign in (or sign up) and approve.
3. Exchange the code at `https://indexerpal.com/oauth/token` with `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id` and `code_verifier`.
4. Access tokens last 60 minutes. Refresh with `grant_type=refresh_token`; every refresh returns a new refresh token, and reusing an old one disconnects the app.

## Step 3b: API keys

The account owner creates keys in the dashboard (API & MCP page). Keys can only be created after the account's first credit purchase. Send `Authorization: Bearer ipk_...` (or `X-API-Key: ipk_...`).

## Step 3c: Register as an agent (service_auth)

Before you register, tell the user you're about to connect to IndexerPal with the scopes above, and confirm.

```http
POST https://indexerpal.com/api/agent/identity
Content-Type: application/json

{"type": "service_auth", "login_hint": "user@example.com", "agent_name": "Your agent", "scope": "read index purchase"}
```

Response:

```json
{
  "registration_id": "reg_...",
  "registration_type": "service_auth",
  "claim_url": "https://indexerpal.com/api/agent/identity/claim",
  "claim_token": "clm_...",
  "claim_token_expires": "...",
  "post_claim_scopes": ["read", "index", "purchase"],
  "claim": {
    "user_code": "123456",
    "expires_in": 600,
    "verification_uri": "https://indexerpal.com/agent/claim?attempt=cla_...",
    "interval": 5
  }
}
```

Keep `claim_token` in memory only; it is returned once. IndexerPal sends no email: the claim is how the user proves they own the address.

## Step 4: Claim

Show the user both in one message:

> Open this link, sign in (or create your IndexerPal account) as user@example.com, and enter this code: **123456**
> https://indexerpal.com/agent/claim?attempt=cla_...

The code goes into IndexerPal's page, not back to you. After 5 wrong codes the registration is locked.

Poll every `interval` seconds:

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

grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=clm_...
```

- `authorization_pending`: keep polling.
- `slow_down`: add 5 seconds to your interval.
- `expired_token`: the code timed out. `POST https://indexerpal.com/api/agent/identity/claim` with `{"claim_token": "clm_..."}` for a new code and link (registrations stay open 24 hours).
- `access_denied`: the user declined. Stop.
- Success:

```json
{"access_token": "...", "token_type": "Bearer", "expires_in": 3600, "scope": "read index purchase", "identity_assertion": "...", "assertion_expires": "..."}
```

The success response comes once. Keep `identity_assertion` (valid 30 days).

When approving, the user may untick scopes you asked for (reading the account always stays). `scope` in this response is what was actually granted: check it before calling anything that needs `index` or `purchase`.

## Step 5: Exchange the assertion

When the access token expires:

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

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>&resource=https://indexerpal.com/mcp
```

`invalid_grant` means the assertion expired or the user disconnected you: start again at Step 3c.

## Step 6: Use

```http
GET https://indexerpal.com/api/v1/account
Authorization: Bearer <access_token>
```

Check `paid`. Indexing needs an account that bought credits at least once; until then you can read and buy. `POST https://indexerpal.com/api/v1/checkout` (MCP: `buy_credits`) returns a Stripe link for the user to pay.

Limits: 120 requests a minute per credential, 20,000 a day per account.

## Errors

| Code | Where | What to do |
| --- | --- | --- |
| `anonymous_not_enabled` | /api/agent/identity | Use service_auth with the user's email. |
| `issuer_not_enabled` | /api/agent/identity | ID-JAG isn't accepted. Use service_auth. |
| `invalid_request` | /api/agent/identity | Fix the body: type and an email in login_hint. |
| `invalid_claim_token` | /api/agent/identity/claim | Register again. |
| `claimed_or_in_flight` | /api/agent/identity/claim | Already claimed or declined. |
| `claim_expired` | /api/agent/identity/claim | Register again. |
| `rate_limited` (429) | any | Wait for Retry-After. |
| `authorization_pending`, `slow_down`, `expired_token`, `access_denied` | /oauth/token | See Step 4. |
| `invalid_grant` | /oauth/token | Expired, used or revoked: start over. |
| `payment_required` (402) | API | Buy credits first. |
| `insufficient_scope` (403) | API | Ask for a credential with that scope. |

## Revocation

`POST https://indexerpal.com/oauth/revoke` with `token=<any token we issued>` disconnects the app or agent (RFC 7009). The user can also disconnect you from their dashboard; your next call gets `401` and `invalid_grant`.

More: https://indexerpal.com/developers/agents
