{
  "openapi": "3.1.0",
  "info": {
    "title": "Gamification API",
    "version": "1.0.0",
    "description": "Server-to-server API for merchants (integration level 3). Authenticate with an API key from Settings → API & Webhooks: `Authorization: Bearer gk_live_...`. All amounts are in Toman. Rate limit: 120 requests/minute per key. Errors use `{ \"error\": { \"code\", \"message\" } }`."
  },
  "servers": [
    {
      "url": "/api/v1"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "gk_live_... key (or send it in the X-Api-Key header)"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Prize": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "discount_code",
              "physical",
              "digital",
              "cashback",
              "nothing"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Campaign": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "game_type": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "live",
              "inactive",
              "draft"
            ]
          },
          "play_url": {
            "type": "string",
            "format": "uri"
          },
          "starts_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "max_plays_per_user": {
            "type": "integer"
          },
          "prizes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "title": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "remaining": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "null = unlimited"
                }
              }
            }
          }
        }
      },
      "Claim": {
        "type": "object",
        "properties": {
          "claim_code": {
            "type": "string",
            "example": "WIN-AB12CD34EF"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "claimed",
              "expired",
              "cancelled"
            ]
          },
          "player_identifier": {
            "type": "string"
          },
          "player_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "discount_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "claimed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "prize": {
            "$ref": "#/components/schemas/Prize"
          },
          "campaign": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "slug": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/campaigns": {
      "get": {
        "summary": "List your campaigns",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Campaign"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Plan without API access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/campaigns/{slug}": {
      "get": {
        "summary": "Get one campaign",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/campaigns/{slug}/plays": {
      "post": {
        "summary": "Play on behalf of a customer (server-decided outcome)",
        "description": "Typical use: give each customer one spin after checkout. The same limits apply as for player-facing plays (per player, monthly plan cap, stock). Forward the customer's IP as player_ip to apply per-IP limits. Skill games (e.g. target_tap) need the player in the loop and return 422 SKILL_GAME_API here.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "player_identifier"
                ],
                "properties": {
                  "player_identifier": {
                    "type": "string",
                    "description": "Stable id for the customer, e.g. mobile number or your customer id"
                  },
                  "player_name": {
                    "type": "string"
                  },
                  "player_ip": {
                    "type": "string",
                    "format": "ip"
                  },
                  "win_multiplier": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 3,
                    "default": 1,
                    "description": "Personalized odds for this player, e.g. from purchase history. Multiplies the chance of every real prize (0 = cannot win, 1 = normal, 3 = triple). If the boosted total passes 100% it is scaled back to 100%."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Played",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "play_id": {
                          "type": "integer"
                        },
                        "result": {
                          "type": "string",
                          "enum": [
                            "win",
                            "lose"
                          ]
                        },
                        "outcome_token": {
                          "type": "string",
                          "description": "Signed result; verifiable via /api/public/verify-outcome"
                        },
                        "prize": {
                          "oneOf": [
                            {
                              "$ref": "#/components/schemas/Prize"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "claim": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "claim_code": {
                              "type": "string"
                            },
                            "discount_code": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "expires_at": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "status": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Campaign unavailable or monthly cap reached (PLAY_LIMIT_REACHED, CAMPAIGN_INACTIVE, …)"
          },
          "429": {
            "description": "Player limit reached (PLAYER_LIMIT_REACHED, TOO_MANY_PLAYS)"
          }
        }
      }
    },
    "/claims/{code}": {
      "get": {
        "summary": "Look up a prize code",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Claim"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/claims/{code}/redeem": {
      "post": {
        "summary": "Mark a prize as delivered / applied",
        "description": "Atomic: a code can be redeemed exactly once. Call it when the discount is applied to an order.",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Redeemed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Claim"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Already redeemed (ALREADY_CLAIMED)"
          },
          "422": {
            "description": "Expired (CLAIM_EXPIRED)"
          }
        }
      }
    },
    "/winners": {
      "get": {
        "summary": "List winners (for syncing into your CRM)",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "claimed",
                "expired",
                "cancelled"
              ]
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 100
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "oldest",
                "newest"
              ],
              "default": "oldest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "The account this API key belongs to (plan, embed permission, remaining plays)",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/stats": {
      "get": {
        "summary": "Totals for the last N days",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 365
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/webhook": {
      "put": {
        "summary": "Point webhooks at a URL; returns a new signing secret (shown once)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "summary": "Stop webhook deliveries",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "webhooks": {
    "play.won": {
      "post": {
        "summary": "A customer won a prize",
        "description": "Headers: X-Webhook-Event, X-Webhook-Id, X-Webhook-Timestamp, X-Webhook-Signature = hex(HMAC-SHA256(secret, \"{timestamp}.{raw body}\")). Reject timestamps older than 5 minutes. Respond 2xx; failures are retried after 10s, 1m, 5m and 30m.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "event": {
                    "type": "string"
                  },
                  "created_at": {
                    "type": "string"
                  },
                  "data": {
                    "$ref": "#/components/schemas/Claim"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged"
          }
        }
      }
    },
    "claim.redeemed": {
      "post": {
        "summary": "A prize was marked as delivered",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "event": {
                    "type": "string"
                  },
                  "created_at": {
                    "type": "string"
                  },
                  "data": {
                    "$ref": "#/components/schemas/Claim"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged"
          }
        }
      }
    }
  }
}