Skip to main content

api

Products API

  • Base Path: /api/v1/products
  • Auth: Endpoints marked "Auth: required" expect a valid Authorization header. Public endpoints do not require auth.

Product Model

  • _id: string (Mongo ObjectId)
  • name: string
  • description: string
  • price: number
  • category: string
  • user: string (User ID)
  • images: string[]
  • quantity: number
  • condition: 'New' | 'LIKE_NEW' | 'GOOD' | 'FAIR'
  • game: string | null (ObjectId)
  • leaderboard: string | null (ObjectId)
  • fulfillment: string | null (ObjectId)
  • tournament: string | null (ObjectId)
  • country: string
  • approvalStatus: 'PENDING' | 'APPROVED' | 'REJECTED'
  • processingStatus: alias for approvalStatus; provided for UI convenience
  • approvalUpdatedAt: string | null (ISO datetime of the last approval status change)
  • rejectionReason: string (present when approvalStatus is REJECTED)
  • createdAt: string (ISO datetime)
  • updatedAt: string (ISO datetime)
  • __v: number

Example Product document (as returned by GET /api/v1/products/:id with populated game and tournament):

{
"_id": "66d8c2c4e8d8b8a1f3a9d123",
"name": "Wireless Controller",
"description": "Pro-grade wireless controller with haptics",
"price": 5999,
"category": "ACCESSORY",
"user": "user_1234abcd",
"images": [
"https://cdn.example.com/p/ctrl-1.jpg",
"https://cdn.example.com/p/ctrl-2.jpg"
],
"quantity": 5,
"condition": "New",
"game": {
"_id": "66d8c2c4e8d8b8a1f3a9d999",
"name": "Space Dash",
"description": "Dodge and sprint in space!",
"tabcode": "spacedash",
"thumbnail": "https://cdn.example.com/games/spacedash.png",
},
"leaderboard": null,
"fulfillment": null,
"tournament": {
"_id": "66d8c2c4e8d8b8a1f3a9d777",
"status": "OPEN",
"startAt": "2025-09-04T10:00:00.000Z",
"endedAt": null,
"totalSeats": 100,
"numberOfPlayers": 12,
"expectedPlayers": 100
},
"createdAt": "2025-09-03T12:30:11.222Z",
"updatedAt": "2025-09-03T12:30:11.222Z",
"__v": 0
}

Note: In list and detail responses, game and tournament are populated objects (selected fields). Additional convenience fields are included in list responses; see below.

List Products

  • Method: GET
  • Path: /api/v1/products
  • Auth: not required
  • Query:
    • user: string (optional) — filter products by owner user ID.
    • page: number (optional, default 1, min 1)
    • limit: number (optional, default 20, max 100)
    • count: boolean (optional, default false) — when true, response meta includes totalCount and totalPages.
    • category: string (optional) — filter by a single category id.
    • categories: string (optional) — comma‑separated category ids (e.g., ACCESSORY,CONSOLE).
    • entryFeeMin: number (optional) — minimum tournament entry fee (points).
    • entryFeeMax: number (optional) — maximum tournament entry fee (points).
    • progressMin: number (optional, 0–100) — minimum fill progress percent, computed as (numberOfPlayers / expectedPlayers) * 100.
    • progressMax: number (optional, 0–100) — maximum fill progress percent.
    • timeLeftMin: number (optional) — minimum time left before tournament end, in hours from now.
    • timeLeftMax: number (optional) — maximum time left before tournament end, in hours from now.
    • sort: 'newest' | 'oldest' | 'popular' | 'ending' (optional; default 'newest')
      • newest: sort by product createdAt descending
      • oldest: sort by product createdAt ascending
      • popular: sort by tournament.numberOfPlayers descending; ties fall back to newest
      • ending: sort by tournament.endedAt ascending; products without endedAt appear last
  • Automatically excludes products whose approvalStatus is not APPROVED.
  • The caller must have a country; results are limited to products with the same country.
  • Response 200: Array of product objects, with:
    • Body shape: \{ result: Product[], meta: \{ page, limit, totalCount?, totalPages? \} \}
    • Each Product includes:
      • All fields from Product Model
      • game: populated Game object with fields: _id, name, description, tabcode, thumbnail, status (or null)
      • tournament: populated Tournament object includes _id, status, startAt, endedAt, totalSeats, numberOfPlayers, expectedPlayers, entryFee, rules (or null)
      • ownerVerified: boolean
      • ownerUsername: string
      • user: string (always the user ID)
      • country: string
      • approvalStatus: 'PENDING' | 'APPROVED' | 'REJECTED'
      • processingStatus: 'PENDING' | 'APPROVED' | 'REJECTED' (same value as approvalStatus)
      • rejectionReason: string (when rejected)
      • approvalUpdatedAt: string | null

Example 200 response (truncated):

{
"result": [
{
"_id": "66d8c2c4e8d8b8a1f3a9d123",
"name": "Wireless Controller",
"description": "Pro-grade wireless controller with haptics",
"price": 5999,
"category": "ACCESSORY",
"user": "user_1234abcd",
"images": ["https://cdn.example.com/p/ctrl-1.jpg"],
"quantity": 5,
"condition": "New",
"game": {
"_id": "66d8c2c4e8d8b8a1f3a9d999",
"name": "Space Dash",
"description": "Dodge and sprint in space!",
"tabcode": "spacedash",
"thumbnail": "https://cdn.example.com/games/spacedash.png",
"status": "PUBLISHED"
},
"leaderboard": null,
"fulfillment": null,
"tournament": {
"_id": "66d8c2c4e8d8b8a1f3a9d777",
"status": "OPEN",
"startAt": "2025-09-04T10:00:00.000Z",
"endedAt": null,
"totalSeats": 100,
"numberOfPlayers": 12,
"expectedPlayers": 100,
"entryFee": 10,
"rules": "No cheating; be respectful."
},
"createdAt": "2025-09-03T12:30:11.222Z",
"updatedAt": "2025-09-03T12:30:11.222Z",
"__v": 0,
"ownerVerified": true,
"ownerUsername": "john_doe"
}

], "meta": { "page": 1, "limit": 20, "totalCount": 123, "totalPages": 7 } }

  • All fields from Product Model
  • ownerVerified: boolean
  • ownerUsername: string
  • user: string (always the user ID)

Example 200 response item:

{
"_id": "66d8c2c4e8d8b8a1f3a9d123",
"name": "Wireless Controller",
"description": "Pro-grade wireless controller with haptics",
"price": 5999,
"category": "ACCESSORY",
"user": "user_1234abcd",
"images": ["https://cdn.example.com/p/ctrl-1.jpg"],
"game": {
"_id": "66d8c2c4e8d8b8a1f3a9d999",
"name": "Space Dash",
"description": "Dodge and sprint in space!",
"tabcode": "spacedash",
"thumbnail": "https://cdn.example.com/games/spacedash.png",
},
"leaderboard": null,
"fulfillment": null,
"tournament": {
"_id": "66d8c2c4e8d8b8a1f3a9d777",
"status": "OPEN",
"startAt": "2025-09-04T10:00:00.000Z",
"endedAt": null,
"totalSeats": 100,
"numberOfPlayers": 12,
"expectedPlayers": 100
},
"createdAt": "2025-09-03T12:30:11.222Z",
"updatedAt": "2025-09-03T12:30:11.222Z",
"__v": 0,
"ownerVerified": true,
"ownerUsername": "john_doe"
}
  • Errors:
    • 500: { "error": "Failed to fetch products" }

Filter examples

  • Categories and entry fee range:
    • GET /api/v1/products?categories=ACCESSORY,CONSOLE&entryFeeMin=5&entryFeeMax=20
  • Progress between 40% and 80%:
    • GET /api/v1/products?progressMin=40&progressMax=80
  • Ending within the next 24 hours:
    • GET /api/v1/products?timeLeftMax=24&sort=ending

Get Product By ID

  • Method: GET
  • Path: /api/v1/products/:id
  • Auth: required
  • Path Params:
    • id: string (ObjectId)
  • Request Body: none
  • Response 200: A single Product document with game and tournament populated as described above. No ownerVerified/ownerUsername enrichment.
  • Errors:
    • 401: { "error": "UNAUTHORIZED", "message": "Unauthorized access not allowed" }
    • 404: { "error": "Product not found" }
    • 500: { "error": "Failed to fetch product" }

List My Favourite Products

  • Method: GET
  • Path: /api/v1/products/favorites
  • Auth: required (guests are not allowed)
  • Request Body: none
  • Query:
    • page: number (optional, default 1, min 1)
    • limit: number (optional, default 20, max 100)
    • count: boolean (optional, default false) — when true, response meta includes totalCount and totalPages.
  • Response 200: \{ result: Product[], meta: \{ page, limit, totalCount?, totalPages? \} \} where each Product matches List Products (populated game, tournament, ownerVerified, ownerUsername, and user normalized to ID). May be an empty result array if no favourites.
  • Errors:
    • 401: { "error": "Unauthorized", "message": "Login required" }
    • 500: { "error": "Failed to fetch favourite products" }

Mark Product as Favourite

  • Method: POST
  • Path: /api/v1/products/:id/favorite
  • Auth: required (guests are not allowed)
  • Path Params:
    • id: string (ObjectId)
  • Request Body: none
  • Response 200: { "ok": true }
  • Errors:
    • 400: { "error": "Invalid product id" }
    • 401: { "error": "Unauthorized", "message": "Login required" }
    • 404: { "error": "Product not found" }
    • 500: { "error": "Failed to mark favourite" }

Unmark Product as Favourite

  • Method: DELETE
  • Path: /api/v1/products/:id/favorite
  • Auth: required (guests are not allowed)
  • Path Params:
    • id: string (ObjectId)
  • Request Body: none
  • Response 200: { "ok": true }
  • Errors:
    • 400: { "error": "Invalid product id" }
    • 401: { "error": "Unauthorized", "message": "Login required" }
    • 500: { "error": "Failed to unmark favourite" }

Notes

  • Normalization in lists: For list and favourites endpoints, the user field is always returned as the owner’s ID (string). The extra fields ownerVerified (boolean) and ownerUsername (string) are also included.
  • IDs: All ObjectIds are returned as strings.
  • Empty arrays: When no favourites exist, /favorites returns [].

Categories

  • Base Path: /api/admin/categories
  • Auth: Admin
  • List Categories
    • Method: GET
    • Path: /api/admin/categories
    • Response 200: [\{ _id: string, name: string, active: boolean, createdAt, updatedAt \}]
  • See also: docs/categories-api.md for full admin categories API details (create, model notes).