Skip to main content

Reels Endpoints

Vertical video feed + engagement + tagging + earnings attribution. See Reels feature for the conceptual model.

Browse + watchโ€‹

GET /api/reelsโ€‹

Paginated feed. Used by web /reels and mobile Reels tab.

Query: cursor?, limit=20, search?, category? Response: { reels: Reel[], nextCursor } โ€” each reel includes consumer/seller, _count (likes, comments, views), tags (multi-tag carousel data)

GET /api/reels/[id]โ€‹

Single reel detail (used by deep links, e.g., /reels?id=X).

DELETE /api/reels/[id] ๐Ÿ”’โ€‹

Owner deletes their own reel. Cascades to ReelLike/Comment/View/Tag.

Engagement (deduplicated by fingerprint)โ€‹

GET /api/reels/[id]/like?fingerprint=...โ€‹

Returns { liked: boolean, count: number }.

POST /api/reels/[id]/likeโ€‹

Body: { fingerprint } Toggles like. Same fingerprint can only like once.

GET /api/reels/[id]/commentsโ€‹

Response: { comments: ReelComment[] }

POST /api/reels/[id]/comments ๐Ÿ”’โ€‹

Body: { text } (max 200 chars)

POST /api/reels/[id]/viewโ€‹

Body: { fingerprint } Tracks a view. Same fingerprint deduplicated within a window.

Tagging (gated by REELS_COMMERCE_ENABLED)โ€‹

GET /api/reels/[id]/tagsโ€‹

Public. Returns the tag carousel data.

Response: { tags: ReelTag[] } โ€” each with embedded product or menuItem info

PUT /api/reels/[id]/tags ๐Ÿ”’โ€‹

Replace tags for a reel. Owner only. Up to 5 tags.

Body: { tags: Array<{ productId? | menuItemId? }> } Returns 503 when REELS_COMMERCE_ENABLED=false.

Earnings attributionโ€‹

POST /api/reels/[id]/redirectโ€‹

Tracks a tap on a tagged product (creator earnings attribution). Logs to ReelProductRedirect.

Body: { fingerprint, productId, menuItemId? }

When the linked product is later ordered (within 24h via REEL_ATTRIBUTION_WINDOW_MS), a ReelEarning row is created in pending. Confirmed on delivered / reversed on cancelled.

Uploadโ€‹

Reels videos are uploaded to GCS via signed URL โ€” the API does NOT see the video bytes.

POST /api/upload-video ๐Ÿ”’โ€‹

Returns a signed PUT URL the client uploads directly to.

Body: { filename, contentType } Response: { uploadUrl, finalUrl }

After upload completes, client calls:

POST /api/reelsโ€‹

Body: { videoUrl, thumbnailUrl, caption, category, productId?, restaurantId?, tags? } Response: { reel: Reel }

Common shapesโ€‹

Reelโ€‹

{
id: number;
consumerId: number | null; // creator (one of these is set)
sellerId: number | null;
videoUrl: string;
thumbnailUrl: string | null;
caption: string | null;
category: string | null;
productId: number | null; // legacy single-tag
restaurantId: number | null; // legacy single-tag
isHidden: boolean;
reportCount: number;
durationSeconds: number | null;
tags?: ReelTag[];
consumer?: Consumer;
seller?: Seller;
product?: Product; // legacy
restaurant?: Restaurant; // legacy (eats archived)
_count: { likes: number; comments: number; views: number };
createdAt: string;
}

ReelTagโ€‹

{
id: number;
reelId: number;
productId: number | null;
menuItemId: number | null;
position: number; // ordering
product?: { id, title, price, images };
menuItem?: { id, name, price, imageUrl, restaurant: { id, name } };
}
  • Reels feature
  • REEL_* constants in src/lib/constants.ts
  • tests/build-integrity.test.ts (Reels section)