{
  "openapi": "3.1.0",
  "info": {
    "title": "Streakline API",
    "version": "1.0.0",
    "description": "Hosted gamification API. Server routes use scoped OAuth M2M access tokens. Mobile routes use one five-minute user-bound Streakline client credential in the Authorization Bearer header."
  },
  "servers": [
    {
      "url": "https://api.qa.streakline.dev",
      "description": "Streakline QA dogfood"
    },
    {
      "url": "https://api.streakline.dev",
      "description": "Streakline Cloud"
    },
    {
      "url": "http://localhost:10000",
      "description": "Local development"
    }
  ],
  "tags": [
    {
      "name": "Client credentials",
      "description": "Short-lived mobile authorization"
    },
    {
      "name": "SDK",
      "description": "Model data consumed by client SDKs"
    },
    {
      "name": "Events",
      "description": "Track what users do"
    },
    {
      "name": "Users",
      "description": "Read gamification state"
    },
    {
      "name": "Store",
      "description": "Purchase released economy items"
    }
  ],
  "paths": {
    "/api/v1/client-credentials": {
      "post": {
        "operationId": "createClientCredential",
        "tags": [
          "Client credentials"
        ],
        "summary": "Create a client credential",
        "description": "High-privilege, server-only route. Requires OAuth M2M scope client_credentials:issue. Use a separate least-privilege BFF client where possible. The customer's BFF must derive user_id from its authenticated session and entitlement checks; Streakline cannot authenticate that caller or accept a client-supplied user ID safely.",
        "security": [
          {
            "OAuthM2MAuth": [
              "client_credentials:issue"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateClientCredentialRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Client credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientCredentialResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer credential"
          },
          "403": {
            "description": "Missing client_credentials:issue scope"
          },
          "409": {
            "description": "User was deleted"
          }
        }
      }
    },
    "/api/v1/sdk/model": {
      "get": {
        "operationId": "getScoringModel",
        "tags": [
          "SDK"
        ],
        "summary": "Get the SDK scoring model",
        "description": "Client-credential route. Client SDKs cache this model and pass current_version to receive 304 when unchanged.",
        "security": [
          {
            "ClientCredentialAuth": []
          }
        ],
        "parameters": [
          {
            "name": "current_version",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Scoring model",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScoringModel"
                }
              }
            }
          },
          "304": {
            "description": "Model unchanged"
          },
          "401": {
            "description": "Missing or invalid Bearer credential"
          }
        }
      }
    },
    "/api/v1/events/batch": {
      "post": {
        "operationId": "trackClientEventsBatch",
        "tags": [
          "Events"
        ],
        "summary": "Batch mobile events",
        "description": "Mobile route. Requires a five-minute user-bound client credential in the Authorization Bearer header with events:write scope. The request body is limited to 256 KiB; every item's metadata is limited to 16 KiB serialized JSON, eight nesting levels, and 100 object keys. Each item is scored synchronously and accepted or rejected independently.",
        "security": [
          {
            "ClientCredentialAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchTrackRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch processed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchTrackResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer credential"
          },
          "403": {
            "description": "Missing events:write scope"
          },
          "413": {
            "description": "Request body exceeds 256 KiB"
          }
        }
      }
    },
    "/api/v1/events/batch/server": {
      "post": {
        "operationId": "trackServerEventsBatch",
        "tags": [
          "Events"
        ],
        "summary": "Batch trusted server events",
        "description": "Server-only synchronous route. Requires OAuth M2M scope events:write and returns one authoritative result per event. Request bodies are limited to 256 KiB; metadata is limited to 16 KiB serialized JSON, eight levels of nesting, and 100 object keys. Malformed occurred_at values and idempotency conflicts are rejected per item without committing.",
        "security": [
          {
            "OAuthM2MAuth": [
              "events:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServerBatchTrackRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch processed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchTrackResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer credential"
          },
          "403": {
            "description": "Missing events:write scope"
          },
          "413": {
            "description": "Request body exceeds 256 KiB"
          }
        }
      }
    },
    "/api/v1/events": {
      "post": {
        "operationId": "trackServerEvent",
        "tags": [
          "Events"
        ],
        "summary": "Track a trusted server event",
        "description": "Server-only synchronous route. Requires OAuth M2M scope events:write and a stable idempotency key. Bodies are limited to 256 KiB; metadata is limited to 16 KiB serialized JSON, eight levels of nesting, and 100 object keys. A successful response means the event and authoritative score/profile update committed atomically.",
        "security": [
          {
            "OAuthM2MAuth": [
              "events:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TrackEventRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event processed or returned as an idempotent duplicate",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackEventResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid environment, event type, or occurred_at",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackEventRejection"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer credential"
          },
          "403": {
            "description": "Missing OAuth M2M scope events:write"
          },
          "409": {
            "description": "Idempotency key was already used for a conflicting event",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackEventRejection"
                }
              }
            }
          },
          "410": {
            "description": "User was deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackEventRejection"
                }
              }
            }
          },
          "413": {
            "description": "Request body exceeds 256 KiB"
          },
          "429": {
            "description": "Cooldown or daily cap rejected the event",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackEventRejection"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me/profile": {
      "get": {
        "operationId": "getCurrentUserProfile",
        "tags": [
          "Users"
        ],
        "summary": "Get the current mobile user's profile",
        "description": "Mobile route. Requires a five-minute user-bound client credential in the Authorization Bearer header with profile:read scope. Returns 410 when that subject has been erased.",
        "security": [
          {
            "ClientCredentialAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserProfile"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer credential"
          },
          "403": {
            "description": "Missing profile:read scope"
          },
          "410": {
            "description": "User was deleted"
          }
        }
      }
    },
    "/api/v1/users/{userId}/profile": {
      "get": {
        "operationId": "getUserProfile",
        "tags": [
          "Users"
        ],
        "summary": "Get a user's profile server-side",
        "description": "Server-only route. Requires OAuth M2M scope profiles:read.",
        "security": [
          {
            "OAuthM2MAuth": [
              "profiles:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/userId"
          }
        ],
        "responses": {
          "200": {
            "description": "Profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserProfile"
                }
              }
            }
          },
          "403": {
            "description": "Missing OAuth M2M scope profiles:read"
          }
        }
      },
      "put": {
        "operationId": "importUserProfile",
        "tags": [
          "Users"
        ],
        "summary": "Import an existing user profile",
        "description": "Server-only idempotent import. The same idempotency_key can be retried without overwriting later progress. Request bodies are limited to 32 KiB; counters and keyed-map values are non-negative signed 32-bit integers.",
        "security": [
          {
            "OAuthM2MAuth": [
              "profiles:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/userId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportUserProfileRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Prior import retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserProfile"
                }
              }
            }
          },
          "201": {
            "description": "Profile imported",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserProfile"
                }
              }
            }
          },
          "409": {
            "description": "A different profile or erasure tombstone already exists"
          },
          "413": {
            "description": "Request body exceeds 32 KiB"
          }
        }
      }
    },
    "/api/v1/users/{userId}": {
      "delete": {
        "operationId": "deleteUser",
        "tags": [
          "Users"
        ],
        "summary": "Erase a user",
        "description": "Deletes project-scoped user data and creates a one-way tombstone that prevents delayed events from recreating the subject.",
        "security": [
          {
            "OAuthM2MAuth": [
              "profiles:delete"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/userId"
          }
        ],
        "responses": {
          "204": {
            "description": "User erased"
          }
        }
      }
    },
    "/api/v1/users/{userId}/events": {
      "get": {
        "operationId": "listUserEvents",
        "tags": [
          "Users"
        ],
        "summary": "List authoritative scored events",
        "description": "Server-only route. Requires OAuth M2M scope events:read. Results use stable keyset pagination ordered by occurred_at and event_id descending.",
        "security": [
          {
            "OAuthM2MAuth": [
              "events:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/userId"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor returned by the previous page.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event history including occurred_at and local_date",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserEventHistory"
                }
              }
            }
          },
          "400": {
            "description": "Invalid limit or cursor"
          }
        }
      }
    },
    "/api/v1/users/{userId}/achievements": {
      "get": {
        "operationId": "listUserAchievements",
        "tags": [
          "Users"
        ],
        "summary": "List unlocked achievements server-side",
        "description": "Server-only route. Requires OAuth M2M scope profiles:read.",
        "security": [
          {
            "OAuthM2MAuth": [
              "profiles:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/userId"
          }
        ],
        "responses": {
          "200": {
            "description": "Achievements",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "achievements"
                  ],
                  "properties": {
                    "achievements": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Achievement"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing OAuth M2M scope profiles:read"
          }
        }
      }
    },
    "/api/v1/store/purchases": {
      "post": {
        "operationId": "purchaseStoreItem",
        "tags": [
          "Store"
        ],
        "summary": "Purchase a released store item",
        "description": "Atomically spends the user named balance from the active released configuration and fulfills the item plus its optional grant. Idempotency keys are scoped to the bound environment and user.",
        "security": [
          {
            "OAuthM2MAuth": [
              "store:purchase"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PurchaseStoreItemRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay of the completed purchase",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseStoreItemResponse"
                }
              }
            }
          },
          "201": {
            "description": "Purchase completed atomically",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseStoreItemResponse"
                }
              }
            }
          },
          "404": {
            "description": "User or released store item not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseStoreItemError"
                }
              }
            }
          },
          "409": {
            "description": "Insufficient balance or conflicting idempotency key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseStoreItemError"
                }
              }
            }
          },
          "410": {
            "description": "User was erased",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseStoreItemError"
                }
              }
            }
          },
          "422": {
            "description": "Released store or balance configuration is unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseStoreItemError"
                }
              }
            }
          },
          "503": {
            "description": "Active release provenance could not be resolved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseStoreItemError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ClientCredentialAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Five-minute user-bound Streakline client credential."
      },
      "OAuthM2MAuth": {
        "type": "oauth2",
        "description": "Workspace/project/environment-bound OAuth 2.0 client credentials.",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "/api/auth/oauth2/token",
            "scopes": {
              "events:write": "Submit trusted server events",
              "events:read": "Read trusted event data",
              "profiles:read": "Read user profiles",
              "profiles:write": "Import or update user profiles",
              "profiles:delete": "Erase user profiles",
              "client_credentials:issue": "Mint five-minute user-bound client credentials",
              "store:purchase": "Purchase released store items for users"
            }
          }
        }
      }
    },
    "parameters": {
      "userId": {
        "name": "userId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Your app's user ID"
      }
    },
    "schemas": {
      "CreateClientCredentialRequest": {
        "type": "object",
        "required": [
          "user_id"
        ],
        "properties": {
          "user_id": {
            "type": "string",
            "example": "user_123"
          }
        }
      },
      "ClientCredentialResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "project_id",
          "environment_id",
          "environment",
          "user_id",
          "scopes",
          "expires_at"
        ],
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "example": "streakline_client_credential"
          },
          "project_id": {
            "type": "string"
          },
          "environment_id": {
            "type": "string"
          },
          "environment": {
            "type": "string",
            "example": "qa"
          },
          "user_id": {
            "type": "string",
            "example": "user_123"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ScoringModel": {
        "type": "object",
        "required": [
          "schema_version",
          "model_version",
          "min_sdk_version",
          "published_at",
          "fetched_at",
          "expires_at",
          "events",
          "level_curve",
          "bonuses",
          "scoring_behavior",
          "score_presentation",
          "enabled_primitives",
          "streak_definitions"
        ],
        "properties": {
          "schema_version": {
            "type": "integer",
            "example": 2
          },
          "model_version": {
            "type": "string",
            "example": "v8"
          },
          "min_sdk_version": {
            "type": "string",
            "example": "0.1.0"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "fetched_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "events": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ScoringModelEvent"
            }
          },
          "level_curve": {
            "$ref": "#/components/schemas/LevelCurve"
          },
          "bonuses": {
            "$ref": "#/components/schemas/ScoringBonuses"
          },
          "scoring_behavior": {
            "type": "string",
            "enum": [
              "standard",
              "streak_before_score"
            ]
          },
          "score_presentation": {
            "$ref": "#/components/schemas/ScorePresentation"
          },
          "enabled_primitives": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "streak_definitions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StreakDefinition"
            }
          }
        }
      },
      "StreakRequirement": {
        "type": "object",
        "required": [
          "event_types",
          "minimum_count"
        ],
        "properties": {
          "event_types": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            }
          },
          "minimum_count": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "StreakDefinition": {
        "type": "object",
        "required": [
          "key",
          "name",
          "period",
          "requirements",
          "milestones"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "period": {
            "type": "string",
            "enum": [
              "daily",
              "weekly",
              "monthly"
            ]
          },
          "requirements": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/StreakRequirement"
            }
          },
          "milestones": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 1
            }
          }
        }
      },
      "ScorePresentation": {
        "type": "object",
        "required": [
          "singular",
          "plural",
          "abbreviation"
        ],
        "properties": {
          "singular": {
            "type": "string",
            "example": "point"
          },
          "plural": {
            "type": "string",
            "example": "points"
          },
          "abbreviation": {
            "type": [
              "string",
              "null"
            ],
            "example": "XP"
          }
        }
      },
      "ScoringModelEvent": {
        "type": "object",
        "required": [
          "event_type",
          "score",
          "category",
          "label",
          "qualifies_for_streak",
          "cooldown_seconds",
          "daily_cap"
        ],
        "properties": {
          "event_type": {
            "type": "string",
            "example": "lesson.completed"
          },
          "score": {
            "type": "integer",
            "example": 10
          },
          "category": {
            "type": "string",
            "example": "learning"
          },
          "label": {
            "type": "string",
            "example": "Lesson completed"
          },
          "qualifies_for_streak": {
            "type": "boolean",
            "example": true
          },
          "cooldown_seconds": {
            "type": "integer",
            "example": 0
          },
          "daily_cap": {
            "type": [
              "integer",
              "null"
            ],
            "example": 10,
            "description": "Maximum scored occurrences per user per day. Null means unlimited; zero blocks all occurrences."
          }
        }
      },
      "LevelCurve": {
        "type": "object",
        "required": [
          "base_score",
          "exponent",
          "offset",
          "rounding"
        ],
        "properties": {
          "base_score": {
            "type": "integer",
            "example": 100
          },
          "exponent": {
            "type": "number",
            "example": 1.5
          },
          "offset": {
            "type": "integer",
            "example": 0
          },
          "rounding": {
            "type": "string",
            "enum": [
              "floor",
              "round"
            ]
          }
        }
      },
      "ScoringBonuses": {
        "type": "object",
        "required": [
          "first_of_day_pct",
          "streak_pct_per_day",
          "max_streak_pct",
          "challenge_pct",
          "combo_pct",
          "daily_score_cap",
          "streak_starts_at_day"
        ],
        "properties": {
          "first_of_day_pct": {
            "type": "number",
            "example": 0.25
          },
          "streak_pct_per_day": {
            "type": "number",
            "example": 0.1
          },
          "max_streak_pct": {
            "type": "number",
            "example": 0.5
          },
          "challenge_pct": {
            "type": "number",
            "example": 0.5
          },
          "combo_pct": {
            "type": "number",
            "example": 0.15
          },
          "daily_score_cap": {
            "type": [
              "integer",
              "null"
            ],
            "example": 500,
            "description": "Maximum score per user per day. Null means unlimited; zero awards no score."
          },
          "streak_starts_at_day": {
            "type": "integer",
            "example": 2
          }
        }
      },
      "BatchTrackRequest": {
        "type": "object",
        "required": [
          "events"
        ],
        "properties": {
          "events": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/BatchTrackEvent"
            }
          }
        }
      },
      "BatchTrackEvent": {
        "type": "object",
        "required": [
          "event_type",
          "idempotency_key"
        ],
        "properties": {
          "event_type": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "example": "lesson.completed"
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 8,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "example": "evt_8f2c"
          },
          "source": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "default": "client",
            "example": "client"
          },
          "metadata": {
            "type": "object",
            "description": "Maximum 16 KiB serialized JSON, eight nesting levels, and 100 object keys.",
            "maxProperties": 100,
            "additionalProperties": true
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "maxLength": 64
          },
          "local_date": {
            "type": "string",
            "format": "date"
          },
          "model_version": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "example": "v8"
          }
        }
      },
      "ServerBatchTrackRequest": {
        "type": "object",
        "required": [
          "events"
        ],
        "properties": {
          "events": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/ServerBatchTrackEvent"
            }
          }
        }
      },
      "ServerBatchTrackEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BatchTrackEvent"
          },
          {
            "type": "object",
            "required": [
              "user_id"
            ],
            "properties": {
              "user_id": {
                "type": "string"
              }
            }
          }
        ]
      },
      "BatchTrackResponse": {
        "type": "object",
        "required": [
          "accepted",
          "rejected",
          "results"
        ],
        "properties": {
          "accepted": {
            "type": "integer",
            "example": 1
          },
          "rejected": {
            "type": "integer",
            "example": 0
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchTrackResult"
            }
          }
        }
      },
      "BatchTrackResult": {
        "type": "object",
        "required": [
          "user_id",
          "event_type",
          "accepted"
        ],
        "properties": {
          "user_id": {
            "type": "string"
          },
          "event_type": {
            "type": "string",
            "example": "lesson.completed"
          },
          "accepted": {
            "type": "boolean",
            "example": true
          },
          "duplicate": {
            "type": "boolean"
          },
          "code": {
            "type": "string",
            "enum": [
              "unknown_event",
              "idempotency_conflict",
              "cooldown_active",
              "daily_cap_reached",
              "user_deleted",
              "invalid_occurred_at",
              "configuration_unavailable",
              "invalid_environment"
            ]
          },
          "event_id": {
            "type": "string"
          },
          "score_added": {
            "type": "integer"
          },
          "total_score": {
            "type": "integer"
          },
          "current_level": {
            "type": "integer"
          },
          "error": {
            "type": "string"
          }
        }
      },
      "TrackEventRequest": {
        "type": "object",
        "required": [
          "user_id",
          "event_type",
          "idempotency_key"
        ],
        "properties": {
          "user_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "example": "user_123"
          },
          "event_type": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "example": "lesson.completed"
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 8,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "example": "evt_8f2c"
          },
          "source": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "default": "server",
            "example": "server"
          },
          "metadata": {
            "type": "object",
            "description": "Maximum 16 KiB serialized JSON, eight nesting levels, and 100 object keys.",
            "maxProperties": 100,
            "additionalProperties": true
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "maxLength": 64,
            "description": "RFC 3339 timestamp (maximum 64 characters). Invalid values return invalid_occurred_at."
          },
          "local_date": {
            "type": "string",
            "format": "date"
          },
          "model_version": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          }
        }
      },
      "TrackEventResponse": {
        "type": "object",
        "required": [
          "accepted",
          "event_id",
          "user_id",
          "event_type",
          "score_added",
          "total_score",
          "current_level",
          "current_streak",
          "leveled_up",
          "streak_milestone"
        ],
        "properties": {
          "accepted": {
            "type": "boolean",
            "const": true
          },
          "duplicate": {
            "type": "boolean"
          },
          "event_id": {
            "type": "string"
          },
          "user_id": {
            "type": "string",
            "example": "user_123"
          },
          "event_type": {
            "type": "string",
            "example": "lesson.completed"
          },
          "score_added": {
            "type": "number",
            "example": 5
          },
          "total_score": {
            "type": "number",
            "example": 42
          },
          "current_level": {
            "type": "integer",
            "example": 2
          },
          "current_streak": {
            "type": "integer",
            "example": 3
          },
          "leveled_up": {
            "type": "boolean"
          },
          "streak_milestone": {
            "type": [
              "integer",
              "null"
            ]
          },
          "streaks": {
            "type": "object",
            "description": "Configured streak state keyed by the customer-owned streak definition key in the active environment configuration.",
            "additionalProperties": {
              "$ref": "#/components/schemas/ConfiguredStreak"
            }
          },
          "streak_milestones": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "key",
                "milestone"
              ],
              "properties": {
                "key": {
                  "type": "string"
                },
                "milestone": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          }
        }
      },
      "ConfiguredStreak": {
        "type": "object",
        "required": [
          "current_streak",
          "longest_streak",
          "last_qualified_period"
        ],
        "properties": {
          "current_streak": {
            "type": "integer",
            "minimum": 0
          },
          "longest_streak": {
            "type": "integer",
            "minimum": 0
          },
          "last_qualified_period": {
            "type": [
              "string",
              "null"
            ],
            "description": "Canonical period key: YYYY-MM-DD for daily streaks, Monday as YYYY-MM-DD for weekly streaks, or YYYY-MM for monthly streaks."
          }
        }
      },
      "TrackEventRejection": {
        "type": "object",
        "required": [
          "accepted",
          "user_id",
          "event_type",
          "code",
          "error"
        ],
        "properties": {
          "accepted": {
            "type": "boolean",
            "const": false
          },
          "user_id": {
            "type": "string",
            "example": "user_123"
          },
          "event_type": {
            "type": "string",
            "example": "lesson.completed"
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_environment",
              "unknown_event",
              "idempotency_conflict",
              "cooldown_active",
              "daily_cap_reached",
              "user_deleted",
              "invalid_occurred_at",
              "configuration_unavailable"
            ]
          },
          "error": {
            "type": "string"
          },
          "event_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "UserProfile": {
        "type": "object",
        "required": [
          "user_id",
          "total_score",
          "current_level",
          "score_to_next_level",
          "badge_scores",
          "current_streak",
          "longest_streak",
          "last_active_date",
          "daily_score_today",
          "daily_score_date",
          "streak_bonus_carry_pct",
          "streak_bonus_carry_expires_on",
          "streak_freeze_tokens",
          "prestige_count",
          "balances",
          "achievements_completed",
          "achievements_total",
          "scoring_version_id"
        ],
        "properties": {
          "user_id": {
            "type": "string",
            "example": "user_123"
          },
          "total_score": {
            "type": "integer",
            "example": 642
          },
          "current_level": {
            "type": "integer",
            "example": 4
          },
          "score_to_next_level": {
            "type": "integer",
            "example": 358
          },
          "badge_scores": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "current_streak": {
            "type": "integer",
            "example": 9
          },
          "longest_streak": {
            "type": "integer",
            "example": 12
          },
          "streaks": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ConfiguredStreak"
            }
          },
          "last_active_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "daily_score_today": {
            "type": "integer"
          },
          "daily_score_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "streak_bonus_carry_pct": {
            "type": "number"
          },
          "streak_bonus_carry_expires_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "streak_freeze_tokens": {
            "type": "integer",
            "example": 2
          },
          "prestige_count": {
            "type": "integer",
            "example": 0
          },
          "achievements_completed": {
            "type": "integer",
            "example": 0
          },
          "achievements_total": {
            "type": "integer",
            "example": 0
          },
          "scoring_version_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "balances": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        }
      },
      "ImportUserProfileRequest": {
        "type": "object",
        "required": [
          "idempotency_key",
          "total_score"
        ],
        "properties": {
          "idempotency_key": {
            "type": "string"
          },
          "total_score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647
          },
          "badge_scores": {
            "type": "object",
            "maxProperties": 100,
            "additionalProperties": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2147483647
            }
          },
          "daily_score_today": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647
          },
          "daily_score_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "current_streak": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647
          },
          "longest_streak": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647
          },
          "streaks": {
            "type": "object",
            "maxProperties": 100,
            "additionalProperties": {
              "$ref": "#/components/schemas/ConfiguredStreak"
            }
          },
          "last_active_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "streak_freeze_tokens": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3
          },
          "streak_bonus_carry_pct": {
            "type": "number",
            "minimum": 0,
            "maximum": 0.5
          },
          "streak_bonus_carry_expires_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          }
        }
      },
      "Achievement": {
        "type": "object",
        "required": [
          "id",
          "progress",
          "completed",
          "completed_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "first_week"
          },
          "progress": {
            "type": "integer",
            "example": 7
          },
          "completed": {
            "type": "boolean",
            "example": true
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "UserEvent": {
        "type": "object",
        "required": [
          "event_id",
          "event_type",
          "rejection_code",
          "accepted",
          "badge",
          "multiplier",
          "score",
          "metadata",
          "bonus_breakdown",
          "was_daily_capped",
          "occurred_at",
          "local_date",
          "created_at"
        ],
        "properties": {
          "event_id": {
            "type": "string",
            "format": "uuid"
          },
          "event_type": {
            "type": "string"
          },
          "badge": {
            "type": [
              "string",
              "null"
            ]
          },
          "multiplier": {
            "type": [
              "integer",
              "null"
            ]
          },
          "score": {
            "type": "integer"
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "bonus_breakdown": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "number"
            }
          },
          "was_daily_capped": {
            "type": "boolean"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "local_date": {
            "type": "string",
            "format": "date"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "accepted": {
            "type": "boolean"
          },
          "rejection_code": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "unknown_event",
              "cooldown_active",
              "daily_cap_reached",
              null
            ]
          }
        }
      },
      "UserEventHistory": {
        "type": "object",
        "required": [
          "events",
          "cursor"
        ],
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UserEvent"
            }
          },
          "cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PurchaseStoreItemRequest": {
        "type": "object",
        "required": [
          "user_id",
          "store_item_key",
          "idempotency_key"
        ],
        "properties": {
          "user_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "store_item_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          }
        }
      },
      "PurchaseStoreItem": {
        "type": "object",
        "required": [
          "key",
          "name"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "PurchaseStorePayment": {
        "type": "object",
        "required": [
          "balance_key",
          "cost",
          "balance_after"
        ],
        "properties": {
          "balance_key": {
            "type": "string"
          },
          "cost": {
            "type": "integer",
            "minimum": 0
          },
          "balance_after": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "PurchaseStoreGrant": {
        "type": "object",
        "required": [
          "key",
          "name",
          "type",
          "amount"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "score",
              "currency",
              "item",
              "unlock"
            ]
          },
          "amount": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "PurchaseStoreItemResponse": {
        "type": "object",
        "required": [
          "purchase_id",
          "user_id",
          "item",
          "paid_with",
          "grant",
          "purchased_at",
          "configuration_version_id",
          "environment_deployment_id"
        ],
        "properties": {
          "purchase_id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": "string"
          },
          "item": {
            "$ref": "#/components/schemas/PurchaseStoreItem"
          },
          "paid_with": {
            "$ref": "#/components/schemas/PurchaseStorePayment"
          },
          "grant": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PurchaseStoreGrant"
              },
              {
                "type": "null"
              }
            ]
          },
          "purchased_at": {
            "type": "string",
            "format": "date-time"
          },
          "configuration_version_id": {
            "type": "string",
            "format": "uuid"
          },
          "environment_deployment_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "PurchaseStoreItemError": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "user_deleted",
              "user_not_found",
              "store_unavailable",
              "item_not_found",
              "balance_not_found",
              "insufficient_balance",
              "idempotency_conflict",
              "configuration_unavailable"
            ]
          }
        }
      }
    }
  }
}
