{
    "openapi": "3.1.0",
    "info": {
        "title": "FiveMX catalog API",
        "summary": "Read-only access to the FiveMX FiveM resource catalog.",
        "description": "Read-only. Every operation is a GET, needs no key, sets no cookie, and answers without a session.\n\nRate limiting is enforced at the edge rather than per key. Stay under roughly one request per second and prefer a larger `per_page` over more pages.\n\nEvery response, success or error, carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, plus the equivalent X-RateLimit-* names for older tooling. These describe a per-IP, 60-second advisory window and exist so a caller can see its own position before the edge limit rejects it. They are not themselves the enforcement point, and reaching zero remaining is a signal to back off rather than a guarantee of what happens next.\n\nErrors are JSON in every case, including for paths outside this document. The shape is `{\"code\",\"message\",\"data\":{\"status\",\"hint\"}}`; `code` is a stable machine identifier and `message` is human-readable text that may change.\n\nCompanion files: /llms.txt describes the site in words, /sitemap_index.xml lists every indexable URL, and /robots.txt states the crawl policy. Cart, checkout, account and order paths are disallowed there and are not part of this API.\n\nPrices are decimal strings in the shop base currency reported by /wp-json/fivemx/v1/catalog. The storefront also displays converted currencies; those conversions are presentational and are not returned here.",
        "version": "1.0.0",
        "contact": {
            "name": "FiveMX",
            "url": "https://fivemx.com/contact/"
        },
        "license": {
            "name": "Proprietary",
            "identifier": "LicenseRef-fivemx-catalog-read-only"
        }
    },
    "servers": [
        {
            "url": "https://fivemx.com",
            "description": "Production."
        }
    ],
    "tags": [
        {
            "name": "catalog",
            "description": "Products and product categories."
        }
    ],
    "paths": {
        "/wp-json/fivemx/v1/catalog": {
            "get": {
                "operationId": "getCatalogIndex",
                "summary": "Catalog index",
                "description": "Counts, the shop base currency, and links to every other endpoint in this contract. Fetch this first to discover the surface without parsing HTML.",
                "tags": [
                    "catalog"
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/CatalogIndex"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "The request was rejected. `code` names the offending parameter.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No resource matches this path.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/wp-json/fivemx/v1/catalog/categories": {
            "get": {
                "operationId": "listCategories",
                "summary": "List product categories",
                "description": "Every product category that holds at least one visible product, with the canonical URL of its archive page.",
                "tags": [
                    "catalog"
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/CategoryList"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "The request was rejected. `code` names the offending parameter.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No resource matches this path.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/wp-json/fivemx/v1/catalog/products": {
            "get": {
                "operationId": "listProducts",
                "summary": "List products",
                "description": "A page of visible, catalog-listed products, ordered by ascending post ID so that paging through the whole catalog is stable while products are added. Prices are in the shop base currency reported by the catalog index; the storefront also displays other currencies, and those conversions are not part of this contract. `summary` is truncated to 300 characters here and returned whole by the single-product endpoint, which also adds `description`.",
                "tags": [
                    "catalog"
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ProductList"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "The request was rejected. `code` names the offending parameter.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No resource matches this path.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "description": "One-based page number. The response reports total_pages.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "Rows per page. Values above the maximum are rejected rather than clamped, so a caller never silently receives fewer rows than it asked for.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 50,
                            "default": 20
                        }
                    },
                    {
                        "name": "category",
                        "in": "query",
                        "required": false,
                        "description": "Restrict to one product category slug, as returned by /catalog/categories.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "framework",
                        "in": "query",
                        "required": false,
                        "description": "Restrict to products carrying this pa_framework attribute term.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "esx",
                                "qbcore",
                                "qbox",
                                "standalone"
                            ]
                        }
                    }
                ]
            }
        },
        "/wp-json/fivemx/v1/catalog/products/{slug}": {
            "get": {
                "operationId": "getProduct",
                "summary": "Get one product by slug",
                "description": "One product, addressed by the same slug that appears in its canonical URL. Returns the untruncated `summary` and the full `description`, neither of which the list endpoint carries.",
                "tags": [
                    "catalog"
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Product"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "The request was rejected. `code` names the offending parameter.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No resource matches this path.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "RateLimit-Limit": {
                                "description": "Requests allowed per 60-second window for the calling IP, currently 60. Advisory: the edge enforces the real limit and this number can change without notice.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Remaining": {
                                "description": "Requests left in the current window for the calling IP.",
                                "schema": {
                                    "type": "integer"
                                }
                            },
                            "RateLimit-Reset": {
                                "description": "Seconds until the current window resets.",
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "slug",
                        "in": "path",
                        "required": true,
                        "description": "The product slug, the last path segment of its canonical URL.",
                        "schema": {
                            "type": "string",
                            "pattern": "^[a-z0-9-]+$"
                        }
                    }
                ]
            }
        }
    },
    "components": {
        "schemas": {
            "CatalogIndex": {
                "type": "object",
                "required": [
                    "name",
                    "currency",
                    "product_count",
                    "category_count",
                    "endpoints"
                ],
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    },
                    "currency": {
                        "type": "string"
                    },
                    "product_count": {
                        "type": "integer"
                    },
                    "category_count": {
                        "type": "integer"
                    },
                    "developers": {
                        "type": "string",
                        "format": "uri"
                    },
                    "openapi": {
                        "type": "string",
                        "format": "uri"
                    },
                    "llms_txt": {
                        "type": "string",
                        "format": "uri"
                    },
                    "sitemap": {
                        "type": "string",
                        "format": "uri"
                    },
                    "endpoints": {
                        "type": "object",
                        "additionalProperties": {
                            "type": "string",
                            "format": "uri"
                        }
                    }
                }
            },
            "CategoryList": {
                "type": "object",
                "required": [
                    "categories"
                ],
                "properties": {
                    "categories": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": [
                                "slug",
                                "name",
                                "count",
                                "url"
                            ],
                            "properties": {
                                "slug": {
                                    "type": "string"
                                },
                                "name": {
                                    "type": "string"
                                },
                                "count": {
                                    "type": "integer"
                                },
                                "url": {
                                    "type": "string",
                                    "format": "uri"
                                },
                                "parent": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                }
                            }
                        }
                    }
                }
            },
            "ProductList": {
                "type": "object",
                "required": [
                    "products",
                    "page",
                    "per_page",
                    "total",
                    "total_pages"
                ],
                "properties": {
                    "products": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/Product"
                        }
                    },
                    "page": {
                        "type": "integer"
                    },
                    "per_page": {
                        "type": "integer"
                    },
                    "total": {
                        "type": "integer"
                    },
                    "total_pages": {
                        "type": "integer"
                    }
                }
            },
            "Product": {
                "type": "object",
                "required": [
                    "slug",
                    "name",
                    "url",
                    "price",
                    "currency",
                    "in_stock",
                    "purchasable"
                ],
                "properties": {
                    "slug": {
                        "type": "string",
                        "description": "Stable identifier and the last segment of `url`."
                    },
                    "name": {
                        "type": "string"
                    },
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "Canonical English URL. Translated locales live under /de/, /fr/, /es/, /pt/, /it/, /tr/, /nl/ and /pl/ with translated slugs."
                    },
                    "price": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Current price as a decimal string in `currency`, or null when the product has no price set."
                    },
                    "regular_price": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Price before any active sale."
                    },
                    "on_sale": {
                        "type": "boolean"
                    },
                    "currency": {
                        "type": "string",
                        "description": "ISO 4217 code of the shop base currency."
                    },
                    "in_stock": {
                        "type": "boolean",
                        "description": "Stock state. Check this as well as `purchasable`: WooCommerce reports a product as purchasable while it is out of stock."
                    },
                    "purchasable": {
                        "type": "boolean",
                        "description": "Whether WooCommerce will accept this product into a cart."
                    },
                    "categories": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "Category slugs, matching /catalog/categories."
                    },
                    "frameworks": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "FiveM framework compatibility from the pa_framework attribute. An empty array means the product declares none, not that it is standalone."
                    },
                    "image": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri"
                    },
                    "summary": {
                        "type": "string",
                        "description": "Plain-text product summary, from the short description. Truncated to 300 characters by the list endpoint and returned whole by the single-product endpoint. Empty when the product stores none."
                    },
                    "description": {
                        "type": "string",
                        "description": "Plain-text full product description. Returned only by the single-product endpoint."
                    }
                }
            },
            "Error": {
                "type": "object",
                "required": [
                    "code",
                    "message",
                    "data"
                ],
                "properties": {
                    "code": {
                        "type": "string",
                        "description": "Stable machine identifier. Branch on this, not on `message`."
                    },
                    "message": {
                        "type": "string",
                        "description": "Human-readable text. Subject to change and to translation."
                    },
                    "data": {
                        "type": "object",
                        "required": [
                            "status"
                        ],
                        "properties": {
                            "status": {
                                "type": "integer",
                                "description": "Repeats the HTTP status code."
                            },
                            "hint": {
                                "type": "string",
                                "description": "What to do instead. Present whenever a next step exists."
                            },
                            "params": {
                                "type": "object",
                                "description": "Per-parameter messages, present on a 400."
                            }
                        }
                    }
                }
            }
        }
    }
}
