Skip to content

Miniapp API

Public API endpoints served by emprops-api for the Emerge Miniapp frontend. These endpoints replace direct database queries, providing optimized data fetching with proper pagination and efficient SQL.

Overview

The Miniapp API is designed for speed and optimized for the homepage experience:

  • Cursor-based pagination for infinite scroll
  • Raw SQL with CTEs for fast aggregation queries
  • Configurable trending windows for relevance tuning
  • Mux video support with playback IDs for streaming

Base URL

NEXT_PUBLIC_EMPROPS_API_BASE_URL=/miniapp/...

Response Format

All endpoints return responses in this format:

json
{
  "data": [ /* array of items */ ],
  "cursor": {
    "next": "cursor-string-or-null",
    "has_more": true
  },
  "error": null | "Error message"
}

Homepage Endpoints

These endpoints power the Emerge Miniapp homepage.

Returns collections ordered by recent generation activity with their featured deck and outputs. Used for the main homepage gallery.

GET /miniapp/collections/trending

Query Parameters:

ParameterTypeDefaultDescription
limitnumber20Max collections per page (1-100)
afterstringnullCursor for pagination
outputs_per_decknumber6Max outputs to include per deck
trending_hoursnumber48Time window for trending calculation
excludestringnullComma-separated collection IDs to exclude

Response (200):

json
{
  "data": [
    {
      "id": "uuid",
      "title": "Veo Video Generator",
      "description": "Create stunning AI videos",
      "cover_image": "https://cdn.example.com/cover.jpg",
      "creator": {
        "fid": "12345",
        "username": "creator.eth",
        "pfp": "https://..."
      },
      "config": {
        "price": 0.001,
        "is_featured": false,
        "generations_per_payment": 1
      },
      "featured_deck": {
        "id": "uuid",
        "name": "Recent Generations",
        "output_count": 45,
        "has_more_outputs": true,
        "outputs": [
          {
            "id": "uuid",
            "url": "https://cdn.example.com/image.jpg",
            "thumbnail_url": "https://cdn.example.com/thumb.jpg",
            "mime_type": "image/jpeg",
            "status": "completed",
            "created_at": "2024-01-10T12:00:00Z",
            "parent_output_id": null,
            "mux_playback_id": null,
            "mux_asset_id": null,
            "creator": {
              "fid": "67890",
              "username": "user.eth",
              "pfp": "https://..."
            }
          }
        ]
      },
      "total_decks": 12,
      "total_outputs": 156,
      "recent_outputs": 23
    }
  ],
  "cursor": {
    "next": "eyJsYXN0X2lkIjoiYWJjMTIzIn0",
    "has_more": true
  },
  "error": null
}

Frontend Hook:

typescript
import { useTrendingCollections } from "@/hooks/queries";

const {
  collections,      // Flattened array from all pages
  isLoading,
  error,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage
} = useTrendingCollections({
  limit: 20,
  outputsPerDeck: 6,
  trendingHours: 48,
  excludeCollectionIds: [FEATURED_ID],
  enabled: true
});

Returns a single featured collection for the homepage hero section.

GET /miniapp/collections/featured

Query Parameters:

ParameterTypeDefaultDescription
outputs_per_decknumber6Max outputs to include

Response (200):

json
{
  "data": {
    "id": "uuid",
    "title": "Featured: AI Art Generator",
    "description": "This week's featured collection",
    "cover_image": "https://cdn.example.com/featured-cover.jpg",
    "creator": {
      "fid": "12345",
      "username": "emerge.eth",
      "pfp": "https://..."
    },
    "config": {
      "price": null,
      "is_featured": true,
      "generations_per_payment": 3
    },
    "featured_deck": {
      "id": "uuid",
      "name": "Showcase",
      "output_count": 100,
      "has_more_outputs": true,
      "outputs": [ /* ... */ ]
    },
    "total_decks": 1,
    "total_outputs": 100,
    "recent_outputs": 15
  },
  "error": null
}

Deck Endpoints

Get Deck Outputs

Returns paginated outputs for a specific deck with cursor-based pagination.

GET /miniapp/decks/:deckId/outputs

Path Parameters:

ParameterTypeDescription
deckIduuidThe deck ID

Query Parameters:

ParameterTypeDefaultDescription
limitnumber20Max outputs per page (1-100)
afterstringnullCursor for pagination

Response (200):

json
{
  "data": {
    "deck_id": "uuid",
    "deck_name": "My Generations",
    "outputs": [
      {
        "id": "uuid",
        "url": "https://cdn.example.com/video.mp4",
        "thumbnail_url": "https://cdn.example.com/thumb.jpg",
        "mime_type": "video/mp4",
        "status": "completed",
        "created_at": "2024-01-10T12:00:00Z",
        "parent_output_id": "uuid-of-source",
        "mux_playback_id": "7iwyZezQo4fjZ...",
        "mux_asset_id": "1L3400tF301xX...",
        "creator": {
          "fid": "12345",
          "username": "user.eth",
          "pfp": "https://..."
        }
      }
    ]
  },
  "cursor": {
    "next": "eyJsYXN0X2lkIjoiZGVmNDU2In0",
    "has_more": true
  },
  "error": null
}

Helper Functions

The useTrendingCollections hook exports helper functions for working with outputs:

getCollectionDisplayImage

Get the best display image for a collection (prioritizes deck output over cover).

typescript
import { getCollectionDisplayImage } from "@/hooks/queries";

const imageUrl = getCollectionDisplayImage(collection);
// Returns: thumbnail_url > url > cover_image

isVideoOutput

Check if an output is a video with Mux streaming support.

typescript
import { isVideoOutput } from "@/hooks/queries";

if (isVideoOutput(output)) {
  // Use MuxPlayer component
}

getMuxStreamUrl

Get the HLS stream URL for Mux video playback.

typescript
import { getMuxStreamUrl } from "@/hooks/queries";

const streamUrl = getMuxStreamUrl(output);
// Returns: "https://stream.mux.com/{playback_id}.m3u8"

getMuxThumbnailUrl

Get a thumbnail URL for Mux videos with optional dimensions.

typescript
import { getMuxThumbnailUrl } from "@/hooks/queries";

const thumb = getMuxThumbnailUrl(output, { width: 640, height: 360, time: 2 });
// Returns: "https://image.mux.com/{playback_id}/thumbnail.jpg?width=640&height=360&time=2"

App Section Mapping

App SectionEndpoints Used
Homepage Hero/miniapp/collections/featured
Homepage Gallery/miniapp/collections/trending
Collection Detail/miniapp/decks/:deckId/outputs
Infinite ScrollAll endpoints (cursor pagination)

Architecture

Data Flow

Database Query Strategy

The miniapp API uses optimized raw SQL queries with Common Table Expressions (CTEs) for aggregation:

sql
WITH trending_counts AS (
  SELECT collection_id, COUNT(*) as recent_count
  FROM output
  WHERE created_at >= NOW() - INTERVAL '48 hours'
    AND status = 'completed'
  GROUP BY collection_id
),
featured_decks AS (
  SELECT DISTINCT ON (collection_id) *
  FROM deck
  WHERE status = 'active'
  ORDER BY collection_id, output_count DESC
)
SELECT c.*, tc.recent_count, fd.*
FROM collection c
LEFT JOIN trending_counts tc ON c.id = tc.collection_id
LEFT JOIN featured_decks fd ON c.id = fd.collection_id
ORDER BY tc.recent_count DESC NULLS LAST

This approach ensures:

  • Single round-trip to database
  • Efficient aggregation in the database layer
  • Minimal data transfer to application

Error Codes

StatusMeaning
400Bad Request - Invalid parameters
404Not Found - Collection or deck doesn't exist
500Internal Server Error

Migration from Local Queries

The miniapp previously used direct Prisma queries in Next.js API routes. The new architecture:

BeforeAfter
/api/collections (local)/miniapp/collections/trending (emprops-api)
Direct Prisma queriesRaw SQL via backend API
Offset-based paginationCursor-based pagination
Multiple round-tripsSingle optimized query

Why This Change?

  1. Speed: Raw SQL with CTEs is faster than ORM abstractions
  2. Separation: Backend handles complex queries, frontend stays thin
  3. Caching: Backend can implement caching strategies
  4. Scaling: API can be scaled independently of frontend

Released under the MIT License.