{"schema_version":1,"contract":{"api_base_url":"https://api.lotteryfeedapi.com/v1","openapi_url":"https://lotteryfeedapi.com/openapi.json","postman_collection_url":"https://lotteryfeedapi.com/postman-collection.json"},"auth":{"type":"bearer","guidance":"Keep production lf_live keys in server-side secrets. Browser integrations should use widgets or revocable feed links instead of bearer API keys. Deterministic sandbox credentials work only while public sandbox access is enabled."},"codegen":{"source":"openapi-generator-cli","input":"https://lotteryfeedapi.com/openapi.json","targets":[{"language":"typescript","generator":"typescript-fetch"},{"language":"python","generator":"python"},{"language":"php","generator":"php"}]},"examples":[{"id":"typescript-fetch-server-client","title":"TypeScript server fetch client","language":"typescript","runtime":"server","description":"Small fetch wrapper for Node.js, Bun, Next.js route handlers, or server actions.","entrypoint":"createLotteryFeedClient","code":"type LotteryFeedClientOptions = {\n  apiKey: string;\n  baseUrl?: string;\n  timeoutMs?: number;\n};\n\ntype LatestResultRow = {\n  game_code: string;\n  draw_date: string;\n  numbers_formatted?: string;\n  numbers_string?: string;\n};\n\ntype LatestResultsResponse = {\n  data?: LatestResultRow[];\n};\n\ntype CheckResultResponse = {\n  matched: boolean;\n  match_class: string;\n  submitted_numbers_formatted: string;\n  winning_numbers_formatted: string;\n};\n\ntype LotteryFeedRateLimit = {\n  scope: 'api_key' | 'workspace' | 'sandbox_client';\n  window: 'utc_day' | 'utc_month' | 'sliding_window';\n  limit: number;\n  used?: number;\n  remaining: number;\n  reset_at?: string;\n  retry_after_seconds: number;\n  hint: string;\n};\n\ntype LotteryFeedErrorPayload = {\n  error?: {\n    code?: string;\n    message?: string;\n    rate_limit?: LotteryFeedRateLimit;\n  };\n};\n\nexport class LotteryFeedApiError extends Error {\n  readonly status: number;\n  readonly code: string;\n  readonly endpoint: string;\n  readonly rateLimit?: LotteryFeedRateLimit;\n  readonly retryAfterSeconds?: number;\n\n  constructor(input: {\n    status: number;\n    code: string;\n    message: string;\n    endpoint: string;\n    rateLimit?: LotteryFeedRateLimit;\n    retryAfterSeconds?: number;\n  }) {\n    super('Lottery Feed API ' + input.status + ' ' + input.code + ': ' + input.message);\n    this.name = 'LotteryFeedApiError';\n    this.status = input.status;\n    this.code = input.code;\n    this.endpoint = input.endpoint;\n    this.rateLimit = input.rateLimit;\n    this.retryAfterSeconds = input.retryAfterSeconds;\n  }\n}\n\nexport function createLotteryFeedClient({\n  apiKey,\n  baseUrl = 'https://api.lotteryfeedapi.com/v1',\n  timeoutMs = 10000\n}: LotteryFeedClientOptions) {\n  async function request<T>(\n    path: string,\n    options: {\n      method?: 'GET' | 'POST';\n      params?: Record<string, string | number | undefined>;\n      body?: unknown;\n      authenticated?: boolean;\n    } = {}\n  ) {\n    const method = options.method || 'GET';\n    const url = new URL(path.replace(/^\\/+/, ''), baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);\n    for (const [key, value] of Object.entries(options.params || {})) {\n      if (value !== undefined && value !== '') url.searchParams.set(key, String(value));\n    }\n\n    const controller = new AbortController();\n    const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n    try {\n      const response = await fetch(url, {\n        method,\n        headers: {\n          accept: 'application/json',\n          ...(options.authenticated === false ? {} : { authorization: `Bearer ${apiKey}` }),\n          ...(options.body === undefined ? {} : { 'content-type': 'application/json' })\n        },\n        body: options.body === undefined ? undefined : JSON.stringify(options.body),\n        signal: controller.signal\n      });\n\n      if (!response.ok) {\n        const payload = (await response.json().catch(() => null)) as LotteryFeedErrorPayload | null;\n        const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));\n        throw new LotteryFeedApiError({\n          status: response.status,\n          code: payload?.error?.code || 'HTTP_' + response.status,\n          message: payload?.error?.message || response.statusText || 'Request failed.',\n          endpoint: url.pathname,\n          rateLimit: payload?.error?.rate_limit,\n          retryAfterSeconds: retryAfterSeconds ?? payload?.error?.rate_limit?.retry_after_seconds\n        });\n      }\n\n      return (await response.json()) as T;\n    } finally {\n      clearTimeout(timeout);\n    }\n  }\n\n  return {\n    latest: (params?: { state?: string; ball_count?: number; game_code?: string; limit?: number }) =>\n      request<LatestResultsResponse>('/results/latest', { params }),\n    history: (params: { date: string; state?: string; game_code?: string; ball_count?: number; limit?: number }) =>\n      request('/results/history', { params }),\n    check: (body: { game_code: string; draw_date: string; numbers: string[] }) =>\n      request<CheckResultResponse>('/results/check', { method: 'POST', body }),\n    lotteries: (params?: { state?: string; ball_count?: number; limit?: number }) => request('/lotteries', { params }),\n    status: () => request('/status', { authenticated: false })\n  };\n}\n\nfunction parseRetryAfter(value: string | null) {\n  if (!value) return undefined;\n  const seconds = Number(value);\n  return Number.isFinite(seconds) && seconds > 0 ? seconds : undefined;\n}\n\nconst client = createLotteryFeedClient({\n  apiKey: 'lf_test_demo_sandbox'\n});\n\ntry {\n  const latest = await client.latest({ state: 'MD', ball_count: 3, limit: 1 });\n  console.log(latest.data?.[0]?.numbers_formatted);\n\n  const check = await client.check({ game_code: 'md-pick3-midday', draw_date: '2026-06-21', numbers: ['2', '3', '6'] });\n  console.log(check.match_class, check.winning_numbers_formatted);\n} catch (error) {\n  if (error instanceof LotteryFeedApiError && error.code === 'RATE_LIMIT_EXCEEDED') {\n    console.warn('Pause this key for', error.retryAfterSeconds, 'seconds', error.rateLimit);\n  }\n  throw error;\n}"},{"id":"python-httpx-server-client","title":"Python httpx client","language":"python","runtime":"server","description":"Minimal backend client with timeout, query serialization, and compact JSON parsing.","entrypoint":"LotteryFeedClient","code":"import os\nimport httpx\n\n\nclass LotteryFeedApiError(Exception):\n    def __init__(\n        self,\n        status_code: int,\n        code: str,\n        message: str,\n        rate_limit: dict | None = None,\n        retry_after_seconds: int | None = None,\n    ):\n        super().__init__(f\"Lottery Feed API {status_code} {code}: {message}\")\n        self.status_code = status_code\n        self.code = code\n        self.rate_limit = rate_limit\n        self.retry_after_seconds = retry_after_seconds\n\n\nclass LotteryFeedClient:\n    def __init__(self, api_key: str, base_url: str = \"https://api.lotteryfeedapi.com/v1\", timeout: float = 10.0):\n        self.api_key = api_key\n        self.base_url = base_url.rstrip(\"/\")\n        self.timeout = timeout\n\n    def _raise_api_error(self, response: httpx.Response) -> None:\n        try:\n            payload = response.json()\n        except ValueError:\n            payload = {}\n        error = payload.get(\"error\") if isinstance(payload, dict) else {}\n        if not isinstance(error, dict):\n            error = {}\n        retry_after = response.headers.get(\"retry-after\")\n        retry_after_seconds = int(retry_after) if retry_after and retry_after.isdigit() else None\n        rate_limit = error.get(\"rate_limit\") if isinstance(error.get(\"rate_limit\"), dict) else None\n        raise LotteryFeedApiError(\n            status_code=response.status_code,\n            code=error.get(\"code\") or f\"HTTP_{response.status_code}\",\n            message=error.get(\"message\") or response.reason_phrase or \"Request failed.\",\n            rate_limit=rate_limit,\n            retry_after_seconds=retry_after_seconds or (rate_limit or {}).get(\"retry_after_seconds\"),\n        )\n\n    def get(self, path: str, params: dict | None = None) -> dict:\n        response = httpx.get(\n            f\"{self.base_url}/{path.lstrip('/')}\",\n            params={k: v for k, v in (params or {}).items() if v not in (None, \"\")},\n            headers={\"accept\": \"application/json\", \"authorization\": f\"Bearer {self.api_key}\"},\n            timeout=self.timeout,\n        )\n        if response.status_code >= 400:\n            self._raise_api_error(response)\n        return response.json()\n\n    def post(self, path: str, payload: dict) -> dict:\n        response = httpx.post(\n            f\"{self.base_url}/{path.lstrip('/')}\",\n            json=payload,\n            headers={\"accept\": \"application/json\", \"authorization\": f\"Bearer {self.api_key}\"},\n            timeout=self.timeout,\n        )\n        if response.status_code >= 400:\n            self._raise_api_error(response)\n        return response.json()\n\n    def latest(self, **params: object) -> dict:\n        return self.get(\"/results/latest\", params)\n\n    def check(self, game_code: str, draw_date: str, numbers: list[str]) -> dict:\n        return self.post(\"/results/check\", {\"game_code\": game_code, \"draw_date\": draw_date, \"numbers\": numbers})\n\n\nclient = LotteryFeedClient(\"lf_test_demo_sandbox\")\ntry:\n    latest = client.latest(state=\"MD\", ball_count=3, limit=1)\n    print(latest[\"data\"][0][\"numbers_formatted\"])\n\n    check = client.check(game_code=\"md-pick3-midday\", draw_date=\"2026-06-21\", numbers=[\"2\", \"3\", \"6\"])\n    print(check[\"match_class\"], check[\"winning_numbers_formatted\"])\nexcept LotteryFeedApiError as exc:\n    if exc.code == \"RATE_LIMIT_EXCEEDED\":\n        print(\"pause key for\", exc.retry_after_seconds, \"seconds\", exc.rate_limit)\n    raise"},{"id":"php-curl-server-client","title":"PHP cURL client","language":"php","runtime":"server","description":"Server-side PHP example for CRM or platform integrations.","entrypoint":"lottery_feed_request","code":"<?php\nfinal class LotteryFeedApiException extends RuntimeException {\n    public function __construct(\n        public readonly int $statusCode,\n        public readonly string $apiCode,\n        public readonly ?array $rateLimit = null,\n        public readonly ?int $retryAfterSeconds = null,\n        string $message = 'Request failed.'\n    ) {\n        parent::__construct('Lottery Feed API ' . $statusCode . ' ' . $apiCode . ': ' . $message);\n    }\n}\n\nfunction lottery_feed_request(string $method, string $path, array $query = [], ?array $payload = null): array {\n    $apiKey = 'lf_test_demo_sandbox';\n    $baseUrl = 'https://api.lotteryfeedapi.com/v1';\n    $query = array_filter($query, fn($value) => $value !== null && $value !== '');\n    $url = $baseUrl . '/' . ltrim($path, '/');\n    if ($query) {\n        $url .= '?' . http_build_query($query);\n    }\n\n    $headers = [\n        'Accept: application/json',\n        'Authorization: Bearer ' . $apiKey,\n    ];\n    if ($payload !== null) {\n        $headers[] = 'Content-Type: application/json';\n    }\n\n    $ch = curl_init($url);\n    curl_setopt_array($ch, [\n        CURLOPT_RETURNTRANSFER => true,\n        CURLOPT_HEADER => true,\n        CURLOPT_TIMEOUT => 10,\n        CURLOPT_HTTPHEADER => $headers,\n    ]);\n    if (strtoupper($method) !== 'GET') {\n        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));\n    }\n    if ($payload !== null) {\n        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_THROW_ON_ERROR));\n    }\n\n    $rawResponse = curl_exec($ch);\n    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);\n    if ($rawResponse === false) {\n        throw new RuntimeException('Lottery Feed API transport error: ' . curl_error($ch));\n    }\n    $rawHeaders = substr($rawResponse, 0, $headerSize);\n    $body = substr($rawResponse, $headerSize);\n    $decoded = $body === '' ? [] : json_decode($body, true, flags: JSON_THROW_ON_ERROR);\n\n    if ($status >= 400) {\n        $error = is_array($decoded['error'] ?? null) ? $decoded['error'] : [];\n        $rateLimit = is_array($error['rate_limit'] ?? null) ? $error['rate_limit'] : null;\n        $retryAfter = lottery_feed_header($rawHeaders, 'retry-after');\n        throw new LotteryFeedApiException(\n            $status,\n            is_string($error['code'] ?? null) ? $error['code'] : 'HTTP_' . $status,\n            $rateLimit,\n            is_numeric($retryAfter) ? (int) $retryAfter : ($rateLimit['retry_after_seconds'] ?? null),\n            is_string($error['message'] ?? null) ? $error['message'] : 'Request failed.'\n        );\n    }\n\n    return $decoded;\n}\n\nfunction lottery_feed_header(string $rawHeaders, string $name): ?string {\n    foreach (explode(\"\\n\", $rawHeaders) as $line) {\n        if (str_contains($line, ':')) {\n            [$headerName, $value] = array_map('trim', explode(':', $line, 2));\n            if (strcasecmp($headerName, $name) === 0) {\n                return $value;\n            }\n        }\n    }\n    return null;\n}\n\nfunction lottery_feed_get(string $path, array $query = []): array {\n    return lottery_feed_request('GET', $path, $query);\n}\n\nfunction lottery_feed_post(string $path, array $payload): array {\n    return lottery_feed_request('POST', $path, [], $payload);\n}\n\ntry {\n    $latest = lottery_feed_get('/results/latest', ['state' => 'MD', 'ball_count' => 3, 'limit' => 1]);\n    echo $latest['data'][0]['numbers_formatted'] . PHP_EOL;\n\n    $check = lottery_feed_post('/results/check', [\n        'game_code' => 'md-pick3-midday',\n        'draw_date' => '2026-06-21',\n        'numbers' => ['2', '3', '6'],\n    ]);\n    echo $check['match_class'] . ' ' . $check['winning_numbers_formatted'] . PHP_EOL;\n} catch (LotteryFeedApiException $exception) {\n    if ($exception->apiCode === 'RATE_LIMIT_EXCEEDED') {\n        error_log('Pause this key for ' . $exception->retryAfterSeconds . ' seconds.');\n    }\n    throw $exception;\n}"},{"id":"curl-result-check-smoke","title":"cURL result checker smoke test","language":"curl","runtime":"ci","description":"POST JSON smoke request for the deterministic sandbox checker response.","entrypoint":"POST /results/check","code":"curl -sS 'https://api.lotteryfeedapi.com/v1/results/check' \\\n  -H 'Accept: application/json' \\\n  -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer lf_test_demo_sandbox' \\\n  --data '{\"game_code\":\"md-pick3-midday\",\"draw_date\":\"2026-06-21\",\"numbers\":[\"2\",\"3\",\"6\"]}'"},{"id":"curl-sandbox-smoke","title":"cURL sandbox smoke test","language":"curl","runtime":"ci","description":"Copy-ready CI smoke request using deterministic sandbox fixtures.","entrypoint":"GET /results/latest","code":"curl -sS 'https://api.lotteryfeedapi.com/v1/results/latest?state=MD&ball_count=3&limit=1' \\\n  -H 'Accept: application/json' \\\n  -H 'Authorization: Bearer lf_test_demo_sandbox'"}],"smoke_tests":[{"id":"latest-md-pick3-sandbox","openapi_operation_id":"listLatestResults","purpose":"Verify auth, query serialization, compact result parsing, and deterministic sandbox fixtures.","availability":"requires_enabled_public_sandbox","request":{"method":"GET","url":"https://api.lotteryfeedapi.com/v1/results/latest?state=MD&ball_count=3&limit=1","headers":{"accept":"application/json","authorization":"Bearer lf_test_demo_sandbox"}},"expected":{"status":200,"first_row":{"game_code":"md-pick3-midday","draw_date":"2026-06-21","numbers_formatted":"2-3-6","numbers_string":"2-3-6"}}},{"id":"check-md-pick3-sandbox","openapi_operation_id":"checkLotteryResult","purpose":"Verify POST JSON handling and checker response parsing with formatted submitted and winning numbers.","availability":"requires_enabled_public_sandbox","request":{"method":"POST","url":"https://api.lotteryfeedapi.com/v1/results/check","headers":{"accept":"application/json","authorization":"Bearer lf_test_demo_sandbox","content-type":"application/json"},"body":{"game_code":"md-pick3-midday","draw_date":"2026-06-21","numbers":["2","3","6"]}},"expected":{"status":200,"matched":true,"match_class":"exact_order","submitted_numbers_formatted":"2-3-6","winning_numbers_formatted":"2-3-6"}}],"version_policy":{"current_namespace":"/v1","compatibility":"stable compact public read model","changelog_url":"https://lotteryfeedapi.com/api-docs/changelog","breaking_change_policy":"Removing a response field; Changing a response field type; Changing documented status meanings; Tightening required parameters; Changing authentication behavior","non_breaking_change_policy":"Adding optional response fields; Adding enum values with documented fallback behavior; Adding new endpoints; Adding new filters that keep existing defaults stable","deprecation_headers":["Deprecation","Sunset","Link"],"deprecation_link_rel":"deprecation"}}