# LoyaltyVIP - Agent & Developer Authentication

> How an automated client or AI agent authenticates to the LoyaltyVIP API. This is the machine-readable handshake guide; full prose docs are at https://loyaltyvip.com/developers.

## Summary (metadata)

- **Auth method:** Personal API key, presented as a Bearer token.
- **Token format:** `lvip_live_<hex>`
- **Auth header:** `Authorization: Bearer lvip_live_...`
- **API base URL:** `https://nbhagdwegk.execute-api.us-east-1.amazonaws.com`
- **OpenAPI spec:** https://loyaltyvip.com/openapi.json
- **Key management:** https://loyaltyvip.com/dashboard/developer (human sign-in required to mint a key)
- **MCP server (Streamable HTTP):** https://loyaltyvip.com/mcp

## Discovery (for agents)

A single unauthenticated probe to an API path returns a `401` whose `WWW-Authenticate` header points at the Protected Resource Metadata, so discovery needs no guessing:

- Protected Resource Metadata (RFC 9728): https://loyaltyvip.com/.well-known/oauth-protected-resource
- Authorization Server Metadata (RFC 8414, with `agent_auth` block): https://loyaltyvip.com/.well-known/oauth-authorization-server
- API catalog (RFC 9727): https://loyaltyvip.com/.well-known/api-catalog
- Agent discovery: https://loyaltyvip.com/.well-known/agent.json
- A2A agent card: https://loyaltyvip.com/.well-known/agent-card.json
- MCP server card: https://loyaltyvip.com/.well-known/mcp/server-card.json

## Register

API keys are **not self-issuable by an unauthenticated agent**. A human account owner mints a scoped key and hands it to the agent. There is no anonymous registration endpoint by design (player data is sensitive).

Human registration flow (at the `register_uri`):

1. Sign in at https://loyaltyvip.com (a human account is required).
2. Open **Settings -> Developer**: https://loyaltyvip.com/dashboard/developer
3. Create a key, select scopes, and copy the secret. The secret is shown **once** and stored only as a hash.

The minted credential the agent receives looks like:

```json
{
  "api_key": "lvip_live_3f9c8a7b6d5e4f3a2b1c0d9e8f7a6b5c",
  "scopes": ["read"],
  "token_type": "Bearer",
  "owner": "user@example.com"
}
```

## Claim

The agent claims the key out-of-band (the human pastes it into the agent's config or an `Authorization` header) and presents it on every request:

```http
POST /v1/player HTTP/1.1
Host: nbhagdwegk.execute-api.us-east-1.amazonaws.com
Authorization: Bearer lvip_live_3f9c8a7b6d5e4f3a2b1c0d9e8f7a6b5c
Content-Type: application/json

{ "action": "tier_status" }
```

Successful response:

```json
{ "data": { "success": true, "tiers": [ /* ... */ ] }, "error": null }
```

To revoke, the owner deletes the key at the `revocation_uri` (https://loyaltyvip.com/dashboard/developer); the key stops working immediately.

## Scopes

| Scope | Grants |
|-------|--------|
| `read` | All of your data & features (read) - tiers, trips, bankroll, offers, optimizers, casino directory, etc. |
| `write` | Create/update your data - add trips/sessions, upsert tiers, log bankroll, create share links, etc. |
| `tax` | (sensitive, opt-in) W-2G / tax reads + writes and tax reports. |
| `host` | Host-business actions (host dashboard, leads, marketplace) - for casino hosts. |

Default keys receive `read`. Admin actions and API-key management are **never** available via an API key.

## Endpoints

**Public (no key):**
- `GET /v1/casinos?q=&state=&type=&has_rewards=&limit=&offset=` - search the casino directory
- `GET /v1/casinos/{slug}` - casino detail incl. rewards program + full tier ladder
- `GET /v1/rewards-programs` and `GET /v1/rewards-programs/{id}` - rewards programs + tiers

**Account (scope `read`):** `GET /v1/me`, `GET /v1/tiers`, `GET /v1/trips`, `GET /v1/offers`

**Full action API:** `POST /v1/player` with `{ "action": "...", ... }` - the complete ~70-action player & host surface. Reads need `read`, writes `write`, tax actions `tax`, host actions `host`.

**Feature endpoints (POST, `{action,...}`):** `/v1/comp/calculate`, `/v1/comp/predict`, `/v1/tier-match`, `/v1/rg`, `/v1/hosts/discover`, `/v1/match-hosts`, `/v1/tax-report`.

## Errors

All errors are JSON: `{ "error": { "code": string, "message": string, "status": number, "docs": string } }`.

A request without a valid key returns `401` with:
`WWW-Authenticate: Bearer realm="LoyaltyVIP API", error="invalid_token", resource_metadata="https://loyaltyvip.com/.well-known/oauth-protected-resource"`

Canonical error codes:

| Code | HTTP | Meaning | Agent action |
|------|------|---------|--------------|
| `invalid_request` | 400 | Malformed request or unknown action. | Fix the request; do not retry unchanged. |
| `invalid_token` | 401 | Missing, malformed, revoked, or expired key. | Obtain a fresh key from the owner. |
| `credential_expired` | 401 | The key has expired. | Owner mints a new key. |
| `insufficient_scope` | 403 | Key lacks the required scope, or action not allowed via API key. | Owner re-mints with the needed scope. |
| `not_found` | 404 | Resource does not exist. | Do not retry. |
| `rate_limited` | 429 | Too many requests. | Wait for `Retry-After`, then retry. |
| `invalid_signature` | 401 | HTTP Message Signature failed (signed-bot requests only). | Re-sign per RFC 9421; see the signatures directory. |
| `internal_error` | 500 | Server error. | Retry with exponential backoff (1s, 2s, 4s). |

Rate-limit headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`) accompany responses; honor `Retry-After` on `429`. Send an `Idempotency-Key` header on writes for safe retries.

## Examples

```bash
# Public - casino detail with tier ladder
curl https://nbhagdwegk.execute-api.us-east-1.amazonaws.com/v1/casinos/bellagio-nv-880e8400

# Authenticated - your tier status (full action API)
curl -H "Authorization: Bearer lvip_live_xxxxxxxx" \
  -X POST https://nbhagdwegk.execute-api.us-east-1.amazonaws.com/v1/player \
  -H 'content-type: application/json' -d '{"action":"tier_status"}'
```
