# PlayerOS API Authentication

> agent **auth.md** — how AI agents **discover**, **pick**, and **claim** credentials for
> the PlayerOS API, then use them. Follows the auth.md convention: Discover → Pick method
> → Claim → Use.

- **Base URL:** `https://api.playeros.ai`
- **Auth method:** property-scoped API keys (`X-Api-Key`, or `Authorization: Bearer pk_…`)
- **Resource metadata:** https://playeros.ai/.well-known/oauth-protected-resource
- **OpenAPI spec:** https://playeros.ai/openapi.json (YAML: https://playeros.ai/openapi.yaml)
- **Human docs:** https://playeros.ai/api-docs

## Discover

Fetch the protected-resource metadata (RFC 9728) to learn how to authenticate:

```http
GET /.well-known/oauth-protected-resource HTTP/1.1
Host: playeros.ai
Accept: application/json
```

```json
{
  "resource": "https://api.playeros.ai",
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["read", "write"],
  "agent_auth": {
    "methods": [
      { "type": "api_key", "in": "header", "name": "X-Api-Key", "key_prefix": "pk_",
        "scopes": ["read", "write"], "registration_url": "https://playeros.ai/dashboard/mcp" }
    ]
  }
}
```

Any unauthenticated API request advertises the same thing in a single probe via the
`WWW-Authenticate` header:

```http
GET /api/players HTTP/1.1
Host: api.playeros.ai
```

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://playeros.ai/.well-known/oauth-protected-resource"
Content-Type: application/json

{ "error": "missing_credential" }
```

## Pick method

PlayerOS offers one credential type for agents, plus an interactive fallback for humans.

| If you are… | Use | How |
|---|---|---|
| A server, agent, or automation | **API key** (recommended) | `X-Api-Key: pk_…` (or `Authorization: Bearer pk_…`) |
| An interactive dashboard session | **User JWT** | Cognito login → `Authorization: Bearer <jwt>` |

Decision tree:

1. **Non-interactive** (no human at the keyboard)? → use an **API key**. Done.
2. **Acting as a signed-in dashboard user**? → use a **User JWT** (see _Alternative_ below).
3. **Need OAuth client registration / DCR / PKCE?** → not offered. PlayerOS authenticates
   agents with API keys; there is no authorization server to register with. Use an API key.

## Claim

Claiming a credential is a one-time ceremony performed by a human admin (Option A) or an
existing admin session (Option B). The resulting `pk_…` secret is shown **once**;
PlayerOS stores only a hash.

### Option A — Dashboard (human)

1. Sign in at https://playeros.ai.
2. Open **Settings → API & MCP** (`/dashboard/mcp`).
3. **Generate Key**, name it (e.g. `Acme AI agent`), and copy the `pk_…` secret.

### Option B — Programmatic (admin JWT)

```http
POST /api/functions/create-api-key HTTP/1.1
Host: api.playeros.ai
Authorization: Bearer <admin-jwt>
Content-Type: application/json

{ "property_id": "<uuid>", "name": "Acme AI agent", "scopes": ["read", "write"] }
```

Success — `201 Created`:

```json
{
  "id": "<uuid>",
  "name": "Acme AI agent",
  "key": "pk_live_xxxxxxxxxxxxxxxxxxxxxxxx",
  "scopes": ["read", "write"],
  "property_id": "<uuid>",
  "created_at": "2026-06-05T00:00:00Z"
}
```

Companion endpoints: `POST /api/functions/list-api-keys` (`{ "property_id": "<uuid>" }`) and
`POST /api/functions/revoke-api-key` (`{ "key_id": "<uuid>" }`).

## Use

Send the key on **every** request in the `X-Api-Key` header:

```http
GET /api/players HTTP/1.1
Host: api.playeros.ai
X-Api-Key: pk_your_key_here
```

A write request (the `Bearer pk_…` form is equivalent):

```http
POST /api/functions/send-campaign HTTP/1.1
Host: api.playeros.ai
Authorization: Bearer pk_your_key_here
Content-Type: application/json
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7

{ "campaignId": "<uuid>", "propertyId": "<uuid>" }
```

Sends honor `Idempotency-Key` (safe retries) and return `X-RateLimit-*` headers; back off
on `429`.

## Scopes

Request the narrowest scope set your integration needs.

| Scope   | Grants                                                            |
|---------|------------------------------------------------------------------|
| `read`  | List and retrieve resources (players, campaigns, content, stats).|
| `write` | Create, update, send, and delete resources.                      |

Keys generated from the dashboard default to `["read","write"]`. Pass an explicit
`scopes` array to `create-api-key` to issue a read-only key.

## Multi-tenancy

Every API key is bound to one property, so you don't need to specify a tenant. A dashboard
user (JWT) with access to multiple properties selects one with the `X-Property-Id: <uuid>`
header.

## Alternative: user JWT (dashboard sessions)

Interactive dashboard sessions authenticate with a Cognito-issued JWT instead of an API
key:

```http
POST /api/auth/login HTTP/1.1
Host: api.playeros.ai
Content-Type: application/json

{ "email": "you@casino.com", "password": "<password>" }
```

```json
{ "token": "<jwt>", "expires_in": 3600 }
```

Then send `Authorization: Bearer <jwt>`. For server-to-server and agent use, prefer API keys.

## Errors

Authentication and authorization failures return a JSON `{ "error": "…" }` body:

| HTTP | Condition token        | Meaning | Agent action |
|------|------------------------|---------|--------------|
| 401  | `missing_credential`   | No API key or token was sent. | Add the `X-Api-Key` header and retry. |
| 401  | `invalid_credential`   | The key is unknown, revoked, or malformed. | Generate a new key in Settings → API & MCP. |
| 401  | `credential_expired`   | The key's `expires_at` has passed. | Issue a new key and retry. |
| 403  | `insufficient_scope`   | The key lacks the required scope (e.g. a write call with a read-only key). | Use a key with the `write` scope. |
| 403  | `wrong_property`       | The key is scoped to a different property. | Use a key for the target property. |
| 429  | `rate_limited`         | Too many requests. Response includes `retry_after`. | Back off and retry after `retry_after` seconds. |

Errors are stable and safe to branch on. Retry `429` after the hint; do not retry
`401`/`403` without changing the credential.

## Revoking a key

Revoke a key anytime from **Settings → API & MCP**, or call
`POST /api/functions/revoke-api-key` with `{ "key_id": "<uuid>" }`. Revocation is immediate.
