# iwasthere Public API — LLM Quick Reference

## What is iwasthere?
iwasthere is a service that lets event participants find their own photos
via face recognition. This public API exposes the **organizer** side only:
create events, upload photos/videos, check indexing status, get share links, read plan limits.

## Public vs NOT public
- **Public (this API):** event CRUD, photo/video upload, status polling, share links, plan info.
- **NOT public (never will be):** participant **face search**. The server does not verify
  liveness, so exposing face search would allow searching with someone else's face photo.
  There is **no** `/api/search` equivalent. Do not look for one.

## Authentication
All requests require: `Authorization: Bearer iwt_<32bytes base64url>`.
Issue keys at https://iwasthere.pics/dashboard/developer. Scopes: `events:read`, `events:write` (choose read-only or read+write per key).

## Plan gating (how to handle 403)
When a plan limit is exceeded the API returns HTTP 403 with a body like:
```json
{ "error": "ACTIVE_EVENT_LIMIT_EXCEEDED", "upgrade": { "message": "...", "url": "https://iwasthere.pics/dashboard/plan" } }
```
Relay `upgrade.message` to the user as-is and point them to `upgrade.url`.
Gating codes: ACTIVE_EVENT_LIMIT_EXCEEDED, PHOTO_LIMIT_EXCEEDED, FILE_SIZE_EXCEEDED,
VIDEO_NOT_ALLOWED, VIDEO_SIZE_EXCEEDED, VIDEO_LIMIT_EXCEEDED, PRO_REQUIRED.

## Rate limiting
Per API key: free = 60/hour, pro = 600/hour. On exceed → HTTP 429 `RATE_LIMITED` + `Retry-After`.

## End-to-end workflow (photo gallery) — copy/paste curl
```bash
KEY="iwt_YOUR_KEY"
BASE="https://iwasthere.pics/api/public/v1"

# 1) Create an event → capture id + shareLink
EV=$(curl -s -X POST "$BASE/events" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{"name":"Summer Workshop"}')
EVENT_ID=$(echo "$EV" | jq -r .id)          # e.g. 3fa85f64-...
echo "$EV" | jq .shareLink                    # → https://iwasthere.pics/e/abc123  (give this to participants)

# 2) Get a presigned upload URL for each file
PRE=$(curl -s -X POST "$BASE/events/$EVENT_ID/photos/presign" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"files":[{"name":"IMG_0001.jpg","size":3145728,"type":"image/jpeg"}]}')
URL=$(echo "$PRE" | jq -r '.uploads[0].url')
PHOTO_ID=$(echo "$PRE" | jq -r '.uploads[0].photoId')
S3KEY=$(echo "$PRE" | jq -r '.uploads[0].s3Key')

# 3) PUT the raw image directly to S3 (no auth header; match Content-Type)
curl -s -X PUT "$URL" -H "Content-Type: image/jpeg" --data-binary @IMG_0001.jpg

# 4) Complete → triggers thumbnail + face indexing
curl -s -X POST "$BASE/events/$EVENT_ID/photos/complete" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d "{\"uploaded\":[{\"photoId\":\"$PHOTO_ID\",\"s3Key\":\"$S3KEY\",\"fileSize\":3145728}]}"

# 5) Poll until every photo indexingStatus == "done"
curl -s "$BASE/events/$EVENT_ID/photos" -H "Authorization: Bearer $KEY" | jq '.photos[].indexingStatus'

# 6) Share the shareLink from step 1 with participants.
```

### Video (Pro only): 3-step multipart
`POST /events/{id}/videos` (→ uploadId + partUrls) → `PUT` each part (collect ETags) → `POST /events/{id}/videos/{videoId}/complete` with parts[].

### Handling a gating error mid-workflow
```json
// HTTP 403 from step 2 when the free photo limit is hit:
{ "error": "PHOTO_LIMIT_EXCEEDED", "current": 50, "limit": 50,
  "upgrade": { "message": "The free plan allows up to 50 photos per event. Pro allows up to 500.", "url": "https://iwasthere.pics/dashboard/plan" } }
```
→ Relay `upgrade.message` to the user and stop uploading, or call `GET /plan` first to check limits proactively.

## Endpoints
| Method | Path | Summary |
|---|---|---|
| GET | `https://iwasthere.pics/api/public/v1/events` | List my events |
| POST | `https://iwasthere.pics/api/public/v1/events` | Create an event |
| GET | `https://iwasthere.pics/api/public/v1/events/{eventId}` | Get a single event |
| PATCH | `https://iwasthere.pics/api/public/v1/events/{eventId}` | Update an event (name/date/portrait protection) |
| DELETE | `https://iwasthere.pics/api/public/v1/events/{eventId}` | Delete or purge an event |
| GET | `https://iwasthere.pics/api/public/v1/events/{eventId}/photos` | List photos + indexing status |
| POST | `https://iwasthere.pics/api/public/v1/events/{eventId}/photos/presign` | Get photo upload URLs (step 1/2) |
| POST | `https://iwasthere.pics/api/public/v1/events/{eventId}/photos/complete` | Complete photo upload (step 2/2) |
| DELETE | `https://iwasthere.pics/api/public/v1/photos/{photoId}` | Delete a single photo |
| GET | `https://iwasthere.pics/api/public/v1/events/{eventId}/videos` | List videos + processing status |
| POST | `https://iwasthere.pics/api/public/v1/events/{eventId}/videos` | Create video multipart upload session (Pro only, step 1/3) |
| POST | `https://iwasthere.pics/api/public/v1/events/{eventId}/videos/{videoId}/complete` | Complete video multipart upload (step 3/3) |
| GET | `https://iwasthere.pics/api/public/v1/plan` | Get plan/limits |

## MCP server (for AI agents)
An MCP server wraps this API so agents (Claude, Cursor, etc.) can call it via tools.
- **Endpoint (Streamable HTTP, stateless):** `https://7y62bns2zdo3xfpm6azyelalxq0gbelo.lambda-url.ap-northeast-2.on.aws/`
- **Transport:** JSON-RPC over HTTP POST (methods: `initialize`, `tools/list`, `tools/call`). No session state.
- **Auth:** send the same `Authorization: Bearer iwt_<...>` header; the server forwards it to this API. Doc tools need no auth.
- **Tools (13):** `search_docs`, `get_page` (docs, no auth) · `create_event`, `list_events`, `get_event`, `get_event_status`, `patch_event`, `delete_event`, `upload_photo`, `complete_upload`, `get_indexing_status`, `get_share_link`, `get_plan_and_limits` (actions). No face-search tool exists.
- **Tool annotations:** each tool carries MCP hints (`readOnlyHint`/`destructiveHint`/`idempotentHint`/`openWorldHint`) so clients can gate approvals. `delete_event` is `destructiveHint:true`.
- **Photo upload (chained, out-of-band PUT):** `upload_photo` returns presigned URLs + a `contentType` per file; do the actual `PUT` yourself (set the `Content-Type` header to that `contentType`, or S3 returns 403), then call `complete_upload` to trigger indexing.
- **Status:** prefer `get_event_status` — it merges event detail + indexing progress counts (`{ total, done, processing, pending, failed, allDone }`) into a single call instead of `get_event` + `get_indexing_status`.
- Setup guide: https://iwasthere.pics/docs/mcp

```bash
# List available MCP tools
curl -s -X POST "https://7y62bns2zdo3xfpm6azyelalxq0gbelo.lambda-url.ap-northeast-2.on.aws/" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# Call an action tool (forward your API key)
curl -s -X POST "https://7y62bns2zdo3xfpm6azyelalxq0gbelo.lambda-url.ap-northeast-2.on.aws/" -H "Content-Type: application/json" \
  -H "Authorization: Bearer iwt_YOUR_KEY" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"create_event","arguments":{"name":"Summer Workshop"}}}'
```

Full contract with per-endpoint examples: https://iwasthere.pics/openapi.json
Per-tag markdown: https://iwasthere.pics/docs/api/events.md · /photos.md · /videos.md · /plan.md
