{
  "openapi": "3.0.3",
  "info": {
    "title": "Tickr.cc API",
    "version": "1.0.0",
    "description": "Persistent counters with a URL. Create a counter, increment it from any system over HTTP, and watch it live at https://tickr.cc/c/{slug}.",
    "contact": { "url": "https://github.com/DavidMChan/tickr/issues" },
    "license": { "name": "MIT" }
  },
  "servers": [{ "url": "https://tickr.cc" }],
  "security": [{ "ApiKeyAuth": [] }],
  "tags": [{ "name": "counters" }],
  "paths": {
    "/api/counters": {
      "get": {
        "tags": ["counters"],
        "summary": "List your counters",
        "operationId": "listCounters",
        "responses": {
          "200": {
            "description": "Counters owned by the authenticated user, newest first.",
            "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Counter" } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["counters"],
        "summary": "Create a counter",
        "operationId": "createCounter",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name": { "type": "string", "example": "API calls" },
                  "initial_value": { "type": "integer", "default": 0 },
                  "is_readonly": { "type": "boolean", "default": false, "description": "When true, the share page shows no increment button. The API still accepts increments." },
                  "is_private": { "type": "boolean", "default": false, "description": "When true, only the owner and authorized users can increment the counter." }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Counter" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "description": "Counter limit (100 per account) reached." }
        }
      }
    },
    "/api/counters/{slug}": {
      "parameters": [{ "$ref": "#/components/parameters/Slug" }],
      "get": {
        "tags": ["counters"],
        "summary": "Read a counter",
        "operationId": "getCounter",
        "security": [],
        "responses": {
          "200": { "description": "The counter (without the owner's user_id).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PublicCounter" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "put": {
        "tags": ["counters"],
        "summary": "Update a counter",
        "description": "Owner only. Provide at least one field.",
        "operationId": "updateCounter",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string" },
                  "current_value": { "type": "integer" },
                  "is_readonly": { "type": "boolean" },
                  "is_private": { "type": "boolean" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Counter" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "tags": ["counters"],
        "summary": "Delete a counter",
        "description": "Owner only.",
        "operationId": "deleteCounter",
        "responses": {
          "200": { "description": "Deleted" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/counters/{slug}/increment": {
      "parameters": [{ "$ref": "#/components/parameters/Slug" }],
      "post": {
        "tags": ["counters"],
        "summary": "Increment a counter",
        "description": "Atomically adds increment_by to the counter. Public counters (including read-only ones) accept anonymous requests of 1 to 100, rate limited per IP. Private counters, negative or larger deltas, and higher rate limits require the owner's API key or an authorized user.",
        "operationId": "incrementCounter",
        "security": [{}, { "ApiKeyAuth": [] }],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "increment_by": { "type": "integer", "default": 1, "description": "Positive or negative integer to add." } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "The updated counter.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Counter" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "description": "Authenticated but not permitted to increment this counter." },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "description": "Rate limited. Check the Retry-After header." }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "http", "scheme": "bearer", "description": "API key from https://tickr.cc/api-docs, sent as `Authorization: Bearer <key>`." }
    },
    "parameters": {
      "Slug": { "name": "slug", "in": "path", "required": true, "schema": { "type": "string" }, "description": "The counter's unique_url_slug, e.g. Xk3f9aB2qz." }
    },
    "responses": {
      "BadRequest": { "description": "Invalid request body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing or invalid API key / session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "No counter with that slug (or not yours).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": { "type": "object", "properties": { "error": { "type": "string" } }, "required": ["error"] },
      "PublicCounter": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "current_value": { "type": "integer", "format": "int64" },
          "unique_url_slug": { "type": "string" },
          "is_readonly": { "type": "boolean" },
          "is_private": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        },
        "required": ["id", "name", "current_value", "unique_url_slug", "created_at", "updated_at"]
      },
      "Counter": {
        "allOf": [
          { "$ref": "#/components/schemas/PublicCounter" },
          { "type": "object", "properties": { "user_id": { "type": "string", "format": "uuid" } } }
        ]
      }
    }
  }
}
