Skip to content

Latest commit

 

History

History
403 lines (307 loc) · 6.37 KB

File metadata and controls

403 lines (307 loc) · 6.37 KB

API Reference

Base URL: https://api.frameiq.app (production) | http://localhost:8000 (local)

All endpoints require Authorization: Bearer <clerk_jwt> unless marked public.

Interactive docs available at /docs (Swagger UI) and /redoc.


Videos

Submit a video

POST /videos

Body

{
  "youtube_url": "https://www.youtube.com/watch?v=abc123"
}

Response 202 Accepted

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "youtube_url": "https://www.youtube.com/watch?v=abc123",
  "status": "queued",
  "created_at": "2026-06-07T10:00:00Z"
}

Errors

Code Reason
400 Invalid YouTube URL
402 Plan limit reached (too many videos)
409 Video already indexed for this user

List videos

GET /videos?page=1&limit=20&status=ready

Query params

Param Type Default Description
page int 1 Page number
limit int 20 Results per page (max 100)
status string Filter by status

Response 200 OK

{
  "items": [
    {
      "id": "550e8400-...",
      "title": "ML Lecture 12: Linear Regression",
      "youtube_id": "abc123",
      "status": "ready",
      "frame_count": 20,
      "duration_secs": 600,
      "created_at": "2026-06-07T10:00:00Z",
      "indexed_at": "2026-06-07T10:05:00Z"
    }
  ],
  "total": 3,
  "page": 1,
  "limit": 20
}

Get video

GET /videos/{video_id}

Response 200 OK

{
  "id": "550e8400-...",
  "title": "ML Lecture 12",
  "status": "indexing",
  "frame_count": null,
  "duration_secs": 600,
  "job": {
    "stage": "describe",
    "progress": 45
  }
}

Delete video

DELETE /videos/{video_id}

Deletes: Qdrant collection, R2 objects, PostgreSQL rows.

Response 204 No Content


Retry failed video

POST /videos/{video_id}/retry

Re-enqueues ingestion from the failed stage.

Response 202 Accepted

{ "status": "queued" }

Get job progress

GET /videos/{video_id}/jobs

Poll this endpoint to track ingestion progress.

Response 200 OK

{
  "video_id": "550e8400-...",
  "status": "indexing",
  "stages": [
    { "stage": "download", "progress": 100, "started_at": "...", "finished_at": "..." },
    { "stage": "extract",  "progress": 100, "started_at": "...", "finished_at": "..." },
    { "stage": "describe", "progress": 60,  "started_at": "...", "finished_at": null }
  ],
  "overall_progress": 73
}

Chat

Ask a question (streaming)

POST /chat/{video_id}
Content-Type: application/json
Accept: text/event-stream

Body

{
  "question": "What was written on the whiteboard at the start?",
  "session_id": "optional-existing-session-uuid"
}

Response 200 OK — Server-Sent Events stream

data: {"type": "session_id", "value": "abc-def-..."}

data: {"type": "frames_used", "value": [
  {"timestamp": "00:30", "r2_key": "videos/.../frames/frame_000001.jpg"},
  {"timestamp": "01:00", "r2_key": "videos/.../frames/frame_000002.jpg"}
]}

data: {"type": "token", "value": "At the 30-second mark,"}
data: {"type": "token", "value": " the whiteboard showed"}
data: {"type": "token", "value": " y = mx + b..."}

data: {"type": "done"}

Errors

Code Reason
402 Monthly query limit reached
404 Video not found or not owned by user
409 Video status is not ready

List chat sessions

GET /chat/{video_id}/sessions

Response 200 OK

{
  "sessions": [
    {
      "id": "session-uuid",
      "created_at": "2026-06-07T10:10:00Z",
      "message_count": 4
    }
  ]
}

Get session messages

GET /chat/sessions/{session_id}

Response 200 OK

{
  "session_id": "...",
  "video_id": "...",
  "messages": [
    {
      "role": "user",
      "content": "What is on the whiteboard?",
      "created_at": "2026-06-07T10:10:00Z"
    },
    {
      "role": "assistant",
      "content": "At the 30-second mark...",
      "frame_timestamps": ["00:30", "01:00"],
      "created_at": "2026-06-07T10:10:05Z"
    }
  ]
}

Usage

Get current usage

GET /usage

Response 200 OK

{
  "plan": "free",
  "videos": {
    "used": 2,
    "limit": 3
  },
  "queries": {
    "used": 23,
    "limit": 50,
    "resets_at": "2026-07-01T00:00:00Z"
  }
}

Billing

Create checkout session

POST /billing/checkout

Body

{ "plan": "pro" }

Response 200 OK

{ "checkout_url": "https://checkout.stripe.com/..." }

Open billing portal

POST /billing/portal

Returns a Stripe Customer Portal URL for managing subscription, invoices, and payment methods.

Response 200 OK

{ "portal_url": "https://billing.stripe.com/..." }

Webhooks (internal, not authenticated by JWT)

Clerk user sync

POST /webhooks/clerk

Handles: user.created, user.updated, user.deleted

Stripe subscription sync

POST /webhooks/stripe

Handles: customer.subscription.created, customer.subscription.updated, customer.subscription.deleted


Error Response Format

All errors follow RFC 9457 Problem Details:

{
  "type": "https://frameiq.app/errors/plan-limit-exceeded",
  "title": "Plan limit exceeded",
  "status": 402,
  "detail": "Your free plan allows 50 queries per month. Upgrade to Pro for 1,000 queries.",
  "instance": "/chat/550e8400-..."
}

Rate Limits

Tier Limit
All users 60 requests/minute per IP
Free plan 50 queries/month
Pro plan 1,000 queries/month
Enterprise Unlimited

Rate limit headers on every response:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1717754400

Health

API health check

GET /health

Public endpoint — no auth required. Used by Railway health probes and smoke tests.

Response 200 OK

{
  "status": "ok",
  "database": "connected",
  "redis": "connected",
  "qdrant": "connected",
  "version": "1.0.0"
}

Returns 503 Service Unavailable with the failing dependency name if any check fails.


Worker health check

GET /health/worker

Public endpoint. Reports Celery queue depth.

Response 200 OK

{
  "status": "ok",
  "queue_depth": 3
}