# IRLEvents — Full Agent Documentation > Token-gated event platform for the on-chain world. This file is the > self-contained agent reference: auth, endpoints, data model, webhooks, and > examples in one document. For the short version see /llms.txt. For the > machine spec see /api/openapi.json. Version: 2.54.0+ Generated for: AI agents (Claude, ChatGPT, custom integrations) and machine-readable consumers. Humans should prefer https://irlevents.io/help. --- ## 1. What IRLEvents is IRLEvents lets creators host events that are gated by on-chain ownership. An event has one or more **gate groups**; a wallet that holds the required NFT, fungible token, POAP, DAO membership, or Ordinal can RSVP. Eligibility is checked at RSVP time and the qualifying token is briefly locked to prevent double-spending across overlapping events. Two use modes: - **Public discovery** — anyone can list events, read details, see trending, read creator pages, fetch the sitemap. No auth required. - **Acting on behalf of a user** — an `api_*` key issued by that user lets an agent check eligibility, RSVP, cancel, sync wallets, and read the user's own profile. Sensitive actions (key management, billing, 2FA, OAuth unlink, account delete) always require a fresh user JWT, never an api key. --- ## 2. Authentication ### 1.9 OAuth 2.0 (multi-tenant agent apps) For SaaS apps that act on behalf of many users, use the standard Authorization Code flow: 1. Register a client at https://irlevents.io/developers/oauth (self-service). Get `client_id` + `client_secret` (shown once). 2. Send users to `https://irlevents.io/oauth/authorize?client_id=...&...` 3. Exchange the returned code at `POST /api/oauth/token` 4. Use the resulting `oat_*` access token like any api key — `Authorization: Bearer oat_...` works on every agent endpoint. Discovery doc (RFC 8414): https://irlevents.io/.well-known/oauth-authorization-server — generic OAuth libraries auto-configure from this. Token endpoint is per-IP rate limited (30 req / 15 min) to prevent client_secret brute force. ### 2.0 MCP (Model Context Protocol) — zero HTTP code If your agent runtime is Claude Desktop, Claude Code, Cursor, Cline, or any other MCP-compatible client, the simplest integration path is the official `irlevents-mcp` server. It exposes 12 native MCP tools (`list_events`, `check_eligibility`, `rsvp_event`, etc.) wired to a user's `api_*` key. Install via npx (no global install required): ```json { "mcpServers": { "irlevents": { "command": "npx", "args": ["-y", "irlevents-mcp"], "env": { "IRLEVENTS_API_KEY": "api_<64hex>" } } } } ``` The MCP server is a thin wrapper around the same HTTP API documented below — the rest of this doc is still relevant if you need finer control, want to build a custom MCP server, or are integrating from a non-MCP runtime. npm: https://www.npmjs.com/package/irlevents-mcp ### 2.1 API keys (preferred for agents) Format: `api_` followed by 64 hex characters. The user mints a key in their dashboard at https://irlevents.io/profile (API Keys tab). Keys: - Are sha256-hashed at rest — IRLEvents never stores or can recover them - Are revocable any time - Can have an optional expiration timestamp - Carry a per-key hourly rate limit (default 1000 req/hr) - Can be named for tracking (e.g. "Claude Personal Assistant") - Carry **scopes** (least privilege — see 2.1.1) ### 2.1.1 Scopes Each api key is granted a subset of these scopes. Requests outside the granted set return 403 with `{ code: "INSUFFICIENT_SCOPE", required: [...], granted: [...] }`. | Scope | Allows | |---|---| | `profile:read` | GET /api/profile | | `profile:write` | POST /api/profile/assets/sync | | `events:read` | GET /api/events*, /eligibility, /rsvp/status, /trending, /creators/*, /stats/*, /users/:wallet/* | | `events:write` | Host actions: POST /api/events, PUT /api/events/:id, DELETE /api/events/:id, POST /api/events/:id/checkin | | `rsvp:write` | POST/DELETE /api/events/:id/rsvp | | `rewards:read` | GET /api/rewards*, /api/my/claims, /api/my/points, /api/my/rewards | | `rewards:write` | POST/DELETE /api/rewards/:id/claim | Default scope set for new keys: `profile:read`, `events:read`, `rsvp:write` — covers the "personal assistant" pattern (look around, check eligibility, RSVP). Users can grant fewer or more at mint time. Keys minted before scopes shipped have empty `scopes` and retain full access for back-compat. Pass the key as a Bearer token on every request: ``` Authorization: Bearer api_a1b2c3d4... ``` ### 2.2 JWT (for human user sessions) Issued by the OAuth and magic-link auth flows. Same `Authorization: Bearer` header — the API auto-detects the `api_*` prefix vs JWT. Most endpoints accept either; agent-callable lists below use the api-key path. ### 2.3 Rate limit headers Every authenticated response includes: - `X-RateLimit-Limit` — total requests allowed in the current hour window - `X-RateLimit-Remaining` — how many you have left - `X-RateLimit-Reset` — Unix seconds when the window resets A 429 with `Retry-After` means you've spent the budget. Back off, don't loop. If Redis is unavailable the limiter fails open (requests still go through). ### 2.4 What an api key cannot do Any of these endpoints will reject an api key with 401/403: - `POST /api/api-keys/*` (key management itself) - `POST /api/billing/*`, `POST /api/subscriptions/*` - `POST /api/auth/2fa/*` - `POST /api/auth/oauth/unlink` - `DELETE /api/account` - Magic-link / password-reset flows These require a fresh JWT. Surface this clearly in your agent UX so the user knows when to step up to a browser. --- ## 3. Endpoint reference (agent-callable) All paths are relative to `https://irlevents.io`. Where auth is "optional", calls without auth return public data; calls with auth personalize the response. ### 3.1 Profile (own-data) #### `GET /api/profile` Auth: api key (own profile only) Returns the key owner's profile, wallets, and cached assets. Response shape (abbreviated): ```json { "id": "0xabc...lowercased", "displayName": "alice.eth", "bio": "...", "avatarUrl": "/uploads/avatar-...-512.webp", "wallets": [ { "address": "0xabc...", "chain": "evm", "verified": true, "primary": true }, { "address": "BvK...", "chain": "solana", "verified": true } ], "assets": { "evm": { "1": { "nfts": [...], "tokens": [...] }, "8453": { ... } }, "solana": { "nfts": [...] }, "btc": { "ordinals": [...] } }, "assetsUpdatedAt": "2026-05-09T12:00:00Z" } ``` #### `POST /api/profile/assets/sync` Auth: api key Force-refreshes the user's NFT/token holdings across every connected chain. Hits Alchemy / Helius / Hiro / chain-specific APIs — slow (5-30s) and resource-heavy. Don't poll. Once per session is fine; rely on the cache otherwise. ### 3.2 Events (read) #### `GET /api/events` Auth: optional Lists public events. Without `?limit`, returns the full array (legacy behavior preserved for back-compat). With `?limit=N` (1-200), returns a page plus `Link: <...>; rel="next"` and `X-Next-Cursor: ` headers — pass that cursor as `?cursor=...` for the next page. Absence of those headers means you've reached the last page. Recommended for agents iterating large result sets. Other filters: `seriesId`, `hashtag`. Personalized when authed (e.g., flags events the user has RSVPed to). Invalid cursor → 400 INVALID_CURSOR. #### `GET /api/events/:id` Auth: optional Single event with full gate config, host info, RSVP count, and (when authed) the caller's RSVP status. Event ID is the public short code (e.g. `7s5TZhMQqrCs`). #### `GET /api/events/trending` Auth: optional Most RSVPs in the last 14 days. Fast — safe to call on page load. #### `GET /api/events/:id/eligibility` Auth: api key Checks whether the key owner satisfies any gate group on this event. Triggers a lightweight asset re-check (uses cache when fresh). Response: ```json { "eligible": true, "reason": null, "matchedGroupId": "gate_main" } ``` #### `GET /api/events/:id/rsvp/status` Auth: api key Returns the user's RSVP state for this event: ```json { "rsvped": true, "rsvpId": "rsvp_abc...", "groupId": "gate_main", "checkedIn": false, "createdAt": "2026-05-09T12:00:00Z" } ``` ### 3.3 Events (write) #### `POST /api/events/:id/rsvp` Auth: api key Body: `{}` (no body fields required; future versions may accept `guests`). Flow: re-check assets → check eligibility → lock the qualifying token in Redis (5-minute TTL) → write `RsvpRecord` → generate per-event `checkinToken` JWT → return. Possible failure modes: - 403 `NOT_ELIGIBLE` — no gate group matched - 409 `TOKEN_LOCKED` — qualifying token already used on another event in window - 409 `ALREADY_RSVPED` — user already has an RSVP for this event - 410 `EVENT_FULL` — capacity exhausted - 410 `EVENT_PAST` — event start time in the past #### `DELETE /api/events/:id/rsvp` Auth: api key Cancels the user's RSVP and frees the locked token. ### 3.4 Discovery #### `GET /api/users/:wallet/events/eligible` Auth: api key (must be the wallet owner) Returns every public event this wallet currently qualifies for. #### `GET /api/users/:userId/achievements` Auth: optional Computed badges and progress for the user (events attended, chains used, streaks). Public — no auth needed. #### `GET /api/creators/leaderboard` Auth: optional Top creators by RSVPs / events. Cached. #### `GET /api/stats/public` Auth: optional Platform-wide counts (events, RSVPs, creators, chains active). ### 3.5 Discovery (no auth, public web) - `GET /llms.txt` — short agent index (this file's sibling) - `GET /llms-full.txt` — this document - `GET /api/openapi.json` — full OpenAPI 3.0 spec - `GET /api/docs` — interactive Swagger UI (HTML) - `GET /api/sitemap.xml` — every public event + creator URL - `GET /api/guides/agent-guide` — the agent guide as markdown - `GET /api/guides/webhook-guide` — the webhook integration guide as markdown - `GET /api/guides/api-documentation` — full human-readable API doc as markdown - `GET /robots.txt` — crawler policy --- ## 4. Data model ### 4.1 Profile (the user) Identified by canonical wallet address. EVM addresses are lowercased; Solana addresses are case-sensitive; Bitcoin inscription IDs are case-insensitive. A user may attach multiple wallets across multiple chains; one is marked primary. Cached `assets` JSON is the result of the last sync. ### 4.2 Event ```json { "id": "7s5TZhMQqrCs", "title": "Bitcoin 2026 — Side Event", "description": "...", "imageUrl": "/uploads/event-...-1024.webp", "startsAt": "2026-04-27T18:00:00-07:00", "endsAt": "2026-04-27T22:00:00-07:00", "location": { "name": "Resorts World", "city": "Las Vegas", "country": "US" }, "capacity": 200, "category": "conference", "creatorId": "0x...", "gates": { "mode": "any", "groups": [ ... ] }, "rsvpCount": 47, "checkedInCount": 0 } ``` ### 4.3 Gates The gate config is the heart of the platform. Each event has one or more **groups**; a user passes the gate if they satisfy any group (OR), and satisfies a group if they satisfy every requirement in it (AND). ```json { "mode": "any", "groups": [ { "id": "gate_main", "label": "BAYC or MAYC holder", "requirements": [ { "chainId": 1, "standard": "erc721", "contract": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", "minBalance": 1 } ] }, { "id": "gate_vip", "label": "POAP attendee", "requirements": [ { "chainId": 100, "standard": "poap", "eventId": "12345" } ] } ] } ``` Standards supported per requirement: - `erc721` — EVM NFT, requires `contract` - `erc1155` — EVM semi-fungible, requires `contract` + `tokenId` - `erc20` — EVM fungible, requires `contract` + `minBalance` (decimal-adjusted) - `spl-nft` — Solana NFT, requires `contract` (mint address) - `spl-token` — Solana fungible, requires `contract` + `minBalance` - `btc-ordinal` — Bitcoin Ordinal, requires `contract` (inscription ID) - `poap` — POAP, requires `eventId` - `snapshot` — Snapshot DAO membership, requires `space` (ENS-like) - `ton`, `aptos`, `tezos`, `flow`, `cosmos` — chain-native tokens / NFTs ### 4.4 RSVP ```json { "id": "rsvp_abc...", "eventId": "7s5TZhMQqrCs", "userId": "0x...", "groupId": "gate_main", "checkinToken": "", "checkedIn": false, "checkedInAt": null, "createdAt": "2026-05-09T12:00:00Z" } ``` `groupId` records which gate group the user qualified through — useful for attendance analytics. ### 4.5 TokenUse Internal table that prevents the same NFT from being used to RSVP for overlapping events. Held in Redis with a 5-minute TTL during RSVP creation, then persisted. --- ## 5. Supported chains EVM (via Alchemy NFT API, 13 chains): | Chain | Chain ID | |---|---| | Ethereum | 1 | | Polygon | 137 | | Base | 8453 | | Arbitrum | 42161 | | Optimism | 10 | | Avalanche | 43114 | | ApeChain | 33139 | | Abstract | 2741 | | Zora | 7777777 | | ZKSync | 324 | | Blast | 81457 | | BNB | 56 | | Berachain | 80094 | Non-EVM: - **Solana** (via Helius) — SPL NFTs and tokens - **Bitcoin Ordinals** (via Hiro) - **TON, Aptos, Tezos, Flow, Cosmos** — chain-native verification - **POAP** (Gnosis chain 100) — auto-claim flow available - **Snapshot** — DAO space membership --- ## 6. Webhooks Push beats polling. Register a webhook to receive signed JSON payloads on relevant changes. ### 6.1 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 removed by its creator | | `rsvp.created` | A user successfully RSVPs (eligibility passed, token locked) | | `rsvp.canceled` | An RSVP is canceled | | `checkin.completed` | A wallet is checked in at the door | ### 6.2 Register ```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"] }' ``` Response includes a one-time `secret` (`whsec_...`). Store it; you'll need it to verify signatures. The endpoint must be HTTPS, publicly reachable, and respond with 2xx within 10 seconds. ### 6.3 Verify signatures Every delivery includes: - `X-IRLEvents-Signature` — HMAC-SHA256 of the raw JSON body, hex-encoded - `X-IRLEvents-Event` — the event type - `X-IRLEvents-Webhook-ID` — your 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), ); } ``` ### 6.4 Retries Failed deliveries retry with backoff: 1 minute → 5 minutes → 30 minutes → hourly, up to 10 attempts. After 10 consecutive failures the webhook auto-disables. Process idempotently — same delivery may arrive twice. Full reference (with payload schemas for each event type): https://irlevents.io/api/guides/webhook-guide ### 6.5 Server-Sent Events alternative If you can't host a webhook endpoint (local agents, sandboxed runtimes, corp networks), use SSE: ```bash curl -N -H "Authorization: Bearer api_..." \ "https://irlevents.io/api/events/stream" ``` Same payload shape as webhooks. Optional filters: `?eventId=xxx` (single event) and `?types=a,b,c` (comma-separated event types). Heartbeat every 25s keeps the connection open through proxies. Requires `events:read` scope. --- ## 7. End-to-end example Goal: find a trending event, check if the user qualifies, RSVP if so. ```bash KEY="api_a1b2..." BASE="https://irlevents.io" # 1. Find trending events EVENT_ID=$(curl -sH "Authorization: Bearer $KEY" \ "$BASE/api/events/trending" | jq -r '.[0].id') # 2. Check eligibility ELIGIBLE=$(curl -sH "Authorization: Bearer $KEY" \ "$BASE/api/events/$EVENT_ID/eligibility" | jq -r '.eligible') # 3. RSVP if eligible if [ "$ELIGIBLE" = "true" ]; then curl -sH "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -X POST "$BASE/api/events/$EVENT_ID/rsvp" \ -d '{}' fi ``` --- ## 8. Etiquette - **Cache aggressively.** `/api/events`, `/api/events/:id`, and `/api/events/trending` rarely change minute-to-minute. Respect HTTP cache headers. - **Don't poll `assets/sync`.** It hits external NFT providers. Once per user session is plenty; the cache lives on `Profile.assets`. - **Respect `X-RateLimit-Remaining`.** Back off proactively, don't wait for the 429. - **Handle the JWT-only routes gracefully.** When a step requires the user to step up (e.g. add a wallet, link OAuth, update billing), surface a link to https://irlevents.io and let them complete it in the browser. - **Webhooks > polling.** If you need real-time updates, register a webhook. - **Identify yourself.** Set a clear `User-Agent` header so we can reach out if your integration starts misbehaving. --- ## 9. JSON-LD on event pages Every public event page injects schema.org `Event` JSON-LD (name, dates, location, organizer, offers, eventStatus, eventAttendanceMode, image, url). Crawlers can extract structured data without scraping HTML. Same on creator pages (`Person`) and post pages (`Article`). --- ## 10. Roadmap These are not yet shipped. Don't depend on them: - API key **scopes** (`events:read`, `events:write`, `rsvp:write`, `profile:read`) — currently keys are full-access within the agent surface. - OAuth-style consent flow for third-party multi-user apps. - Server-Sent Events stream for real-time event/RSVP changes (alternative to webhooks). - Open `POST /api/events` to api keys (currently JWT-only — gated behind scopes shipping first). --- ## 11. Useful links - Production: https://irlevents.io - Agent guide (markdown): https://irlevents.io/api/guides/agent-guide - Webhook guide (markdown): https://irlevents.io/api/guides/webhook-guide - API documentation (markdown): https://irlevents.io/api/guides/api-documentation - OpenAPI 3.0: https://irlevents.io/api/openapi.json - Swagger UI: https://irlevents.io/api/docs - Sitemap: https://irlevents.io/api/sitemap.xml - Help: https://irlevents.io/help - Privacy: https://irlevents.io/privacy - Terms: https://irlevents.io/terms - Contact: https://irlevents.io/contact If you ship something on top of IRLEvents — tell us. We feature good agents.