# Build an Agent on IRLEvents

A short walkthrough for AI agents and integrations that want to read, RSVP, and
operate against IRLEvents on behalf of a user.

If you only want to **discover** the platform, start here:
- [`/llms.txt`](https://irlevents.io/llms.txt) — concise machine summary
- [`/llms-full.txt`](https://irlevents.io/llms-full.txt) — self-contained doc dump (one-shot ingest)
- [`/api/openapi.json`](https://irlevents.io/api/openapi.json) — full OpenAPI 3.0 spec
- [`/api/docs`](https://irlevents.io/api/docs) — interactive Swagger UI
- [`/api/guides/webhook-guide`](https://irlevents.io/api/guides/webhook-guide) — webhook integration guide (markdown)
- [`/api/sitemap.xml`](https://irlevents.io/api/sitemap.xml) — every public event + creator

If your client speaks **Model Context Protocol** (Claude Desktop, Claude Code,
Cursor, Cline), skip the HTTP plumbing entirely — install
[`irlevents-mcp`](https://www.npmjs.com/package/irlevents-mcp) and you'll have
12 native tools (`list_events`, `check_eligibility`, `rsvp_event`, etc.) wired
to your api key. See section 0 below.

---

## 0. MCP server (zero-code path)

If your AI client speaks Model Context Protocol — Claude Desktop, Claude Code,
Cursor, Cline, and most modern agent frameworks do — you don't need to write
any HTTP code. Install [`irlevents-mcp`](https://www.npmjs.com/package/irlevents-mcp)
once and the platform's 12 agent-callable endpoints become first-class tools.

### Claude Desktop / Claude Code / Cursor / Cline

```json
{
  "mcpServers": {
    "irlevents": {
      "command": "npx",
      "args": ["-y", "irlevents-mcp"],
      "env": { "IRLEVENTS_API_KEY": "api_..." }
    }
  }
}
```

Restart your client. The model can now call `list_events`, `trending_events`,
`get_event`, `check_eligibility`, `rsvp_event`, `cancel_rsvp`, `rsvp_status`,
`get_my_profile`, `sync_my_assets`, `my_eligible_events`, `top_creators`, and
`platform_stats` directly. Sensitive routes (key management, billing, 2FA)
are intentionally not exposed.

Source + full client matrix: https://github.com/irlevents/irlevents-mcp
(or skip to section 1 below for the manual HTTP path).

---

## 1. Mint an API key

A user grants your agent access by minting a key in their dashboard:

1. Sign in at https://irlevents.io
2. Open **Profile → API Keys**
3. Click **Create key**, give it a name (e.g. *Claude Personal Assistant*)
4. Copy the `api_<64 hex>` value — it's only shown once

Keys are sha256-hashed at rest, revocable any time, and support optional
expiration plus per-key rate limits (default 1000 req/hr).

When you mint, you pick **scopes** — a least-privilege subset:

- `profile:read` — read your profile, wallets, cached assets
- `profile:write` — trigger asset re-sync (slow, external IO)
- `events:read` — read events, eligibility, RSVP status, trending, leaderboards, stats
- `events:write` — host actions: create/edit/delete events, check in attendees
- `rsvp:write` — RSVP and cancel on your behalf
- `rewards:read` — read IRLRewards: points balance, claim history, rewards catalog
- `rewards:write` — claim rewards on your behalf, cancel pending claims

Default for new keys: `profile:read`, `events:read`, `rsvp:write` (the
"personal assistant" set). Add `rewards:*` if your agent needs to act on
the IRLRewards side too — same backend, same auth, same tokens. Requests outside your granted scopes return
**403 with `{ code: "INSUFFICIENT_SCOPE", required, granted }`** — easy
for agents to handle. Keys minted before scopes shipped have empty
`scopes` and retain full access for back-compat.

## 2. Authenticate

Pass the key on the standard `Authorization` header. Same shape as a user JWT —
the API auto-detects the `api_*` prefix:

```http
Authorization: Bearer api_a1b2c3d4...
```

You'll receive `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and
`X-RateLimit-Reset` (unix seconds) headers on every authenticated response.
A 429 means you've blown your hourly budget — back off until `X-RateLimit-Reset`.

## 3. What you can call

### Always agent-callable

| Endpoint | Purpose |
|---|---|
| `GET /api/profile` | Read the key owner's profile, wallets, assets cache |
| `POST /api/profile/assets/sync` | Force-refresh NFT/token holdings across chains |
| `GET /api/events` | List public events with filters |
| `GET /api/events/:id` | Single event with full gate config |
| `GET /api/events/trending` | Most RSVPs in the last 14 days |
| `GET /api/events/:id/eligibility` | Will this user pass the token gate? |
| `GET /api/events/:id/rsvp/status` | Current RSVP state for this event |
| `POST /api/events/:id/rsvp` | RSVP on behalf of the user (locks the qualifying NFT) |
| `DELETE /api/events/:id/rsvp` | Cancel the user's RSVP |
| `GET /api/users/:wallet/events/eligible` | All events this user qualifies for |
| `GET /api/users/:userId/achievements` | Computed badges + progress |
| `GET /api/creators/leaderboard` | Top creators by RSVPs / events |
| `GET /api/stats/public` | Cached platform-wide stats |

### Never agent-callable (user JWT only)

API key management, Stripe / billing, 2FA setup, OAuth link/unlink, account
delete, password reset. The API returns 401/403 if a key is presented to those
endpoints.

## 4. End-to-end example

```bash
KEY="api_a1b2..."
BASE="https://irlevents.io"

# Find an event the user might like
curl -sH "Authorization: Bearer $KEY" "$BASE/api/events/trending" | jq '.[0]'

# Check eligibility
EVENT_ID="abc123"
curl -sH "Authorization: Bearer $KEY" \
  "$BASE/api/events/$EVENT_ID/eligibility" | jq

# RSVP if eligible
curl -sH "Authorization: Bearer $KEY" \
     -H "Content-Type: application/json" \
     -X POST "$BASE/api/events/$EVENT_ID/rsvp" \
     -d '{}' | jq
```

## 4.1 Server-Sent Events (real-time, no webhook endpoint needed)

Can't host an HTTPS webhook endpoint? Use SSE instead. Same payload shape
as webhooks, delivered over a long-lived HTTP stream that any agent can
consume — including local Claude Desktop / Cursor sessions running on
your laptop.

```bash
curl -N -H "Authorization: Bearer api_..." \
  "https://irlevents.io/api/events/stream?types=rsvp.created,checkin.completed"
```

Output is `text/event-stream`:

```
retry: 5000

: connected 2026-05-09T21:00:00Z

event: rsvp.created
data: {"type":"rsvp.created","timestamp":"...","data":{...}}

: ping
```

Filters (optional query params):

- `?eventId=xxx` — only events referencing this event id
- `?types=a,b,c` — comma-separated webhook event types

Heartbeat every 25s keeps the connection open through nginx, Cloudflare,
and most corporate proxies. Reconnect on disconnect — the `retry: 5000`
hint tells SSE clients to retry after 5s.

Requires `events:read` scope.

## 5. Webhooks (push, not pull)

Prefer push over polling? Register a webhook endpoint and IRLEvents will deliver
signed JSON payloads on every relevant change. Same `api_*` key works for the
management endpoints below.

### Available event types

| Type | When it fires |
|---|---|
| `event.created` | A new event is published |
| `event.updated` | Event title, time, location, capacity, or gates change |
| `event.deleted` | Event is removed by its creator |
| `rsvp.created` | A user successfully RSVPs (eligibility passed, token locked) |
| `rsvp.canceled` | An RSVP is canceled (frees up the locked token) |
| `checkin.completed` | A wallet is checked in at the door |

### Register a webhook

```bash
curl -X POST "https://irlevents.io/api/webhooks/register" \
  -H "Authorization: Bearer api_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/irlevents",
    "events": ["event.created", "rsvp.created", "checkin.completed"]
  }'
```

The response includes a one-time `secret` (`whsec_...`) — store it; you'll need
it to verify signatures.

### Verify the signature on receipt

Every delivery includes `X-IRLEvents-Signature` (HMAC-SHA256 of the raw JSON
body, hex-encoded) plus `X-IRLEvents-Event` and `X-IRLEvents-Webhook-ID`.

```typescript
import crypto from "crypto";

function verify(rawBody: string, signature: string, secret: string) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
```

Failed deliveries retry with backoff (1m → 5m → 30m → hourly, up to 10 attempts)
before the webhook auto-disables.

→ Full reference: [WEBHOOK_INTEGRATION_GUIDE.md](./WEBHOOK_INTEGRATION_GUIDE.md)
or fetch live at [`https://irlevents.io/api/guides/webhook-guide`](https://irlevents.io/api/guides/webhook-guide).

## 6. Etiquette

- Cache aggressively — `/api/events` rarely changes minute-to-minute
- Respect `X-RateLimit-Remaining`; ask for a higher limit instead of looping retries
- Don't poll `assets/sync` — it hits external NFT APIs. Once per session is fine
- Report bugs at https://irlevents.io/contact — we want agents to succeed here

## 6.1 OAuth 2.0 (for multi-tenant apps)

Single-user agents (one user, one key in the env) should use api keys.
But if you're building a **multi-tenant app** that acts on behalf of many
IRLEvents users (think: a SaaS, an event-discovery bot, etc.), use the
OAuth 2.0 Authorization Code flow instead.

Three steps:

1. **Register your app** — self-service at
   [`/developers/oauth`](https://irlevents.io/developers/oauth). Pick a
   name, list your redirect URIs, choose the scopes your app needs, and
   you'll get a `client_id` + `client_secret` (shown once — save it).
   Up to 10 clients per user. You can rotate the secret or revoke any
   time. Generic OAuth libraries can auto-discover endpoints from
   [`/.well-known/oauth-authorization-server`](https://irlevents.io/.well-known/oauth-authorization-server).

2. **Send users to the consent page:**

   ```
   https://irlevents.io/oauth/authorize
     ?client_id=irl_<your_id>
     &redirect_uri=<your_callback>
     &response_type=code
     &scope=profile:read events:read rsvp:write
     &state=<random_csrf_value>
   ```

3. **Exchange the auth code for an access token** at the callback:

   ```bash
   curl -X POST https://irlevents.io/api/oauth/token \
     -H "Content-Type: application/json" \
     -d '{
       "grant_type": "authorization_code",
       "code": "<from redirect>",
       "client_id": "irl_...",
       "client_secret": "irlsecret_...",
       "redirect_uri": "<same as step 2>"
     }'
   ```

   You get back `access_token` (`oat_<64hex>`, 30-day) and
   `refresh_token` (90-day). Use the access token exactly like an api
   key — `Authorization: Bearer oat_...` works on every agent-callable
   endpoint, scope-gated.

4. **Refresh** when the access token expires:

   ```bash
   curl -X POST https://irlevents.io/api/oauth/token \
     -H "Content-Type: application/json" \
     -d '{
       "grant_type": "refresh_token",
       "refresh_token": "<your refresh token>",
       "client_id": "irl_...",
       "client_secret": "irlsecret_..."
     }'
   ```

Users can view and revoke any app they've authorized at
**Profile → API Keys → Authorized Apps**.

## 7. Roadmap (what's coming)

- ~~API key **scopes**~~ ✓ shipped v2.56.0
- ~~OAuth-style consent flow~~ ✓ shipped v2.60.0
- ~~Server-Sent Events stream~~ ✓ shipped v2.57.0

If you build something on top of IRLEvents, tell us — we'll feature it.
