Search Endpoint

The primary filtering endpoint is POST /games/search. It accepts a JSON body containing all filter parameters, sort order, and pagination controls. A GET /games endpoint with query parameters exists for simple lookups, but the POST endpoint is the recommended interface for multi-dimensional queries.

Why POST for Search

Complex filter queries involve arrays, nested parameters, and boolean logic that map poorly to URL query strings:

  • mechanics=["cooperative","hand-management"]&mechanics_not=["dice-rolling"] is awkward as query parameters and ambiguous across HTTP client implementations.
  • URL length limits (practical limit around 2000 characters) can be exceeded by queries with many filter values.
  • JSON bodies have well-defined semantics for arrays, nulls, and nested objects.

The POST /games/search endpoint is not creating a resource – it is a query. This follows the established pattern used by Elasticsearch, Algolia, and other search APIs. The endpoint returns 200 OK, not 201 Created.

Request Schema

{
  // Dimension 1: Rating & Confidence
  "rating_min": null,
  "rating_max": null,
  "min_rating_votes": null,
  "confidence_min": null,

  // Dimension 2: Weight
  "weight_min": 2.0,
  "weight_max": 3.5,
  "effective_weight": false,
  "min_weight_votes": null,

  // Dimension 3: Player Count
  "players": 4,
  "players_min": null,
  "players_max": null,
  "top_at": null,
  "recommended_at": null,
  "effective": false,              // search across expansion combinations
  "include_integrations": false,   // include integrates_with products in effective mode
  "edition": null,                 // edition slug for edition-specific resolution

  // Dimension 4: Play Time
  "playtime_min": null,
  "playtime_max": 90,
  "community_playtime_min": null,
  "community_playtime_max": null,
  "playtime_source": "publisher",  // "publisher" or "community"
  "playtime_experience": null,     // "first_play", "learning", "experienced", "expert"

  // Dimension 5: Age Recommendation
  "age_min": null,
  "age_max": null,
  "community_age_min": null,
  "community_age_max": null,
  "age_source": "publisher",       // "publisher" or "community"

  // Dimension 6: Game Type & Mechanics
  "type": ["base_game", "standalone_expansion"],
  "mode": null,                    // shorthand: "all", "playable", "addons"
  "mechanics": ["cooperative"],    // OR logic
  "mechanics_all": null,           // AND logic
  "mechanics_not": null,           // exclusion

  // Dimension 7: Theme
  "theme": null,                   // OR logic
  "theme_not": ["space"],          // exclusion

  // Dimension 8: Metadata
  "designer": null,
  "publisher": null,
  "family": null,
  "category": null,
  "year_min": null,
  "year_max": null,
  "language_dependence": null,

  // Dimension 9: Corpus & Archetype (aspirational)
  "corpus": null,
  "corpus_rating_min": null,

  // Resource embedding
  "include": null,                 // e.g., ["mechanics", "experience_playtime"]

  // Pagination & sort
  "sort": "rating_desc",
  "limit": 20,
  "cursor": null
}

All fields are optional. Omitted or null fields are treated as inactive filters (no constraint on that dimension).

Field Types

FieldTypeDefaultDescription
rating_minfloatnullMinimum community rating (1.0-10.0)
rating_maxfloatnullMaximum community rating (1.0-10.0)
min_rating_votesintegernullMinimum rating vote count
confidence_minfloatnullMinimum rating confidence score (0.0-1.0)
weight_minfloatnullMinimum complexity weight (1.0-5.0)
weight_maxfloatnullMaximum complexity weight (1.0-5.0)
effective_weightbooleanfalseUse expansion-modified weights
min_weight_votesintegernullMinimum weight vote count
playersintegernullExact player count filter
players_minintegernullMinimum player count range
players_maxintegernullMaximum player count range
top_atintegernullCommunity highly-rated player count
recommended_atintegernullCommunity acceptable player count
effectivebooleanfalseEnable expansion-aware search
include_integrationsbooleanfalseInclude integrates_with products in effective mode combinations
editionstringnullEdition slug or ID for edition-specific property resolution. See Effective Mode.
playtime_minintegernullMinimum play time in minutes
playtime_maxintegernullMaximum play time in minutes
community_playtime_minintegernullMinimum community-reported play time
community_playtime_maxintegernullMaximum community-reported play time
playtime_sourcestring“publisher”Time source for playtime_min/max: “publisher” or “community”
playtime_experiencestringnullExperience adjustment: “first_play”, “learning”, “experienced”, “expert”
age_minintegernullMinimum age (source-toggled)
age_maxintegernullMaximum age (source-toggled)
community_age_minintegernullMinimum community-suggested age
community_age_maxintegernullMaximum community-suggested age
age_sourcestring“publisher”Age source for age_min/max: “publisher” or “community”
typestring[][“base_game”, “standalone_expansion”]Game type filter
modestringnullShorthand: “all”, “playable”, “addons”
mechanicsstring[]nullAny of these mechanics (OR)
mechanics_allstring[]nullAll of these mechanics (AND)
mechanics_notstring[]nullNone of these mechanics (NOT)
themestring[]nullAny of these themes (OR)
theme_notstring[]nullNone of these themes (NOT)
designerstring[]nullAny of these designers (slug or UUID)
publisherstring[]nullAny of these publishers (slug or UUID)
familystring[]nullAny of these families (slug or UUID)
categorystring[]nullAny of these categories (slug)
year_minintegernullPublished in or after this year
year_maxintegernullPublished in or before this year
language_dependencestring[]nullFilter by text dependence level
corpusstringnullFilter by player corpus (aspirational). See Dimensions: Corpus & Archetype.
corpus_rating_minfloatnullMinimum rating within the specified corpus (aspirational).
includestring[]nullEmbed related resources in response (e.g., ["mechanics", "experience_playtime"]). See ADR-0017.
sortstring“rating_desc”Sort order (see Dimensions)
limitinteger20Results per page (1-100)
cursorstringnullPagination cursor from previous response

Dimensional weight filters: Implementations that support the detailed weight mode may also accept per-dimension weight filters (weight_rules_complexity_min, weight_strategic_depth_min, etc.). See Dimensions: Dimensional Weight Filters.

GET Equivalent

For simple queries, GET /games accepts a subset of parameters as query strings:

GET /games?players=4&playtime_max=90&mechanics=cooperative&sort=rating_desc&limit=20

Array parameters use comma-separated values in GET:

GET /games?mechanics=cooperative,hand-management&theme_not=space

The GET endpoint supports all the same parameters but is less ergonomic for complex queries. Use POST when:

  • You have more than 3-4 active filters
  • You use array parameters with multiple values
  • You use both inclusion and exclusion parameters on the same dimension
  • Your query might exceed URL length limits

Response Schema

See Response Metadata for the full response structure.

Validation

The API validates the request body and returns RFC 9457 Problem Details for validation errors:

{
  "type": "https://api.opentabletop.org/errors/validation",
  "title": "Validation Error",
  "status": 422,
  "detail": "weight_min must be between 1.0 and 5.0",
  "errors": [
    {
      "field": "weight_min",
      "message": "must be between 1.0 and 5.0",
      "value": 6.0
    }
  ]
}

Validation Rules

  • rating_min and rating_max must be between 1.0 and 10.0
  • confidence_min must be between 0.0 and 1.0
  • weight_min and weight_max must be between 1.0 and 5.0
  • limit must be between 1 and 100
  • type values must be valid game type discriminators
  • mechanics, theme, category values must be valid slugs in the taxonomy
  • playtime_source must be “publisher” or “community”
  • age_source must be “publisher” or “community”
  • playtime_experience must be “first_play”, “learning”, “experienced”, or “expert”
  • language_dependence values must be valid dependence levels
  • sort must be a valid sort key
  • Unknown fields are ignored (forward compatibility)