TCGHermit API Documentation

Backend API for card deck building and trading app - Complete API Reference

Authentication: Some endpoints require authentication. Include a Supabase JWT token in the Authorization header as Bearer <token>. See Authentication Guide for details.

Categories

GET /categories
List all categories with pagination. Results are sorted by category_id.

Query Parameters:

page (int, optional, default: 1) - Page number
limit (int, optional, default: 100, max: 1000) - Items per page

Example Request:

GET /categories?page=1&limit=20

Response Schema:

{ "data": [Category], "page": int, "limit": int, "total": int | null, "has_more": bool }
Category object
category_id bigint (required)
name text (required)
display_name text | null
seo_category_name text | null
sealed_label text | null
non_sealed_label text | null
condition_guide_url text | null
is_scannable boolean | null
popularity integer | null
modified_on timestamp with time zone | null
fetched_at timestamp with time zone (required)
raw jsonb (default: {})
fixed_amount integer | null

Example Response:

{ "data": [ { "category_id": 1, "name": "Pokemon", "display_name": "PokΓ©mon", "seo_category_name": "pokemon", "sealed_label": "Sealed", "non_sealed_label": "Unsealed", "condition_guide_url": "https://example.com/condition-guide", "is_scannable": true, "popularity": 100, "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {}, "fixed_amount": null }, { "category_id": 2, "name": "Magic: The Gathering", "display_name": "Magic: The Gathering", "seo_category_name": "magic-the-gathering", "sealed_label": "Sealed", "non_sealed_label": "Unsealed", "condition_guide_url": null, "is_scannable": true, "popularity": 95, "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {}, "fixed_amount": null } ], "page": 1, "limit": 20, "total": 50, "has_more": true }
GET /categories/{category_id}
Get a single category by primary key (category_id).

Path Parameters:

category_id (int, required) - Category ID

Example Request:

GET /categories/1

Example Response:

{ "category_id": 1, "name": "Pokemon", "display_name": "PokΓ©mon", "seo_category_name": "pokemon", "sealed_label": "Sealed", "non_sealed_label": "Unsealed", "condition_guide_url": "https://example.com/condition-guide", "is_scannable": true, "popularity": 100, "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {}, "fixed_amount": null }
GET /categories/product-counts
Get product counts by category. Returns a dictionary mapping category_id to product count.

Example Request:

GET /categories/product-counts

Response Schema:

{ "category_id": int // Dictionary mapping category_id to product count }

Example Response:

{ "1": 150, "2": 200, "3": 75 }
GET /categories/rules
Get category rules (game rules for deck building). If category_id is provided, returns rules for that category. If not provided, returns all category game rules.

Query Parameters:

category_id (int, optional) - Filter by category_id

Example Requests:

GET /categories/rules GET /categories/rules?category_id=1

Response Schema (single category):

CategoryGameRules
CategoryGameRules object
category_id bigint (required)
deck_size integer | null
max_duplicates integer | null
extended_rules jsonb (default: {})
created_at timestamp with time zone (required)
updated_at timestamp with time zone (required)

Example Response (with category_id):

{ "category_id": 1, "deck_size": 60, "max_duplicates": 4, "extended_rules": { "energy_types": ["Fire", "Water", "Grass"], "special_rules": [] }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-15T10:30:00Z" }

Example Response (all rules - array):

[ { "category_id": 1, "deck_size": 60, "max_duplicates": 4, ... }, { "category_id": 2, "deck_size": 60, "max_duplicates": 4, ... } ]

Groups

GET /groups
List all groups with pagination. Results are sorted by published_on (descending), then by group_id.

Query Parameters:

page (int, optional, default: 1)
limit (int, optional, default: 100, max: 1000)

Response Schema:

PaginatedResponse<Group>
Group object
group_id bigint (required)
category_id bigint (required)
name text (required)
abbreviation text | null
is_supplemental boolean | null
published_on timestamp with time zone | null
modified_on timestamp with time zone | null
fetched_at timestamp with time zone (required)
raw jsonb (default: {})

Example Response:

{ "data": [ { "group_id": 1, "category_id": 1, "name": "Base Set", "abbreviation": "BS", "is_supplemental": false, "published_on": "1999-01-09T00:00:00Z", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {} } ], "page": 1, "limit": 100, "total": 50, "has_more": false }
GET /groups/{group_id}
Get a single group by primary key (group_id).

Response Schema:

Group

Example Response:

{ "group_id": 1, "category_id": 1, "name": "Base Set", "abbreviation": "BS", "is_supplemental": false, "published_on": "1999-01-09T00:00:00Z", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {} }
GET /groups/by-category/{category_id}
Get all groups for a specific category. Results are sorted by published_on (descending), then by group_id.

Response Schema:

PaginatedResponse<Group>

Example Response:

{ "data": [ { "group_id": 1, "category_id": 1, "name": "Base Set", "abbreviation": "BS", "is_supplemental": false, "published_on": "1999-01-09T00:00:00Z", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {} } ], "page": 1, "limit": 100, "total": 10, "has_more": false }

Products

GET /products
List all products with pagination. Results are sorted by the specified columns (default: color, type, rarity, level, cost in ascending order). Each product includes cached fields (color, type, level, cost, atk, hp, rarity) and a number field from extended data.

Query Parameters:

page (int, optional, default: 1) - Page number
limit (int, optional, default: 100, max: 1000) - Number of items per page
sort_columns (array of strings, optional, default: ["color", "type", "rarity", "level", "cost"]) - List of column names to sort by (in order)
sort_direction (string or array of strings, optional, default: "asc") - Sort direction: "asc" or "desc", or a list of directions (one per column in sort_columns). If a list, length must match sort_columns length.

Response Schema:

PaginatedResponse<Product>
Product object
product_id bigint (required)
category_id bigint (required)
group_id bigint (required)
name text (required)
clean_name text | null
image_url text | null
url text | null
modified_on timestamp with time zone | null
fetched_at timestamp with time zone (required)
raw jsonb (default: {})
fixed_amount integer | null
number text | null
rarity text | null
color text | null
type text | null
level integer | null
cost integer | null
atk integer | null
hp integer | null
extended_data_raw jsonb | null (raw JSON string)

Example Response:

{ "data": [ { "product_id": 12345, "category_id": 1, "group_id": 1, "name": "Pikachu", "clean_name": "pikachu", "image_url": "https://example.com/pikachu.jpg", "url": "https://tcgplayer.com/product/12345", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {}, "fixed_amount": null, "number": "025", "rarity": "Common", "color": "Yellow", "type": "Electric", "level": null, "cost": 1, "atk": 20, "hp": 60, "extended_data_raw": "{\"Rarity\": \"Common\", \"Number\": \"025\", \"Type\": \"Electric\"}" } ], "page": 1, "limit": 100, "total": 1000, "has_more": true }
GET /products/search
Search products by partial name matching. Performs case-insensitive partial matching on product names. Results are sorted by the specified columns (default: color, type, rarity, level, cost in ascending order).

Query Parameters:

q (string, required, min: 1) - Search query for partial name matching (case-insensitive)
page (int, optional, default: 1) - Page number
limit (int, optional, default: 100, max: 1000) - Number of items per page
sort_columns (array of strings, optional, default: ["color", "type", "rarity", "level", "cost"]) - List of column names to sort by (in order)
sort_direction (string or array of strings, optional, default: "asc") - Sort direction: "asc" or "desc", or a list of directions (one per column in sort_columns). If a list, length must match sort_columns length.

Example Request:

GET /products/search?q=pika&page=1&limit=20

Response Schema:

PaginatedResponse<Product>
PaginatedResponse object
data array of Product
page integer (required)
limit integer (required)
total integer | null
has_more boolean (required)

Example Response:

{ "data": [ { "product_id": 12345, "category_id": 1, "group_id": 1, "name": "Pikachu", "clean_name": "pikachu", "image_url": "https://example.com/pikachu.jpg", "url": "https://tcgplayer.com/product/12345", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "fixed_amount": null, "number": "025", "rarity": "Common", "color": "Yellow", "type": "Electric", "level": null, "cost": 1, "atk": 20, "hp": 60, "extended_data_raw": "{\"Rarity\": \"Common\", \"Number\": \"025\", \"Type\": \"Electric\"}" }, { "product_id": 12346, "category_id": 1, "group_id": 1, "name": "Pikachu VMAX", "clean_name": "pikachu vmax", "image_url": "https://example.com/pikachu-vmax.jpg", "url": "https://tcgplayer.com/product/12346", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "fixed_amount": null, "number": "188", "rarity": "Rare", "color": "Yellow", "type": "Electric", "level": null, "cost": 3, "atk": 200, "hp": 300, "extended_data_raw": "{\"Rarity\": \"Ultra Rare\", \"Number\": \"188\", \"Type\": \"Electric\"}" } ], "page": 1, "limit": 20, "total": 15, "has_more": false }
GET /products/{product_id}
Get a single product by primary key (product_id). Each product includes a number field from extended data.

Response Schema:

Product

Example Response:

{ "product_id": 12345, "category_id": 1, "group_id": 1, "name": "Pikachu", "clean_name": "pikachu", "image_url": "https://example.com/pikachu.jpg", "url": "https://tcgplayer.com/product/12345", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {}, "fixed_amount": null, "number": "025", "extended_data_raw": "{\"Rarity\": \"Common\", \"Number\": \"025\", \"Type\": \"Electric\"}" }
GET /products/by-category/{category_id}
Get all products for a specific category. Results are sorted by the specified columns (default: color, type, rarity, level, cost in ascending order).

Query Parameters:

page (int, optional, default: 1) - Page number
limit (int, optional, default: 100, max: 1000) - Number of items per page
sort_columns (array of strings, optional, default: ["color", "type", "rarity", "level", "cost"]) - List of column names to sort by (in order)
sort_direction (string or array of strings, optional, default: "asc") - Sort direction: "asc" or "desc", or a list of directions (one per column in sort_columns). If a list, length must match sort_columns length.

Response Schema:

PaginatedResponse<Product>

Example Response:

{ "data": [ { "product_id": 12345, "category_id": 1, "group_id": 1, "name": "Pikachu", "clean_name": "pikachu", "image_url": "https://example.com/pikachu.jpg", "url": "https://tcgplayer.com/product/12345", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {}, "fixed_amount": null, "number": "025", "extended_data_raw": "{\"Rarity\": \"Common\", \"Number\": \"001\"}" } ], "page": 1, "limit": 100, "total": 500, "has_more": true }
GET /products/by-group/{group_id}
Get all products for a specific group. Results are sorted by the specified columns (default: color, type, rarity, level, cost in ascending order).

Query Parameters:

page (int, optional, default: 1) - Page number
limit (int, optional, default: 100, max: 1000) - Number of items per page
sort_columns (array of strings, optional, default: ["color", "type", "rarity", "level", "cost"]) - List of column names to sort by (in order)
sort_direction (string or array of strings, optional, default: "asc") - Sort direction: "asc" or "desc", or a list of directions (one per column in sort_columns). If a list, length must match sort_columns length.

Response Schema:

PaginatedResponse<Product>

Example Response:

{ "data": [ { "product_id": 12345, "category_id": 1, "group_id": 1, "name": "Pikachu", "clean_name": "pikachu", "image_url": "https://example.com/pikachu.jpg", "url": "https://tcgplayer.com/product/12345", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {}, "fixed_amount": null, "number": "025", "extended_data_raw": "{\"Rarity\": \"Common\", \"Number\": \"001\"}" } ], "page": 1, "limit": 100, "total": 102, "has_more": true }
POST /products/filter
Filter products by extended data key-value pairs, category, and/or group. Different filter keys are combined with AND logic, while multiple values for a single key are combined with OR logic. For example, {"Rarity": ["Common", "Rare"], "Number": "001"} returns products with (Rarity=Common OR Rarity=Rare) AND Number=001. Results are sorted by the specified columns (default: color, type, rarity, level, cost in ascending order).

Query Parameters:

page (int, optional, default: 1)
limit (int, optional, default: 100, max: 1000)

Request Body:

category_id (int, optional) - Filter by category ID
group_id (int, optional) - Filter by group ID
filters (object, optional) - Key-value pairs for extended data filtering. Values can be strings or arrays of strings. Multiple values for a key use OR logic, different keys use AND logic.
sort_columns (array of strings, optional, default: ["color", "type", "rarity", "level", "cost"]) - List of column names to sort by (in order)
sort_direction (string or array of strings, optional, default: "asc") - Sort direction: "asc" or "desc", or a list of directions (one per column in sort_columns). If a list, length must match sort_columns length.

Example Request (with OR logic):

POST /products/filter?page=1&limit=20 { "category_id": 1, "filters": { "Rarity": ["Common", "Rare"], "Number": "001" }, "sort_columns": ["color", "type", "rarity", "level", "cost"], "sort_direction": "asc" }

Example Request (with per-column sort directions):

POST /products/filter?page=1&limit=20 { "category_id": 1, "filters": { "Rarity": ["Common", "Rare"] }, "sort_columns": ["color", "type", "rarity"], "sort_direction": ["asc", "desc", "asc"] }

Response Schema:

PaginatedResponse<Product>

Example Response:

{ "data": [ { "product_id": 12345, "category_id": 1, "group_id": 1, "name": "Bulbasaur", "clean_name": "bulbasaur", "image_url": "https://example.com/bulbasaur.jpg", "url": "https://tcgplayer.com/product/12345", "modified_on": "2024-01-15T10:30:00Z", "fetched_at": "2024-01-20T08:00:00Z", "raw": {}, "fixed_amount": null, "number": "001", "rarity": "Common", "color": "Green", "type": "Grass", "level": null, "cost": 1, "atk": 10, "hp": 50, "extended_data_raw": "{\"Rarity\": \"Common\", \"Number\": \"001\", \"Type\": \"Grass\"}" } ], "page": 1, "limit": 20, "total": 50, "has_more": true }

Current Prices

GET /prices-current
List all current prices. Results are sorted by product_id.

Response Schema:

Array<PriceCurrent>
PriceCurrent object
product_id bigint (required)
low_price numeric | null
mid_price numeric | null
high_price numeric | null
market_price numeric | null
direct_low_price numeric | null
sub_type_name text | null
fetched_at timestamp with time zone (required)
raw jsonb (default: {})

Example Response:

[ { "product_id": 12345, "low_price": 0.50, "mid_price": 1.25, "high_price": 2.00, "market_price": 1.15, "direct_low_price": 0.45, "sub_type_name": "Normal", "fetched_at": "2024-01-20T08:00:00Z", "raw": {} }, { "product_id": 67890, "low_price": 5.00, "mid_price": 7.50, "high_price": 10.00, "market_price": 7.25, "direct_low_price": 4.75, "sub_type_name": "Holo", "fetched_at": "2024-01-20T08:00:00Z", "raw": {} } ]
GET /prices-current/{product_id}
Get current price for a single product by primary key (product_id).

Response Schema:

PriceCurrent

Example Response:

{ "product_id": 12345, "low_price": 0.50, "mid_price": 1.25, "high_price": 2.00, "market_price": 1.15, "direct_low_price": 0.45, "sub_type_name": "Normal", "fetched_at": "2024-01-20T08:00:00Z", "raw": {} }
POST /prices-current/bulk
Get multiple current prices by their product IDs in bulk. Maximum 1000 product IDs per request. Products that don't have prices will not be included in the response.

Request Body:

product_ids (array[int], required, min: 1, max: 1000) - List of product IDs to fetch

Example Request:

POST /prices-current/bulk { "product_ids": [12345, 67890, 11111] }

Response Schema:

{ "prices": [PriceCurrent], "requested_count": int, "found_count": int, "missing_count": int }

Example Response:

{ "prices": [ { "product_id": 12345, "low_price": 0.50, "mid_price": 1.25, "high_price": 2.00, "market_price": 1.15, "direct_low_price": 0.45, "sub_type_name": "Normal", "fetched_at": "2024-01-20T08:00:00Z", "raw": {} }, { "product_id": 67890, "low_price": 5.00, "mid_price": 7.50, "high_price": 10.00, "market_price": 7.25, "direct_low_price": 4.75, "sub_type_name": "Holo", "fetched_at": "2024-01-20T08:00:00Z", "raw": {} } ], "requested_count": 3, "found_count": 2, "missing_count": 1 }

Price History

GET /prices-history
List price history with pagination and optional date/product filtering. Results are sorted by product_id, then fetched_at.

Query Parameters:

page (int, optional, default: 1)
limit (int, optional, default: 100, max: 1000)
start_date (datetime, optional, ISO format) - Start date for filtering (YYYY-MM-DDTHH:MM:SS or YYYY-MM-DD)
end_date (datetime, optional, ISO format) - End date for filtering
product_id (int, optional) - Filter by specific product ID

Example Request:

GET /prices-history?start_date=2024-01-01&end_date=2024-12-31&product_id=12345

Response Schema:

PaginatedResponse<PriceHistory>
PriceHistory object
product_id bigint (required)
fetched_at timestamp with time zone (required)
low_price numeric | null
mid_price numeric | null
high_price numeric | null
market_price numeric | null
direct_low_price numeric | null
sub_type_name text | null
raw jsonb (default: {})

Example Response:

{ "data": [ { "product_id": 12345, "fetched_at": "2024-01-20T08:00:00Z", "low_price": 0.50, "mid_price": 1.25, "high_price": 2.00, "market_price": 1.15, "direct_low_price": 0.45, "sub_type_name": "Normal", "raw": {} } ], "page": 1, "limit": 100, "total": 500, "has_more": true }
GET /prices-history/by-product/{product_id}
Get all price history for a specific product. Supports optional date filtering via query parameters.

Query Parameters:

start_date (datetime, optional, ISO format)
end_date (datetime, optional, ISO format)

Response Schema:

PaginatedResponse<PriceHistory>

Example Response:

{ "data": [ { "product_id": 12345, "fetched_at": "2024-01-20T08:00:00Z", "low_price": 0.50, "mid_price": 1.25, "high_price": 2.00, "market_price": 1.15, "direct_low_price": 0.45, "sub_type_name": "Normal", "raw": {} } ], "page": 1, "limit": 100, "total": 30, "has_more": false }
GET /prices-history/by-product-date
Get a single price history entry by composite primary key (product_id, fetched_at).

Query Parameters:

product_id (bigint, required)
fetched_at (timestamp with time zone, required, ISO format)

Response Schema:

PriceHistory

Example Response:

{ "product_id": 12345, "fetched_at": "2024-01-20T08:00:00Z", "low_price": 0.50, "mid_price": 1.25, "high_price": 2.00, "market_price": 1.15, "direct_low_price": 0.45, "sub_type_name": "Normal", "raw": {} }

Product Extended Data

GET /product-extended-data
List all product extended data entries with pagination. Results are sorted by product_id, then key.

Response Schema:

PaginatedResponse<ProductExtendedData>
ProductExtendedData object
product_id bigint (required)
key text (required)
value text | null

Example Response:

{ "data": [ { "product_id": 12345, "key": "Rarity", "value": "Common" }, { "product_id": 12345, "key": "Number", "value": "025" } ], "page": 1, "limit": 100, "total": 5000, "has_more": true }
GET /product-extended-data/by-category/{category_id}
Get all extended data entries for products in a specific category. Results are sorted by product_id, then key and support pagination.

Response Schema:

PaginatedResponse<ProductExtendedData>

Example Response:

{ "data": [...], "page": 1, "limit": 100, "total": 2000, "has_more": true }
GET /product-extended-data/by-category/{category_id}/keys
Get unique list of extended data keys for products in a category (returns distinct key names).

Example Response:

["Rarity", "Number", "Type", "HP", "Attack"]
GET /product-extended-data/by-category/{category_id}/key-values
Get unique key-value pairs for products in a category (returns dictionary of key -> list of unique values, useful for filtering).

Example Response:

{ "Rarity": ["Common", "Uncommon", "Rare", "Ultra Rare"], "Number": ["001", "002", "003", ...], "Type": ["Fire", "Water", "Grass", "Electric"] }
GET /product-extended-data/by-product/{product_id}
Get all extended data for a product. Results are sorted by key and support pagination.

Response Schema:

PaginatedResponse<ProductExtendedData>

Example Response:

{ "data": [ { "product_id": 12345, "key": "Rarity", "value": "Common" }, { "product_id": 12345, "key": "Number", "value": "025" } ], "page": 1, "limit": 100, "total": 5, "has_more": false }
GET /product-extended-data/by-product-key
Get a single extended data entry by composite primary key (product_id, key).

Query Parameters:

product_id (int, required)
key (string, required)

Example Response:

{ "product_id": 12345, "key": "Rarity", "value": "Common" }

Category Extended Data Keys

GET /category-extended-data-keys
List all category extended data keys with pagination. Results are sorted by category_id, then key.

Response Schema:

PaginatedResponse<CategoryExtendedDataKey>
CategoryExtendedDataKey object
category_id bigint (required)
key text (required)
first_seen timestamp with time zone (required)
last_seen timestamp with time zone (required)

Example Response:

{ "data": [ { "category_id": 1, "key": "Rarity", "first_seen": "2024-01-01T00:00:00Z", "last_seen": "2024-01-20T08:00:00Z" }, { "category_id": 1, "key": "Number", "first_seen": "2024-01-01T00:00:00Z", "last_seen": "2024-01-20T08:00:00Z" } ], "page": 1, "limit": 100, "total": 50, "has_more": false }
GET /category-extended-data-keys/by-category/{category_id}
Get all extended data keys for a specific category. Results are sorted by key and support pagination. Useful for discovering what metadata fields are available for products in a category.

Response Schema:

PaginatedResponse<CategoryExtendedDataKey>

Example Response:

{ "data": [ { "category_id": 1, "key": "Rarity", "first_seen": "2024-01-01T00:00:00Z", "last_seen": "2024-01-20T08:00:00Z" } ], "page": 1, "limit": 100, "total": 10, "has_more": false }
GET /category-extended-data-keys/by-category-key
Get a single category extended data key by composite primary key (category_id, key).

Query Parameters:

category_id (int, required)
key (string, required)

Example Response:

{ "category_id": 1, "key": "Rarity", "first_seen": "2024-01-01T00:00:00Z", "last_seen": "2024-01-20T08:00:00Z" }

Favorites

GET /favorites
Get favorites for a user. Public read access - no authentication required. Returns the user's favorites as JSONB (product_id string -> quantity integer, typically 1 for favorited).
Note: Favorites are stored in the profiles table's favorites column.

Query Parameters:

user_id (string/UUID, required) - User ID to get favorites for

Example Request:

GET /favorites?user_id=f4f079f2-2d7f-411b-b309-67dc9f019f57

Response Schema:

Favorites
Favorites object
user_id uuid (required)
favorites jsonb (default: {}, dict of product_id string -> quantity integer)
created_at timestamp with time zone | null
updated_at timestamp with time zone | null

Example Response:

{ "user_id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "favorites": { "12345": 1, "67890": 1, "11111": 1 }, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }
POST /favorites
Update favorites for the current user. Merges new favorites with existing favorites. Items with quantity 0 are automatically removed. πŸ”’ Requires Authentication
Format: JSON object where keys are product_id strings (e.g., "12345") and values are integer quantities (typically 1 for favorited).
Note: Favorites are stored in the profiles table's favorites column.

Headers:

Authorization (string, required) - Bearer token

Request Body:

favorites (object, required) - Dictionary of product_id (string) -> quantity (integer)

Example Request:

POST /favorites Headers: Authorization: Bearer <token> { "favorites": { "12345": 1, "67890": 1 } }

Example Response:

{ "user_id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "favorites": { "12345": 1, "67890": 1, "11111": 1 }, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }
DELETE /favorites
Delete favorites from the current user's favorites. πŸ”’ Requires Authentication

Headers:

Authorization (string, required) - Bearer token

Request Body:

product_ids (array of int, required) - List of product IDs to remove from favorites

Example Request:

DELETE /favorites Headers: Authorization: Bearer <token> { "product_ids": [12345, 67890] }

User Inventory πŸ”’ Requires Authentication

GET /user-inventory
Get inventory for a user. Public read access - no authentication required. Returns the user's inventory with items as JSONB (product_id string -> quantity integer).
Note: Inventory is stored in the profiles table's items column.

Query Parameters:

user_id (string/UUID, required) - User ID to get inventory for

Example Request:

GET /user-inventory?user_id=f4f079f2-2d7f-411b-b309-67dc9f019f57

Response Schema:

UserInventory
UserInventory object
user_id uuid (required)
items jsonb (default: {}, dict of product_id string -> quantity integer)
created_at timestamp with time zone | null
updated_at timestamp with time zone | null
total_count integer (default: 0)

Example Response:

{ "user_id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "items": { "12345": 5, "67890": 2, "11111": 10 }, "total_count": 17, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }
POST /user-inventory/items
Update items in inventory for the current user. This is the only endpoint for updating inventory items. Merges new items with existing items. Items with quantity 0 are automatically removed. πŸ”’ Requires Authentication
Format: JSON object where keys are product_id strings (e.g., "12345") and values are integer quantities (e.g., 5).
Note: Inventory is stored in the profiles table's items column.

Headers:

Authorization (string, required) - Bearer token

Request Body:

items (object, required) - Dictionary of product_id (string) -> quantity (integer). New items are merged with existing items, overriding duplicates.

Example Request:

POST /user-inventory/items Headers: Authorization: Bearer <token> { "items": { "12345": 5, "67890": 2, "11111": 10 } }

Response Schema:

UserInventory

Example Response:

{ "user_id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "items": { "12345": 5, "67890": 2, "11111": 10 }, "total_count": 17, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }
DELETE /user-inventory/items
Delete items from inventory for the current user. πŸ”’ Requires Authentication

Headers:

Authorization (string, required) - Bearer token

Request Body:

product_ids (array[int], required, min: 1) - List of product IDs to remove from inventory

Example Request:

DELETE /user-inventory/items Headers: Authorization: Bearer <token> { "product_ids": [12345, 67890] }

Response Schema:

UserInventory

Example Response:

{ "user_id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "items": { "11111": 10 }, "total_count": 10, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }
GET /user-inventory/by-category/stats
Get inventory statistics grouped by category for the current user. Returns total quantity and unique product count per category.

Response Schema:

Array<CategoryStats>
CategoryStats object
category_id bigint
total_quantity integer
unique_products integer

Example Response:

[ { "category_id": 1, "total_quantity": 150, "unique_products": 25 }, { "category_id": 2, "total_quantity": 200, "unique_products": 30 } ]

Profiles

GET /profiles
Get a user's profile. Public read access - no authentication required. Returns profile data including username, avatar_url, currency, items (inventory), favorites, and timestamps.
Note: full_name is excluded from public responses for privacy.

Query Parameters:

user_id (string/UUID, required) - User ID to get profile for

Example Request:

GET /profiles?user_id=f4f079f2-2d7f-411b-b309-67dc9f019f57

Response Schema:

Profile
Profile object
id uuid (required)
username text | null
avatar_url text | null
currency text (required, default: USD)
items jsonb (default: {}, dict of product_id string -> quantity integer)
favorites jsonb (default: {}, dict of product_id string -> quantity integer)
total_count integer (default: 0)
created_at timestamp with time zone (required)
updated_at timestamp with time zone (required)

Example Response:

{ "id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "username": "cardcollector", "avatar_url": "https://example.com/avatar.jpg", "currency": "USD", "items": { "12345": 5, "67890": 2, "11111": 10 }, "favorites": { "12345": 1, "67890": 1 }, "total_count": 17, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }
POST /profiles
Update the current user's profile. Requires authentication - users can only update their own profile. Updates any provided fields (username, full_name, avatar_url, currency, items, favorites, total_count). πŸ”’ Requires Authentication
Note: Database triggers will automatically update updated_at and calculate total_count from items.

Headers:

Authorization (string, required) - Bearer token

Request Body:

username (string, optional) - Username
full_name (string, optional) - Full name
avatar_url (string, optional) - Avatar URL
currency (string, optional) - Default currency code (e.g., USD, EUR, GBP)
items (object, optional) - Inventory items (product_id string -> quantity integer)
favorites (object, optional) - Favorites (product_id string -> quantity integer, typically 1)
total_count (int, optional) - Total inventory count (auto-calculated, but can be set manually)

Example Request:

POST /profiles Headers: Authorization: Bearer <token> { "username": "cardcollector", "full_name": "John Doe", "avatar_url": "https://example.com/avatar.jpg", "currency": "EUR", "items": { "12345": 5, "67890": 2 }, "favorites": { "12345": 1 } }

Example Response:

{ "id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "username": "cardcollector", "full_name": "John Doe", "avatar_url": "https://example.com/avatar.jpg", "currency": "EUR", "items": { "12345": 5, "67890": 2 }, "favorites": { "12345": 1 }, "total_count": 7, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }

Deck Lists

GET /deck-lists
List deck lists with optional filtering. Public read access - anyone can view all deck lists. Optional filters: user_id and/or category_id. Results are sorted by updated_at (descending).

Query Parameters:

page (int, optional, default: 1)
limit (int, optional, default: 100, max: 1000)
user_id (string/UUID, optional) - Filter by user_id
category_id (int, optional) - Filter by category_id

Response Schema:

PaginatedResponse<DeckList>
DeckList object
deck_list_id bigint (required, auto-increment)
user_id uuid (required)
category_id bigint (required)
name text (required)
items jsonb (default: {}, dict of product_id string -> quantity integer)
created_at timestamp with time zone (required)
updated_at timestamp with time zone (required)
card_count integer (required, default: 0)

Example Response:

{ "data": [ { "deck_list_id": 1, "user_id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "category_id": 1, "name": "My Deck", "items": { "12345": 4, "67890": 2 }, "card_count": 6, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" } ], "page": 1, "limit": 100, "total": 10, "has_more": false }
GET /deck-lists/{deck_list_id}
Get a single deck list by primary key (deck_list_id). Public read access - no authentication required.

Response Schema:

DeckList

Example Response:

{ "deck_list_id": 1, "user_id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "category_id": 1, "name": "My Deck", "items": { "12345": 4, "67890": 2 }, "card_count": 6, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }
POST /deck-lists
Create a new deck list for the current user. πŸ”’ Requires Authentication

Request Body:

category_id (int, required)
name (string, required)
items (object, optional) - Dictionary of product_id (string) -> quantity (int)

Example Request:

POST /deck-lists { "category_id": 1, "name": "My Deck", "items": { "12345": 4, "67890": 2 } }
PATCH /deck-lists/{deck_list_id}
Update a deck list name for the current user. πŸ”’ Requires Authentication
Note: To update items, use POST /deck-lists/{deck_list_id}/items endpoint instead.

Headers:

Authorization (string, required) - Bearer token

Request Body:

name (string, optional) - New name for the deck list

Example Request:

PATCH /deck-lists/5 Headers: Authorization: Bearer <token> { "name": "Updated Deck Name" }
DELETE /deck-lists/{deck_list_id}
Delete a deck list for the current user. πŸ”’ Requires Authentication
POST /deck-lists/{deck_list_id}/items
Update items in a deck list for the current user. This is the only endpoint for updating deck list items. Merges new items with existing items. Items with quantity 0 are automatically removed. πŸ”’ Requires Authentication
Format: JSON object where keys are product_id strings (e.g., "12345") and values are integer quantities (e.g., 4).

Headers:

Authorization (string, required) - Bearer token

Request Body:

items (object, required) - Dictionary of product_id (string) -> quantity (integer). New items are merged with existing items, overriding duplicates.

Example Request:

POST /deck-lists/5/items Headers: Authorization: Bearer <token> { "items": { "12345": 4, "67890": 2, "11111": 1 } }

Response Schema:

DeckList

Example Response:

{ "deck_list_id": 5, "user_id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "category_id": 1, "name": "My Deck", "items": { "12345": 4, "67890": 2, "11111": 1 }, "card_count": 7, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }
DELETE /deck-lists/{deck_list_id}/items
Delete items from a deck list for the current user. πŸ”’ Requires Authentication

Headers:

Authorization (string, required) - Bearer token

Request Body:

product_ids (array[int], required, min: 1) - List of product IDs to remove from the deck list

Example Request:

DELETE /deck-lists/5/items Headers: Authorization: Bearer <token> { "product_ids": [12345, 67890] }

Response Schema:

DeckList

Example Response:

{ "deck_list_id": 5, "user_id": "f4f079f2-2d7f-411b-b309-67dc9f019f57", "category_id": 1, "name": "My Deck", "items": { "11111": 1 }, "card_count": 1, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T08:00:00Z" }

TCGHermit API v1.0.0

For interactive API exploration, visit Swagger UI