{
  "openapi": "3.1.0",
  "info": {
    "title": "TrustPadel Crest API",
    "summary": "Public + authenticated endpoints for the Crest rating standard.",
    "version": "1.0.0",
    "description": "Two surfaces:\n  - Keyless (`/api/v1/*`): venue search, tournament listings, coach match, public Crest lookups. CORS-open. Subject to per-IP throttling.\n  - Keyed (`/api/v1/*` with Authorization): higher rate limits + write scopes. Tier limits documented at /developers/.\n\nLocked invariants:\n  - Minor + private accounts return 404 indistinguishable from \"no such player\".\n  - Article-9 special-category data is never present in any response.\n  - Provisional ratings carry an explicit boolean + confidence_band.\n  - Records below provenance ≥ 0.7 or integrity ≥ 0.7 are absent (not flagged).\n\nStandard buyer licence: https://trustpadel.com/data-licence/",
    "contact": {
      "name": "TrustPadel developer support",
      "email": "developers@trustpadel.com",
      "url": "https://trustpadel.com/developers/"
    },
    "license": {
      "name": "TrustPadel Data Buyer Licence",
      "url": "https://trustpadel.com/data-licence/"
    }
  },
  "servers": [
    {
      "url": "https://trustpadel.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "players",
      "description": "Resolve handles + read public Crests."
    },
    {
      "name": "rankings",
      "description": "Sport + discipline leaderboards."
    },
    {
      "name": "venues",
      "description": "Venue search (keyless)."
    },
    {
      "name": "tournaments",
      "description": "Upcoming public tournaments (keyless)."
    },
    {
      "name": "coaches",
      "description": "Coach match (keyless)."
    },
    {
      "name": "badge",
      "description": "Embeddable Crest badge SVG."
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "crst_<prefix>.<secret>",
        "description": "Authorization header carrying a key from /account/api-keys. The plaintext key is shown exactly once at creation."
      }
    },
    "schemas": {
      "Crest": {
        "type": "object",
        "required": [
          "handle",
          "discipline",
          "crest_number",
          "tier",
          "provisional"
        ],
        "properties": {
          "handle": {
            "type": "string",
            "example": "sandbox-gold"
          },
          "display_name": {
            "type": "string",
            "nullable": true
          },
          "discipline": {
            "type": "string",
            "example": "padel-singles"
          },
          "sport": {
            "type": "string",
            "example": "padel"
          },
          "crest_number": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3000,
            "example": 1742
          },
          "tier": {
            "type": "string",
            "enum": [
              "unranked",
              "iron",
              "bronze",
              "silver",
              "gold",
              "platinum",
              "diamond",
              "crown",
              "sovereign"
            ]
          },
          "provisional": {
            "type": "boolean"
          },
          "confidence_band": {
            "type": "number",
            "nullable": true
          },
          "games_played": {
            "type": "integer",
            "nullable": true
          },
          "wins": {
            "type": "integer",
            "nullable": true
          },
          "last_played_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "as_of": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "nullable": true
          }
        }
      }
    },
    "responses": {
      "NotFound": {
        "description": "404. Used for: missing handle / minor player / private player / non-L2 handle. Callers MUST treat all four cases identically.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid Authorization header.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Per-tier rate limit exceeded. Retry after the Retry-After window."
      }
    }
  },
  "paths": {
    "/api/v1/players/{handle}/rating": {
      "get": {
        "tags": [
          "players"
        ],
        "summary": "Top-discipline Crest for a public handle",
        "description": "Returns the highest Crest across the player's public disciplines. 404 for minor / private / non-L2 with no distinction.",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "test_diamond_active"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Top Crest",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Crest"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/players/{handle}/crest/{discipline}": {
      "get": {
        "tags": [
          "players"
        ],
        "summary": "Per-discipline Crest",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "discipline",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Per-discipline Crest",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Crest"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/players/{handle}/history": {
      "get": {
        "tags": [
          "players"
        ],
        "summary": "Rating-event history (commercial_eligible only)",
        "description": "Last 50 (max 100) integrity-clean rating events. Disputed / sandbagging-flagged events drop until resolved. Requires history:read scope (Developer+ tier).",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "discipline",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Rating events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "handle": {
                      "type": "string"
                    },
                    "discipline": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "occurred_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "crest_before": {
                            "type": "integer",
                            "nullable": true
                          },
                          "crest_after": {
                            "type": "integer",
                            "nullable": true
                          },
                          "mu_before": {
                            "type": "number",
                            "nullable": true
                          },
                          "mu_after": {
                            "type": "number",
                            "nullable": true
                          },
                          "sigma_before": {
                            "type": "number",
                            "nullable": true
                          },
                          "sigma_after": {
                            "type": "number",
                            "nullable": true
                          },
                          "source": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/players/{handle}/badge": {
      "get": {
        "tags": [
          "badge"
        ],
        "summary": "Embeddable Crest badge SVG",
        "description": "Cacheable SVG (5min TTL). Returns a neutral \"Unrated\" badge for non-public handles — no 404 leak. Response is image/svg+xml.",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "theme",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ],
              "default": "light"
            }
          },
          {
            "name": "size",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "small",
                "medium"
              ],
              "default": "medium"
            }
          },
          {
            "name": "discipline",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "padel-singles"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "SVG badge",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/venues/search": {
      "get": {
        "tags": [
          "venues"
        ],
        "summary": "Search public venues (keyless)",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "city",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "country",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "has_indoor",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "has_outdoor",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "amenity",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Venue search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tournaments/upcoming": {
      "get": {
        "tags": [
          "tournaments"
        ],
        "summary": "Upcoming public tournaments (keyless)",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "city",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 365
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Upcoming tournaments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/coaches/match": {
      "get": {
        "tags": [
          "coaches"
        ],
        "summary": "Coach match (keyless)",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "level",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "language",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "mobile",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Coach matches",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "x-rally-tiers": {
    "free": {
      "per_min": 60,
      "per_month": 10000
    },
    "developer": {
      "per_min": 600,
      "per_month": 1000000
    },
    "pro": {
      "per_min": 3000,
      "per_month": 10000000
    },
    "enterprise": {
      "per_min": "negotiated",
      "per_month": "negotiated"
    }
  },
  "x-rally-sandbox-handles": [
    "test_diamond_active",
    "test_disputed_recent",
    "test_gold_active",
    "test_minor_invisible",
    "test_private_invisible",
    "test_provisional"
  ],
  "x-rally-invariants": [
    "minor + private return indistinguishable 404",
    "article-9 tags never in any feed",
    "provisional ratings are explicitly marked",
    "integrity-gated commercial feeds (absence, not flag)"
  ]
}