{
  "openapi": "3.1.0",
  "info": {
    "title": "DidReset public API",
    "version": "1.0.0",
    "summary": "Official usage-limit reset signals for OpenAI Codex, Anthropic Claude Code and xAI Grok",
    "description": "Free, unauthenticated, read-only endpoints backing https://didreset.com: the departures board (last official reset, latest signal, cadence estimate and stats per provider), the announcement timeline as JSON or CSV, an RSS 2.0 feed, an .ics calendar builder for personal windows, an MCP server and a Markdown mirror of the board. Every event links to the original public post. If you use this data, please link back to didreset.com. Estimates are cadence estimates (last reset + median gap of the last 10 resets), not vendor schedules. Not affiliated with OpenAI, Anthropic or xAI.",
    "termsOfService": "https://didreset.com/api-docs",
    "contact": {
      "name": "DidReset",
      "url": "https://didreset.com/api-docs",
      "email": "hello@didreset.com"
    },
    "license": {
      "name": "Free to use with attribution — please link back to https://didreset.com",
      "identifier": "CC-BY-4.0"
    },
    "x-attribution": "If you use this data, link to didreset.com",
    "x-link-back": "https://didreset.com"
  },
  "externalDocs": {
    "description": "Human-readable documentation (English; Chinese at /zh/api-docs)",
    "url": "https://didreset.com/api-docs"
  },
  "servers": [
    { "url": "https://didreset.com", "description": "Production" }
  ],
  "tags": [
    { "name": "board", "description": "Departures board data" },
    { "name": "timeline", "description": "Announcement history" },
    { "name": "feeds", "description": "RSS and Markdown mirrors" },
    { "name": "calendar", "description": "Personal window calendar export" },
    { "name": "mcp", "description": "Model Context Protocol server" }
  ],
  "paths": {
    "/api/status": {
      "get": {
        "tags": ["board"],
        "operationId": "getStatus",
        "summary": "Board status for all three providers",
        "description": "One object per provider (codex, claude, grok): board status word, last official account-wide reset, latest signal of any kind, cadence estimate for the next reset, and stats. Cached at the edge for 60 seconds (Cache-Control: public, s-maxage=60, stale-while-revalidate=300). CORS: Access-Control-Allow-Origin: *.",
        "externalDocs": { "url": "https://didreset.com/api-docs#status" },
        "responses": {
          "200": {
            "description": "Current board",
            "headers": {
              "Cache-Control": { "schema": { "type": "string" }, "example": "public, s-maxage=60, stale-while-revalidate=300" },
              "Access-Control-Allow-Origin": { "schema": { "type": "string" }, "example": "*" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/StatusResponse" },
                "example": {
                  "updatedAt": "2026-09-18T03:42:59.306Z",
                  "providers": [
                    {
                      "provider": "codex",
                      "daysSince": 5.82,
                      "status": "overdue",
                      "lastReset": {
                        "id": "2098685367058612394",
                        "provider": "codex",
                        "kind": "reset",
                        "scope": "global",
                        "announcedAt": "2026-09-12T08:09:17.000Z",
                        "effectiveAt": null,
                        "sourceUrl": "https://x.com/thsottiaux/status/2098685367058612394",
                        "sourceAccount": "@thsottiaux",
                        "summary": "Reset all propagated. Sweet dreams. https://t.co/VgKVUixoJG",
                        "audience": [],
                        "verified": true
                      },
                      "latestSignal": {
                        "id": "2098685367058612394",
                        "provider": "codex",
                        "kind": "reset",
                        "scope": "global",
                        "announcedAt": "2026-09-12T08:09:17.000Z",
                        "effectiveAt": null,
                        "sourceUrl": "https://x.com/thsottiaux/status/2098685367058612394",
                        "sourceAccount": "@thsottiaux",
                        "summary": "Reset all propagated. Sweet dreams. https://t.co/VgKVUixoJG",
                        "audience": [],
                        "verified": true
                      },
                      "nextEstimate": "2026-09-14T12:14:05.000Z",
                      "stats": {
                        "totalResets": 44,
                        "medianIntervalDays": 2.17,
                        "meanIntervalDays": 8.37,
                        "longestWaitDays": 73.16,
                        "hourHistogramUtc": [4, 4, 4, 5, 5, 2, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 0, 2, 4, 2, 2, 5],
                        "firstRecordedAt": "2025-09-17T04:02:52.000Z"
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": { "$ref": "#/components/pathItems/CorsPreflight/options" }
    },
    "/api/timeline": {
      "get": {
        "tags": ["timeline"],
        "operationId": "getTimeline",
        "summary": "Announcement timeline (JSON, or CSV with format=csv)",
        "description": "Official announcements newest first. Each event carries the original post URL and account. Add format=csv for the same rows as text/csv (identical to /api/timeline.csv). Cached at the edge for 60 seconds. CORS: *.",
        "externalDocs": { "url": "https://didreset.com/api-docs#timeline" },
        "parameters": [
          { "$ref": "#/components/parameters/ProviderOrAll" },
          { "$ref": "#/components/parameters/Limit" },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Response format. csv returns text/csv with a header row.",
            "schema": { "type": "string", "enum": ["json", "csv"], "default": "json" }
          }
        ],
        "responses": {
          "200": {
            "description": "Events",
            "headers": {
              "Cache-Control": { "schema": { "type": "string" }, "example": "public, s-maxage=60, stale-while-revalidate=300" },
              "Access-Control-Allow-Origin": { "schema": { "type": "string" }, "example": "*" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TimelineResponse" },
                "example": {
                  "provider": "codex",
                  "updatedAt": "2026-09-18T03:43:00.208Z",
                  "events": [
                    {
                      "id": "2098685367058612394",
                      "provider": "codex",
                      "kind": "reset",
                      "scope": "global",
                      "announcedAt": "2026-09-12T08:09:17.000Z",
                      "effectiveAt": null,
                      "sourceUrl": "https://x.com/thsottiaux/status/2098685367058612394",
                      "sourceAccount": "@thsottiaux",
                      "summary": "Reset all propagated. Sweet dreams. https://t.co/VgKVUixoJG",
                      "audience": [],
                      "verified": true
                    }
                  ]
                }
              },
              "text/csv": {
                "schema": { "$ref": "#/components/schemas/TimelineCsv" },
                "example": "id,date,provider,kind,scope,summary,source_url,page_url\r\n2098685367058612394,2026-09-12T08:09:17.000Z,codex,reset,global,Reset all propagated. Sweet dreams. https://t.co/VgKVUixoJG,https://x.com/thsottiaux/status/2098685367058612394,https://didreset.com/codex#event-2098685367058612394\r\n"
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidQuery" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": { "$ref": "#/components/pathItems/CorsPreflight/options" }
    },
    "/api/timeline.csv": {
      "get": {
        "tags": ["timeline"],
        "operationId": "getTimelineCsv",
        "summary": "Announcement timeline as CSV",
        "description": "RFC 4180 CSV with a header row: id, date (ISO 8601 UTC), provider, kind, scope, summary, source_url, page_url. Fields with commas, quotes or line breaks are double-quoted; rows end with CRLF. Cached at the edge for 60 seconds. CORS: *.",
        "externalDocs": { "url": "https://didreset.com/api-docs#timeline-csv" },
        "parameters": [
          { "$ref": "#/components/parameters/ProviderOrAll" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "CSV rows",
            "headers": {
              "Content-Disposition": { "schema": { "type": "string" }, "example": "inline; filename=\"didreset-timeline-all.csv\"" },
              "Cache-Control": { "schema": { "type": "string" }, "example": "public, s-maxage=60, stale-while-revalidate=300" },
              "Access-Control-Allow-Origin": { "schema": { "type": "string" }, "example": "*" }
            },
            "content": {
              "text/csv": {
                "schema": { "$ref": "#/components/schemas/TimelineCsv" },
                "example": "id,date,provider,kind,scope,summary,source_url,page_url\r\n2098685367058612394,2026-09-12T08:09:17.000Z,codex,reset,global,Reset all propagated. Sweet dreams. https://t.co/VgKVUixoJG,https://x.com/thsottiaux/status/2098685367058612394,https://didreset.com/codex#event-2098685367058612394\r\n"
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidQuery" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": { "$ref": "#/components/pathItems/CorsPreflight/options" }
    },
    "/feed.xml": {
      "get": {
        "tags": ["feeds"],
        "operationId": "getFeed",
        "summary": "RSS 2.0 feed of the latest 100 announcements",
        "description": "Newest first, across providers or one provider with ?provider=. Each item's <link> and <guid> point to the permanent DidReset anchor https://didreset.com/{provider}#event-{id}; the original post is in <source url> and inside the description. Cache-Control: public, s-maxage=300, stale-while-revalidate=600. /api/feed is a 308 alias. CORS: *.",
        "externalDocs": { "url": "https://didreset.com/api-docs#feed" },
        "parameters": [{ "$ref": "#/components/parameters/Provider" }],
        "responses": {
          "200": {
            "description": "RSS document",
            "headers": {
              "Cache-Control": { "schema": { "type": "string" }, "example": "public, s-maxage=300, stale-while-revalidate=600" },
              "Access-Control-Allow-Origin": { "schema": { "type": "string" }, "example": "*" }
            },
            "content": {
              "application/rss+xml": {
                "schema": { "type": "string", "description": "RSS 2.0 XML" },
                "example": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\"><channel><title>DidReset · Codex / Claude / Grok usage reset announcements</title><item><title>Codex · Reset · 12 Sep 2026</title><link>https://didreset.com/codex#event-2098685367058612394</link><guid isPermaLink=\"true\">https://didreset.com/codex#event-2098685367058612394</guid><pubDate>Sat, 12 Sep 2026 08:09:17 GMT</pubDate><category>codex</category><category>reset</category><source url=\"https://x.com/thsottiaux/status/2098685367058612394\">@thsottiaux</source></item></channel></rss>"
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidQuery" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": { "$ref": "#/components/pathItems/CorsPreflight/options" }
    },
    "/api/ics": {
      "get": {
        "tags": ["calendar"],
        "operationId": "getIcs",
        "summary": "Build an .ics file for your own 5-hour / weekly reset times",
        "description": "Returns text/calendar with one 15-minute VEVENT per window passed in (resetAt for the 5-hour window, weeklyAt for the weekly window; at least one is required), each with a VALARM 5 minutes before. Times are written in UTC. Nothing is stored (Cache-Control: no-store); the file is the only copy. CORS: *.",
        "externalDocs": { "url": "https://didreset.com/api-docs#ics" },
        "parameters": [
          {
            "name": "provider",
            "in": "query",
            "required": true,
            "description": "Product the windows belong to (used in event titles).",
            "schema": { "type": "string", "enum": ["codex", "claude", "grok"] }
          },
          {
            "name": "resetAt",
            "in": "query",
            "required": false,
            "description": "Your 5-hour window reset time, ISO 8601 with offset (e.g. 2026-09-18T15:00:00Z). resetAt or weeklyAt is required.",
            "schema": { "type": "string", "format": "date-time" }
          },
          {
            "name": "weeklyAt",
            "in": "query",
            "required": false,
            "description": "Your weekly window reset time, ISO 8601 with offset.",
            "schema": { "type": "string", "format": "date-time" }
          },
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "description": "IANA time zone name, only echoed in the event description.",
            "schema": { "type": "string", "maxLength": 64, "pattern": "^[A-Za-z0-9_+\\-/]+$" }
          },
          {
            "name": "locale",
            "in": "query",
            "required": false,
            "description": "Language of event titles.",
            "schema": { "type": "string", "enum": ["en", "zh"], "default": "en" }
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar file",
            "headers": {
              "Content-Disposition": { "schema": { "type": "string" }, "example": "attachment; filename=\"didreset-codex.ics\"" },
              "Cache-Control": { "schema": { "type": "string" }, "example": "no-store" },
              "Access-Control-Allow-Origin": { "schema": { "type": "string" }, "example": "*" }
            },
            "content": {
              "text/calendar": {
                "schema": { "type": "string", "description": "RFC 5545 iCalendar" },
                "example": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//DidReset//Window Tracker//EN\r\nBEGIN:VEVENT\r\nDTSTART:20260918T150000Z\r\nDURATION:PT15M\r\nSUMMARY:Codex 5-hour window resets\r\nBEGIN:VALARM\r\nTRIGGER:-PT5M\r\nEND:VALARM\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidQuery" }
        }
      },
      "options": { "$ref": "#/components/pathItems/CorsPreflight/options" }
    },
    "/index.md": {
      "get": {
        "tags": ["feeds"],
        "operationId": "getIndexMarkdown",
        "summary": "Markdown mirror of the departures board (English)",
        "description": "text/markdown rendering of the board: status per provider, last reset, next estimate, latest six announcements with original post URLs, and the list of machine-readable endpoints. Regenerated per request, cached at the edge for 60 seconds. Chinese version at /zh/index.md.",
        "externalDocs": { "url": "https://didreset.com/api-docs#markdown" },
        "responses": {
          "200": {
            "description": "Markdown document",
            "content": { "text/markdown": { "schema": { "type": "string" } } }
          }
        }
      }
    },
    "/zh/index.md": {
      "get": {
        "tags": ["feeds"],
        "operationId": "getIndexMarkdownZh",
        "summary": "Markdown mirror of the departures board (Chinese)",
        "responses": {
          "200": {
            "description": "Markdown document",
            "content": { "text/markdown": { "schema": { "type": "string" } } }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "tags": ["mcp"],
        "operationId": "mcp",
        "summary": "MCP server (Streamable HTTP, JSON-RPC 2.0)",
        "description": "Model Context Protocol endpoint exposing three read-only tools: get_status (), get_timeline (provider?: codex|claude|grok|all, limit?: 1–100) and get_next_estimate (provider: codex|claude|grok). No authentication. Send Accept: application/json, text/event-stream. Rate limit: 60 requests per minute per IP, then 429 with Retry-After. /api/mcp is an alias.",
        "externalDocs": { "url": "https://didreset.com/api-docs#mcp" },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/JsonRpcRequest" },
              "examples": {
                "toolsList": { "value": { "jsonrpc": "2.0", "id": 1, "method": "tools/list" } },
                "getNextEstimate": {
                  "value": { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "get_next_estimate", "arguments": { "provider": "codex" } } }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response (application/json or text/event-stream, per the client's Accept header)",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcResponse" } },
              "text/event-stream": { "schema": { "type": "string" } }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": { "Retry-After": { "schema": { "type": "integer" }, "description": "Seconds to wait" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcResponse" } } }
          }
        }
      }
    }
  },
  "components": {
    "pathItems": {
      "CorsPreflight": {
        "options": {
          "operationId": "corsPreflight",
          "summary": "CORS preflight",
          "description": "Returns 204 with Access-Control-Allow-Origin: * and Access-Control-Allow-Methods: GET, OPTIONS.",
          "responses": {
            "204": {
              "description": "No content",
              "headers": {
                "Access-Control-Allow-Origin": { "schema": { "type": "string" }, "example": "*" },
                "Access-Control-Allow-Methods": { "schema": { "type": "string" }, "example": "GET, OPTIONS" },
                "Access-Control-Max-Age": { "schema": { "type": "integer" }, "example": 86400 }
              }
            }
          }
        }
      }
    },
    "parameters": {
      "Provider": {
        "name": "provider",
        "in": "query",
        "required": false,
        "description": "One provider only; omit for all.",
        "schema": { "type": "string", "enum": ["codex", "claude", "grok"] }
      },
      "ProviderOrAll": {
        "name": "provider",
        "in": "query",
        "required": false,
        "description": "Filter by provider.",
        "schema": { "type": "string", "enum": ["codex", "claude", "grok", "all"], "default": "all" }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Number of events, newest first.",
        "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 200 }
      }
    },
    "responses": {
      "InvalidQuery": {
        "description": "Invalid query parameters",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "invalid_query", "issues": [{ "code": "invalid_value", "path": ["provider"], "message": "Invalid option" }] }
          }
        }
      },
      "InternalError": {
        "description": "Data source unavailable",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "internal_error" }
          }
        }
      }
    },
    "schemas": {
      "Provider": {
        "type": "string",
        "enum": ["codex", "claude", "grok"],
        "description": "codex = OpenAI Codex, claude = Anthropic Claude Code, grok = xAI Grok"
      },
      "EventKind": {
        "type": "string",
        "enum": ["reset", "banked", "boost", "promo", "tease"],
        "description": "reset = account-wide reset; banked = banked reset card; boost = limit increase; promo = promotion; tease = reset teased but not yet effective"
      },
      "BoardStatus": {
        "type": "string",
        "enum": ["reset_now", "banked", "tease", "waiting", "overdue", "no_data"],
        "description": "reset_now = reset announced in the last 24 h; banked = latest signal is a banked reset card; tease = reset teased; waiting = normal; overdue = gap past 1.5x the recent median; no_data = nothing recorded"
      },
      "Event": {
        "type": "object",
        "required": ["id", "provider", "kind", "scope", "announcedAt", "effectiveAt", "sourceUrl", "sourceAccount", "summary", "audience", "verified"],
        "properties": {
          "id": { "type": "string", "description": "Post id; permanent page is https://didreset.com/{provider}#event-{id}" },
          "provider": { "$ref": "#/components/schemas/Provider" },
          "kind": { "$ref": "#/components/schemas/EventKind" },
          "scope": { "type": "string", "description": "global, or plan:{name}", "examples": ["global", "plan:pro"] },
          "announcedAt": { "type": "string", "format": "date-time" },
          "effectiveAt": { "type": ["string", "null"], "format": "date-time", "description": "Set only when the post names a later effective time" },
          "sourceUrl": { "type": "string", "format": "uri", "description": "Original public post; keep it when you republish" },
          "sourceAccount": { "type": "string", "examples": ["@thsottiaux"] },
          "summary": { "type": "string", "description": "Excerpt of the original post (up to 280 characters)" },
          "audience": { "type": "array", "items": { "type": "string" } },
          "verified": { "type": "boolean", "description": "Checked by hand before it flipped the board" }
        }
      },
      "ProviderStats": {
        "type": "object",
        "required": ["totalResets", "medianIntervalDays", "meanIntervalDays", "longestWaitDays", "hourHistogramUtc", "firstRecordedAt"],
        "properties": {
          "totalResets": { "type": "integer" },
          "medianIntervalDays": { "type": ["number", "null"], "description": "Median gap of the last 10 resets, in days" },
          "meanIntervalDays": { "type": ["number", "null"] },
          "longestWaitDays": { "type": ["number", "null"] },
          "hourHistogramUtc": { "type": "array", "minItems": 24, "maxItems": 24, "items": { "type": "integer" }, "description": "Reset announcements per UTC hour, index 0 = 00:00" },
          "firstRecordedAt": { "type": ["string", "null"], "format": "date-time" }
        }
      },
      "ProviderStatus": {
        "type": "object",
        "required": ["provider", "daysSince", "status", "lastReset", "latestSignal", "nextEstimate", "stats"],
        "properties": {
          "provider": { "$ref": "#/components/schemas/Provider" },
          "daysSince": { "type": ["number", "null"], "description": "Days since the last reset (fractional)" },
          "status": { "$ref": "#/components/schemas/BoardStatus" },
          "lastReset": { "oneOf": [{ "$ref": "#/components/schemas/Event" }, { "type": "null" }], "description": "Most recent kind=reset event" },
          "latestSignal": { "oneOf": [{ "$ref": "#/components/schemas/Event" }, { "type": "null" }], "description": "Most recent event of any kind" },
          "nextEstimate": { "type": ["string", "null"], "format": "date-time", "description": "Last reset + median gap of the last 10 resets. A cadence estimate, not a promise; a past value means any time now" },
          "stats": { "$ref": "#/components/schemas/ProviderStats" }
        }
      },
      "StatusResponse": {
        "type": "object",
        "required": ["updatedAt", "providers"],
        "properties": {
          "updatedAt": { "type": "string", "format": "date-time" },
          "providers": { "type": "array", "items": { "$ref": "#/components/schemas/ProviderStatus" } }
        }
      },
      "TimelineResponse": {
        "type": "object",
        "required": ["provider", "updatedAt", "events"],
        "properties": {
          "provider": { "type": "string", "enum": ["codex", "claude", "grok", "all"] },
          "updatedAt": { "type": "string", "format": "date-time" },
          "events": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } }
        }
      },
      "TimelineCsv": {
        "type": "string",
        "description": "RFC 4180 CSV. Header row: id,date,provider,kind,scope,summary,source_url,page_url. date is ISO 8601 UTC; page_url is the permanent DidReset anchor."
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "enum": ["invalid_query", "invalid_provider", "internal_error"] },
          "issues": { "type": "array", "items": { "type": "object" }, "description": "zod issues (400 only)" }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "type": ["integer", "string", "null"] },
          "method": { "type": "string", "examples": ["initialize", "tools/list", "tools/call"] },
          "params": { "type": "object" }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": ["jsonrpc"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "type": ["integer", "string", "null"] },
          "result": { "type": "object" },
          "error": {
            "type": "object",
            "properties": { "code": { "type": "integer" }, "message": { "type": "string" }, "data": {} }
          }
        }
      }
    }
  }
}
