{
 "openapi": "3.1.0",
 "info": {
  "title": "SantiAuth Public API",
  "version": "1.0.0"
 },
 "servers": [
  {
   "url": "https://api.santiauth.com"
  }
 ],
 "paths": {
  "/api/analytics/stats": {
   "get": {
    "tags": [
     "Analytics"
    ],
    "summary": "Get analytics overview",
    "description": "Returns the main analytics dashboard payload for the authenticated user. Use this endpoint to power overview screens that need totals, time-series snapshots, executor activity, country and referrer breakdowns, plus service, provider, and revenue performance for a selected preset range.",
    "parameters": [
     {
      "schema": {
       "type": "string",
       "enum": [
        "7",
        "14",
        "30",
        "90",
        "365"
       ],
       "default": "30",
       "description": "Time range in days for analytics data. Options: 7, 14, 30, 90, 365 — capped by the plan (403 LIMIT_EXCEEDED `analytics_range`).",
       "example": "30"
      },
      "required": false,
      "description": "Time range in days for analytics data. Options: 7, 14, 30, 90, 365 — capped by the plan (403 LIMIT_EXCEEDED `analytics_range`).",
      "name": "range",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Analytics overview returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/AnalyticsStatsResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": {
          "totals": {
           "total_keys": 2500,
           "total_generated_keys": 2400,
           "total_used_keys": 1820,
           "expired_keys": 115,
           "total_flows": 6400,
           "active_revenues": 8,
           "total_script_executions": 0,
           "total_active_scripts": 0
          },
          "byExecutor": [
           {
            "date": "2026-04-30",
            "executor": "cloudflare-turnstile",
            "count": 120
           }
          ],
          "byCountry": [
           {
            "country": "US",
            "count": 840
           }
          ],
          "byReferrer": [
           {
            "referrer": "discord.gg",
            "count": 220
           }
          ],
          "byService": [
           {
            "service_id": "660e8400-e29b-41d4-a716-446655440001",
            "service_name": "Premium Access",
            "flow_starts": 510,
            "checkpoints_passed": 301
           }
          ],
          "byProvider": [
           {
            "provider_id": "770e8400-e29b-41d4-a716-446655440002",
            "provider_name": "Discord Verification",
            "flow_starts": 410,
            "checkpoints_passed": 245
           }
          ],
          "byRevenue": [
           {
            "revenue_id": "880e8400-e29b-41d4-a716-446655440003",
            "revenue_name": "Discord OAuth",
            "checkpoints_passed": 188
           }
          ],
          "timeSeries": [
           {
            "date": "2026-04-30",
            "clicks": 342,
            "checkpoints": 198,
            "keys": 0,
            "keys_generated": 75,
            "keys_used": 56,
            "script_executions": 0
           }
          ],
          "_cached": false,
          "_generatedAt": "2026-04-30T15:38:25.123Z"
         }
        }
       }
      }
     }
    },
    "x-scope": "analytics:read"
   }
  },
  "/api/analytics/time-series": {
   "get": {
    "tags": [
     "Analytics"
    ],
    "summary": "Get time-series analytics",
    "description": "Returns trend data for the authenticated user over a custom time window, optionally filtered to a single provider. Choose hourly granularity for short-term monitoring and daily granularity for reporting, dashboards, or longer-range performance reviews.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Optional provider UUID to filter time-series analytics",
       "example": "550e8400-e29b-41d4-a716-446655440000"
      },
      "required": false,
      "description": "Optional provider UUID to filter time-series analytics",
      "name": "provider_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "description": "Start of the analytics range in ISO-8601 format",
       "example": "2026-04-01T00:00:00.000Z"
      },
      "required": true,
      "description": "Start of the analytics range in ISO-8601 format",
      "name": "from",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "description": "End of the analytics range in ISO-8601 format",
       "example": "2026-04-30T23:59:59.999Z"
      },
      "required": true,
      "description": "End of the analytics range in ISO-8601 format",
      "name": "to",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "hourly",
        "daily"
       ],
       "default": "daily",
       "description": "Aggregation granularity for the time-series data",
       "example": "daily"
      },
      "required": false,
      "description": "Aggregation granularity for the time-series data",
      "name": "granularity",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Time-series analytics returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/AnalyticsAdvancedTimeSeriesPointResponse"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": [
          {
           "period": "2026-04-30",
           "flowStarts": 120,
           "flowCompletions": 84,
           "keysGenerated": 33,
           "keysUsed": 21
          }
         ]
        }
       }
      }
     }
    },
    "x-scope": "analytics:read"
   }
  },
  "/api/analytics/funnel": {
   "get": {
    "tags": [
     "Analytics"
    ],
    "summary": "Get provider funnel metrics",
    "description": "Returns per-step funnel performance for a provider on a specific date. Use this to identify where users drop off in the flow, compare checkpoint effectiveness, and analyze how each step contributes to overall conversion.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Provider UUID to retrieve conversion funnel data for",
       "example": "550e8400-e29b-41d4-a716-446655440000"
      },
      "required": true,
      "description": "Provider UUID to retrieve conversion funnel data for",
      "name": "provider_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "description": "Target date in ISO-8601 format used to compute funnel metrics",
       "example": "2026-04-30T00:00:00.000Z"
      },
      "required": true,
      "description": "Target date in ISO-8601 format used to compute funnel metrics",
      "name": "date",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Funnel analytics returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/AnalyticsFunnelStepResponse"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": [
          {
           "stepIndex": 1,
           "stepName": "Discord verification",
           "entered": 200,
           "completed": 160,
           "dropped": 40,
           "conversionRate": 80
          }
         ]
        }
       }
      }
     }
    },
    "x-scope": "analytics:read"
   }
  },
  "/api/analytics/geo": {
   "get": {
    "tags": [
     "Analytics"
    ],
    "summary": "Get geographic analytics",
    "description": "Returns country-level flow performance for the requested time range. Use this endpoint for map visualizations, regional traffic analysis, or comparing completion rates across markets.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "description": "Start of the geo analytics range in ISO-8601 format",
       "example": "2026-04-01T00:00:00.000Z"
      },
      "required": true,
      "description": "Start of the geo analytics range in ISO-8601 format",
      "name": "from",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "description": "End of the geo analytics range in ISO-8601 format",
       "example": "2026-04-30T23:59:59.999Z"
      },
      "required": true,
      "description": "End of the geo analytics range in ISO-8601 format",
      "name": "to",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Geographic analytics returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/AnalyticsGeoPointResponse"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": [
          {
           "country": "US",
           "flowStarts": 150,
           "flowCompletions": 90
          }
         ]
        }
       }
      }
     }
    },
    "x-scope": "analytics:read"
   }
  },
  "/api/analytics/ab-comparison": {
   "get": {
    "tags": [
     "Analytics"
    ],
    "summary": "Compare A/B experiment variants",
    "description": "Returns aggregate performance for each variant in a named experiment across the requested date range. Use this to compare which experience drives more starts, completions, generated keys, or stronger conversion rates before rolling out a change.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "description": "Experiment name to compare A/B variant performance for",
       "example": "landing-page-copy-test"
      },
      "required": true,
      "description": "Experiment name to compare A/B variant performance for",
      "name": "experiment_name",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "description": "Start of the A/B comparison range in ISO-8601 format",
       "example": "2026-04-01T00:00:00.000Z"
      },
      "required": true,
      "description": "Start of the A/B comparison range in ISO-8601 format",
      "name": "from",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "description": "End of the A/B comparison range in ISO-8601 format",
       "example": "2026-04-30T23:59:59.999Z"
      },
      "required": true,
      "description": "End of the A/B comparison range in ISO-8601 format",
      "name": "to",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "A/B comparison returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/AnalyticsAbVariantResponse"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": [
          {
           "variant": "variant-a",
           "flowStarts": 300,
           "flowCompletions": 180,
           "keysGenerated": 72,
           "conversionRate": 60
          }
         ]
        }
       }
      }
     }
    },
    "x-scope": "analytics:read"
   }
  },
  "/api/keys/folders": {
   "get": {
    "tags": [
     "Folders"
    ],
    "summary": "List folders",
    "description": "Returns a paginated list of folders owned by the authenticated user. Use this endpoint for folder pickers, navigation trees, or organization views that need lightweight folder metadata.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "number",
       "minimum": 1,
       "maximum": 100,
       "default": 20,
       "description": "Maximum number of folders to return (1-100)",
       "example": 20
      },
      "required": false,
      "description": "Maximum number of folders to return (1-100)",
      "name": "limit",
      "in": "query"
     },
     {
      "schema": {
       "type": "number",
       "nullable": true,
       "minimum": 0,
       "default": 0,
       "description": "Number of folders to skip for pagination",
       "example": 0
      },
      "required": false,
      "description": "Number of folders to skip for pagination",
      "name": "offset",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Folder list returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/FolderResponse"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": [
          {
           "id": 123,
           "name": "Production Keys",
           "createdAt": "2026-04-30T15:37:35.645Z"
          },
          {
           "id": 124,
           "name": "Staging Keys",
           "createdAt": "2026-04-30T16:12:18.321Z"
          }
         ]
        }
       }
      }
     }
    },
    "x-scope": "folders:read"
   },
   "post": {
    "tags": [
     "Folders"
    ],
    "summary": "Create a folder",
    "description": "Creates a folder for organizing user-owned resources such as keys and providers. Use folders to separate environments, customers, or internal teams without changing the underlying resource behavior.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CreateFolderRequest"
       },
       "example": {
        "name": "Production Keys"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Folder created successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/FolderResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": {
          "id": 123,
          "name": "Production Keys",
          "createdAt": "2026-04-30T15:37:35.645Z"
         }
        }
       }
      }
     }
    },
    "x-scope": "folders:write"
   }
  },
  "/api/keys/folders/{id}": {
   "get": {
    "tags": [
     "Folders"
    ],
    "summary": "Get a folder",
    "description": "Returns a single folder by ID for the authenticated owner. Use this when loading folder details, verifying access before reassigning resources, or confirming the current folder name in the UI.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Folder ID",
       "example": 123
      },
      "required": true,
      "description": "Folder ID",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "200": {
      "description": "Folder returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/FolderResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": {
          "id": 123,
          "name": "Production Keys",
          "createdAt": "2026-04-30T15:37:35.645Z"
         }
        }
       }
      }
     }
    },
    "x-scope": "folders:read"
   },
   "put": {
    "tags": [
     "Folders"
    ],
    "summary": "Update a folder",
    "description": "Updates folder metadata for the authenticated owner. At present this is primarily used for renaming folders. If no updatable fields are provided, the current folder state is returned unchanged.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Folder ID",
       "example": 123
      },
      "required": true,
      "description": "Folder ID",
      "name": "id",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/UpdateFolderRequest"
       },
       "example": {
        "name": "Archived Keys"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Folder updated successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/FolderResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": {
          "id": 123,
          "name": "Staging Keys",
          "createdAt": "2026-04-30T15:37:35.645Z"
         }
        }
       }
      }
     }
    },
    "x-scope": "folders:write"
   },
   "delete": {
    "tags": [
     "Folders"
    ],
    "summary": "Delete a folder",
    "description": "Permanently deletes a folder owned by the authenticated user. Resources inside the folder are not deleted, but they become unassigned from that folder. Use this when cleaning up old organization structures. The operation cannot be undone.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Folder ID",
       "example": 123
      },
      "required": true,
      "description": "Folder ID",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "204": {
      "description": "Folder deleted successfully"
     }
    },
    "x-scope": "folders:write"
   }
  },
  "/api/keys": {
   "get": {
    "tags": [
     "Keys"
    ],
    "summary": "List keys",
    "description": "Returns a paginated key list with optional filters for provider, status, type, and search. Use this to power key management screens, exports, and reporting workflows.",
    "parameters": [
     {
      "schema": {
       "anyOf": [
        {
         "type": "string",
         "enum": [
          "true"
         ]
        },
        {
         "type": "string",
         "enum": [
          "false"
         ]
        }
       ],
       "default": true,
       "description": "Whether to return paginated results",
       "example": "true"
      },
      "required": false,
      "description": "Whether to return paginated results",
      "name": "paginated",
      "in": "query"
     },
     {
      "schema": {
       "type": "number",
       "minimum": 1,
       "default": 1,
       "description": "Page number for pagination (1-indexed)",
       "example": 1
      },
      "required": false,
      "description": "Page number for pagination (1-indexed)",
      "name": "page",
      "in": "query"
     },
     {
      "schema": {
       "type": "number",
       "minimum": 1,
       "maximum": 100,
       "default": 25,
       "description": "Number of keys per page (1-100)",
       "example": 25
      },
      "required": false,
      "description": "Number of keys per page (1-100)",
      "name": "limit",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "status",
        "status_active",
        "id",
        "newest",
        "oldest",
        "expires_soon",
        "expires_late"
       ],
       "default": "status",
       "description": "Sort order: status (disabled/expired first), status_active (active first), newest (alias id), oldest, expires_soon, expires_late",
       "example": "status"
      },
      "required": false,
      "description": "Sort order: status (disabled/expired first), status_active (active first), newest (alias id), oldest, expires_soon, expires_late",
      "name": "sort",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "premium",
        "standard"
       ],
       "description": "Filter keys by type. Options: premium, standard",
       "example": "premium"
      },
      "required": false,
      "description": "Filter keys by type. Options: premium, standard",
      "name": "keys_view",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 2,
       "description": "Search term to filter keys (minimum 2 characters)",
       "example": "trial"
      },
      "required": false,
      "description": "Search term to filter keys (minimum 2 characters)",
      "name": "search",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "active",
        "used",
        "expired",
        "disabled"
       ],
       "description": "Filter keys by status. Options: active, used, expired, disabled",
       "example": "active"
      },
      "required": false,
      "description": "Filter keys by status. Options: active, used, expired, disabled",
      "name": "status",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Filter keys by provider ID",
       "example": 1
      },
      "required": false,
      "description": "Filter keys by provider ID",
      "name": "provider_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Filter keys by folder ID",
       "example": 1
      },
      "required": false,
      "description": "Filter keys by folder ID",
      "name": "folder_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "pattern": "^\\d{5,25}$",
       "description": "Only keys linked to this Discord user",
       "example": "1550213780359610410"
      },
      "required": false,
      "description": "Only keys linked to this Discord user",
      "name": "discord_user_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "linked",
        "unlinked"
       ],
       "description": "Only keys with (linked) or without (unlinked) a Discord account",
       "example": "linked"
      },
      "required": false,
      "description": "Only keys with (linked) or without (unlinked) a Discord account",
      "name": "discord",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "maxLength": 64,
       "description": "Roblox username substring or exact Roblox user id",
       "example": "Builderman"
      },
      "required": false,
      "description": "Roblox username substring or exact Roblox user id",
      "name": "roblox",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "bound",
        "unbound"
       ],
       "description": "Only keys bound (or not yet bound) to a device",
       "example": "bound"
      },
      "required": false,
      "description": "Only keys bound (or not yet bound) to a device",
      "name": "hwid",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "maxLength": 64,
       "description": "Only keys validated at least once from this executor",
       "example": "Wave"
      },
      "required": false,
      "description": "Only keys validated at least once from this executor",
      "name": "executor",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "never",
        "24h",
        "7d",
        "30d"
       ],
       "description": "Keys that never expire, or unexpired keys expiring within the window",
       "example": "7d"
      },
      "required": false,
      "description": "Keys that never expire, or unexpired keys expiring within the window",
      "name": "expires",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "nullable": true,
       "format": "date-time",
       "description": "Created at or after this ISO timestamp",
       "example": "2026-09-01T00:00:00.000Z"
      },
      "required": false,
      "description": "Created at or after this ISO timestamp",
      "name": "created_from",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "nullable": true,
       "format": "date-time",
       "description": "Created at or before this ISO timestamp",
       "example": "2026-09-30T23:59:59.999Z"
      },
      "required": false,
      "description": "Created at or before this ISO timestamp",
      "name": "created_to",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Keys retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "keys": {
             "type": "array",
             "items": {
              "$ref": "#/components/schemas/KeyListItem"
             },
             "description": "Array of key objects"
            },
            "pagination": {
             "type": "object",
             "properties": {
              "page": {
               "type": "integer",
               "description": "Current page number",
               "example": 1
              },
              "limit": {
               "type": "integer",
               "description": "Keys per page",
               "example": 25
              },
              "total": {
               "type": "integer",
               "description": "Total number of keys",
               "example": 100
              },
              "totalPages": {
               "type": "integer",
               "description": "Total number of pages",
               "example": 4
              }
             },
             "required": [
              "page",
              "limit",
              "total",
              "totalPages"
             ],
             "description": "Pagination metadata"
            }
           },
           "required": [
            "keys",
            "pagination"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:read"
   },
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Create a key",
    "description": "Creates a single key with the requested access rules. Use this when you need one-off licenses, trials, premium access, or keys with hardware binding and expiration settings.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CreateKeyRequest"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Key created successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/CreateKeyResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/keys/batch": {
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Create keys in batch",
    "description": "Creates multiple keys with the same configuration in one request. This is useful for bulk distribution, promotions, or preload workflows where each generated key should share the same lifetime and usage rules.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CreateKeysBatchRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Batch keys created successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "ok": {
             "type": "boolean",
             "description": "Whether the batch operation succeeded",
             "example": true
            },
            "created": {
             "type": "integer",
             "description": "Number of keys successfully created",
             "example": 10
            }
           },
           "required": [
            "ok",
            "created"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/keys/filter-options": {
   "get": {
    "tags": [
     "Keys"
    ],
    "summary": "Key list filter options",
    "description": "Returns values for key list filters that depend on the service's data, such as the executors its keys were recently validated from.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Filter options retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/KeyFilterOptions"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:read"
   }
  },
  "/api/keys/discord-users": {
   "get": {
    "tags": [
     "Keys"
    ],
    "summary": "Discord profiles",
    "description": "Looks up display names, usernames and avatars for up to 100 Discord ids (cached for an hour).",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "description": "Comma-separated Discord user ids (max 100)",
       "example": "1550213780359610410,80351110224678912"
      },
      "required": true,
      "description": "Comma-separated Discord user ids (max 100)",
      "name": "ids",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Profiles retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/DiscordProfiles"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:read"
   }
  },
  "/api/keys/discord-users/{discordUserId}": {
   "get": {
    "tags": [
     "Keys"
    ],
    "summary": "Discord key holder",
    "description": "Returns a Discord user's keys in the service, the devices they bound, where they run scripts, and a merged activity timeline (key creation, validations, Discord panel actions, giveaway claims).",
    "parameters": [
     {
      "schema": {
       "type": "string",
       "pattern": "^\\d{5,25}$",
       "description": "Discord user id",
       "example": "1550213780359610410"
      },
      "required": true,
      "description": "Discord user id",
      "name": "discordUserId",
      "in": "path"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Key holder retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/DiscordKeyHolder"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:read"
   }
  },
  "/api/keys/bans": {
   "get": {
    "tags": [
     "Keys"
    ],
    "summary": "List service bans",
    "description": "Active bans of the service's key holders. A banned Discord account or device can't validate keys, claim keys through the flow or giveaways, or use the Discord panel of this service.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Bans retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ServiceBan"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:read"
   },
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Ban a key holder",
    "description": "Bans a Discord account or HWID from this service (editors and above). Banning an already banned subject replaces its reason and duration.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CreateServiceBan"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Ban created",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "allOf": [
            {
             "$ref": "#/components/schemas/ServiceBan"
            },
            {
             "description": "Response payload"
            }
           ]
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/keys/bans/{banId}": {
   "delete": {
    "tags": [
     "Keys"
    ],
    "summary": "Lift a service ban",
    "parameters": [
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "description": "Ban id"
      },
      "required": true,
      "description": "Ban id",
      "name": "banId",
      "in": "path"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Ban lifted",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "ok": {
             "type": "boolean",
             "enum": [
              true
             ]
            }
           },
           "required": [
            "ok"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/keys/sharing-alerts": {
   "get": {
    "tags": [
     "Keys"
    ],
    "summary": "List key sharing alerts",
    "description": "Keys the service's sharing policy flagged, newest first. `open` counts unresolved alerts regardless of the filter.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "open",
        "resolved"
       ]
      },
      "required": false,
      "name": "status",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 100,
       "default": 25
      },
      "required": false,
      "name": "limit",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "nullable": true,
       "minimum": 0,
       "default": 0
      },
      "required": false,
      "name": "offset",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Alerts retrieved",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "items": {
             "type": "array",
             "items": {
              "$ref": "#/components/schemas/KeySharingAlert"
             }
            },
            "total": {
             "type": "integer"
            },
            "open": {
             "type": "integer"
            }
           },
           "required": [
            "items",
            "total",
            "open"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:read"
   }
  },
  "/api/keys/sharing-alerts/{alertId}/resolve": {
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Resolve a key sharing alert",
    "description": "Closes an open alert (editors and above), optionally revoking the key or resetting its HWID.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true
      },
      "required": true,
      "name": "alertId",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ResolveKeySharingAlertBody"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "`resolved: false` when the alert was already resolved",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "resolved": {
             "type": "boolean"
            }
           },
           "required": [
            "resolved"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/keys/export": {
   "get": {
    "tags": [
     "Keys"
    ],
    "summary": "Export keys",
    "description": "Exports keys as a downloadable CSV, newline-delimited JSON, or plain text file. The same filters used by the list endpoint can be applied here when you need backups, offline analysis, or bulk distribution files.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Filter keys by folder ID",
       "example": 1
      },
      "required": false,
      "description": "Filter keys by folder ID",
      "name": "folder_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "csv",
        "json",
        "txt"
       ],
       "description": "Export format. Options: csv, json, txt",
       "example": "csv"
      },
      "required": true,
      "description": "Export format. Options: csv, json, txt",
      "name": "format",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "premium",
        "standard"
       ],
       "description": "Filter keys by type. Options: premium, standard",
       "example": "premium"
      },
      "required": false,
      "description": "Filter keys by type. Options: premium, standard",
      "name": "keys_view",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 2,
       "description": "Search term to filter keys (minimum 2 characters)",
       "example": "trial"
      },
      "required": false,
      "description": "Search term to filter keys (minimum 2 characters)",
      "name": "search",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "active",
        "used",
        "expired",
        "disabled"
       ],
       "description": "Filter keys by status. Options: active, used, expired, disabled",
       "example": "active"
      },
      "required": false,
      "description": "Filter keys by status. Options: active, used, expired, disabled",
      "name": "status",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Filter keys by provider ID",
       "example": 1
      },
      "required": false,
      "description": "Filter keys by provider ID",
      "name": "provider_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "status",
        "status_active",
        "id",
        "newest",
        "oldest",
        "expires_soon",
        "expires_late"
       ],
       "default": "status",
       "description": "Sort order: status (disabled/expired first), status_active (active first), newest (alias id), oldest, expires_soon, expires_late",
       "example": "status"
      },
      "required": false,
      "description": "Sort order: status (disabled/expired first), status_active (active first), newest (alias id), oldest, expires_soon, expires_late",
      "name": "sort",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "pattern": "^\\d{5,25}$",
       "description": "Only keys linked to this Discord user",
       "example": "1550213780359610410"
      },
      "required": false,
      "description": "Only keys linked to this Discord user",
      "name": "discord_user_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "linked",
        "unlinked"
       ],
       "description": "Only keys with (linked) or without (unlinked) a Discord account",
       "example": "linked"
      },
      "required": false,
      "description": "Only keys with (linked) or without (unlinked) a Discord account",
      "name": "discord",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "maxLength": 64,
       "description": "Roblox username substring or exact Roblox user id",
       "example": "Builderman"
      },
      "required": false,
      "description": "Roblox username substring or exact Roblox user id",
      "name": "roblox",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "bound",
        "unbound"
       ],
       "description": "Only keys bound (or not yet bound) to a device",
       "example": "bound"
      },
      "required": false,
      "description": "Only keys bound (or not yet bound) to a device",
      "name": "hwid",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "minLength": 1,
       "maxLength": 64,
       "description": "Only keys validated at least once from this executor",
       "example": "Wave"
      },
      "required": false,
      "description": "Only keys validated at least once from this executor",
      "name": "executor",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "never",
        "24h",
        "7d",
        "30d"
       ],
       "description": "Keys that never expire, or unexpired keys expiring within the window",
       "example": "7d"
      },
      "required": false,
      "description": "Keys that never expire, or unexpired keys expiring within the window",
      "name": "expires",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "nullable": true,
       "format": "date-time",
       "description": "Created at or after this ISO timestamp",
       "example": "2026-09-01T00:00:00.000Z"
      },
      "required": false,
      "description": "Created at or after this ISO timestamp",
      "name": "created_from",
      "in": "query"
     },
     {
      "schema": {
       "type": "string",
       "nullable": true,
       "format": "date-time",
       "description": "Created at or before this ISO timestamp",
       "example": "2026-09-30T23:59:59.999Z"
      },
      "required": false,
      "description": "Created at or before this ISO timestamp",
      "name": "created_to",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Keys exported successfully as file download",
      "content": {
       "text/csv": {
        "schema": {
         "type": "string",
         "format": "binary"
        }
       },
       "application/x-ndjson": {
        "schema": {
         "type": "string",
         "format": "binary"
        }
       },
       "text/plain": {
        "schema": {
         "type": "string",
         "format": "binary"
        }
       }
      }
     }
    },
    "x-scope": "keys:read"
   }
  },
  "/api/keys/expired": {
   "delete": {
    "tags": [
     "Keys"
    ],
    "summary": "Delete expired keys",
    "description": "Deletes all expired keys for the authenticated user. Optionally filter by provider_id to delete expired keys for a specific provider only. Use cases include cleanup operations, storage management, and maintaining a clean key database. Returns the count of deleted keys.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Optional provider ID to limit deletion to specific provider's expired keys",
       "example": 1
      },
      "required": false,
      "description": "Optional provider ID to limit deletion to specific provider's expired keys",
      "name": "provider_id",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Expired keys deleted successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "ok": {
             "type": "boolean",
             "enum": [
              true
             ],
             "description": "Operation success indicator",
             "example": true
            },
            "deleted": {
             "type": "integer",
             "description": "Number of expired keys deleted",
             "example": 42
            }
           },
           "required": [
            "ok",
            "deleted"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/keys/bulk": {
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Bulk key action",
    "description": "Applies one action to many keys of a service: an explicit list of ids (up to 1000) or every key matching the list filters. Requires the editor role. Returns how many keys changed; keys the action would not change are skipped.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/BulkKeyActionRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Action applied",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/BulkKeyActionResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/keys/refund": {
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Revoke keys of a refunded order",
    "description": "Call this from your store (or its automation) when an order is refunded or charged back: the listed keys are revoked at once and, with `ban`, their holders are banned from the service. Idempotent — repeating the call changes nothing. Values that are not keys of the service are returned in `not_found`. Requires the editor role (`keys:write` for API keys).",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RefundKeysRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Keys revoked",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/RefundKeysResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/keys/{id}": {
   "patch": {
    "tags": [
     "Keys"
    ],
    "summary": "Update a key",
    "description": "Updates mutable fields on an existing key (name, folder, Discord link, expiry, disabled state, premium flag, single-use flag, expiry-on-first-use flag, HWID locking and limit) and can reset its device binding, Roblox link or usage. Only fields included in the body are changed.",
    "parameters": [
     {
      "schema": {
       "type": "string",
       "format": "uuid",
       "description": "UUID of the key to update",
       "example": "880e8400-e29b-41d4-a716-446655440003"
      },
      "required": true,
      "description": "UUID of the key to update",
      "name": "id",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/UpdateKeyBody"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Key updated successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "allOf": [
            {
             "$ref": "#/components/schemas/KeyListItem"
            },
            {
             "description": "Response payload"
            }
           ]
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   },
   "delete": {
    "tags": [
     "Keys"
    ],
    "summary": "Delete a specific key",
    "description": "Deletes a single key by its UUID. The key must belong to the authenticated user or be accessible through sharing permissions. Use cases include revoking access, cleaning up test keys, and managing key lifecycle. Returns confirmation of deletion.",
    "parameters": [
     {
      "schema": {
       "type": "string",
       "format": "uuid",
       "description": "UUID of the key to delete",
       "example": "880e8400-e29b-41d4-a716-446655440003"
      },
      "required": true,
      "description": "UUID of the key to delete",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "200": {
      "description": "Key deleted successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "ok": {
             "type": "boolean",
             "enum": [
              true
             ],
             "description": "Operation success indicator",
             "example": true
            }
           },
           "required": [
            "ok"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/keys/import": {
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Import keys from CSV file",
    "description": "Imports keys from a CSV file upload. Requires provider_id and accepts an optional template_id for applying default settings. The CSV should contain key data in the expected format. Use cases include migrating from other systems, bulk key creation from external sources, and restoring from backups. Returns import statistics including success and failure counts.",
    "requestBody": {
     "content": {
      "multipart/form-data": {
       "schema": {
        "type": "object",
        "properties": {
         "file": {
          "type": "string",
          "description": "CSV file containing keys to import",
          "format": "binary"
         },
         "provider_id": {
          "type": "string",
          "format": "uuid",
          "description": "UUID of the provider to import keys into",
          "example": "550e8400-e29b-41d4-a716-446655440000"
         },
         "template_id": {
          "type": "string",
          "format": "uuid",
          "description": "Optional template UUID for default key settings",
          "example": "770e8400-e29b-41d4-a716-446655440002"
         },
         "service_id": {
          "type": "string",
          "description": "Optional service to attach the keys to. Service editors can import into the service's providers."
         }
        },
        "required": [
         "file",
         "provider_id"
        ]
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Keys imported successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "imported": {
             "type": "integer",
             "description": "Number of keys successfully imported",
             "example": 95
            },
            "skipped": {
             "type": "integer",
             "description": "Number of keys that were skipped",
             "example": 5
            },
            "errors": {
             "type": "array",
             "items": {
              "type": "string"
             },
             "description": "Array of error messages for failed imports"
            }
           },
           "required": [
            "imported",
            "skipped",
            "errors"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "keys:write"
   }
  },
  "/api/revenues/types": {
   "get": {
    "tags": [
     "Revenues"
    ],
    "summary": "List revenue types",
    "description": "Returns the revenue types the platform knows how to configure, such as Discord or Telegram. Call this before creating a revenue if the client needs to discover supported checkpoint backends dynamically.",
    "responses": {
     "200": {
      "description": "Revenue types retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/RevenueTypeResponse"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "revenues:read"
   }
  },
  "/api/revenues": {
   "get": {
    "tags": [
     "Revenues"
    ],
    "summary": "List revenues",
    "description": "Returns the revenues owned by the authenticated user. Use limit and offset to page through larger sets when building settings pages or checkpoint management tools.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "number",
       "minimum": 1,
       "maximum": 100,
       "default": 20,
       "description": "Number of revenues per page (1-100)",
       "example": 20
      },
      "required": false,
      "description": "Number of revenues per page (1-100)",
      "name": "limit",
      "in": "query"
     },
     {
      "schema": {
       "type": "number",
       "nullable": true,
       "minimum": 0,
       "default": 0,
       "description": "Number of revenues to skip for pagination",
       "example": 0
      },
      "required": false,
      "description": "Number of revenues to skip for pagination",
      "name": "offset",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "List of revenues retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/RevenueResponse"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "revenues:read"
   },
   "post": {
    "tags": [
     "Revenues"
    ],
    "summary": "Create a revenue",
    "description": "Creates a reusable third-party revenue that can later be attached to provider checkpoints. The required config depends on the selected revenue type. Use the /types endpoint first if the client needs to discover valid revenue families.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CreateRevenueRequest"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Revenue created successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "allOf": [
            {
             "$ref": "#/components/schemas/RevenueResponse"
            },
            {
             "description": "Response payload"
            }
           ]
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "revenues:write"
   }
  },
  "/api/revenues/{id}": {
   "get": {
    "tags": [
     "Revenues"
    ],
    "summary": "Get a revenue",
    "description": "Returns the full stored configuration for a single revenue, including its type, status, and other metadata needed to manage checkpoint behavior.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the revenue",
       "example": 4
      },
      "required": true,
      "description": "ID of the revenue",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "200": {
      "description": "Revenue retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "allOf": [
            {
             "$ref": "#/components/schemas/RevenueResponse"
            },
            {
             "description": "Response payload"
            }
           ]
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "revenues:read"
   },
   "patch": {
    "tags": [
     "Revenues"
    ],
    "summary": "Update a revenue",
    "description": "Updates part of a revenue record, such as its display name, config, or active status. Only the fields you send are changed. The revenue type itself stays fixed after creation.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the revenue",
       "example": 4
      },
      "required": true,
      "description": "ID of the revenue",
      "name": "id",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/UpdateRevenueRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Revenue updated successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "allOf": [
            {
             "$ref": "#/components/schemas/RevenueResponse"
            },
            {
             "description": "Response payload"
            }
           ]
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "revenues:write"
   },
   "delete": {
    "tags": [
     "Revenues"
    ],
    "summary": "Delete a revenue",
    "description": "Permanently deletes a revenue that is no longer needed. Revenues still referenced by provider checkpoints cannot be removed until those checkpoint references are cleared.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the revenue",
       "example": 4
      },
      "required": true,
      "description": "ID of the revenue",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "204": {
      "description": "Revenue deleted successfully"
     }
    },
    "x-scope": "revenues:write"
   }
  },
  "/api/revenues/{id}/stats": {
   "get": {
    "tags": [
     "Revenues"
    ],
    "summary": "Get revenue statistics",
    "description": "Returns usage metrics for a single revenue over a 7, 14, or 30 day window. This is useful when you want to understand how often a checkpoint backend runs and how reliably it succeeds.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the revenue",
       "example": 4
      },
      "required": true,
      "description": "ID of the revenue",
      "name": "id",
      "in": "path"
     },
     {
      "schema": {
       "type": "number",
       "description": "Time range in days for statistics",
       "example": 7
      },
      "required": true,
      "description": "Time range in days for statistics",
      "name": "range",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Revenue statistics retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "nullable": true,
           "description": "Response payload"
          }
         },
         "required": [
          "success"
         ]
        }
       }
      }
     }
    },
    "x-scope": "revenues:read"
   }
  },
  "/api/providers": {
   "get": {
    "tags": [
     "Providers"
    ],
    "summary": "List providers",
    "description": "Returns a service's providers in key-page order. Any member of the service may list them.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     },
     {
      "schema": {
       "type": "number",
       "minimum": 1,
       "maximum": 100,
       "default": 20,
       "description": "Number of providers per page (1-100)",
       "example": 20
      },
      "required": false,
      "description": "Number of providers per page (1-100)",
      "name": "limit",
      "in": "query"
     },
     {
      "schema": {
       "type": "number",
       "nullable": true,
       "minimum": 0,
       "default": 0,
       "description": "Number of providers to skip for pagination",
       "example": 0
      },
      "required": false,
      "description": "Number of providers to skip for pagination",
      "name": "offset",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "List of providers retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ProviderResponse"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "providers:read"
   },
   "post": {
    "tags": [
     "Providers"
    ],
    "summary": "Create a provider",
    "description": "Creates a provider that controls how keys are issued and what checkpoints a user must satisfy before receiving access. Use providers to define key lifetime rules, hardware binding rules, and checkpoint sequences.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CreateProviderRequest"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Provider created successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/ProviderResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "providers:write"
   }
  },
  "/api/providers/stats": {
   "get": {
    "tags": [
     "Providers"
    ],
    "summary": "Get provider statistics across all providers",
    "description": "Returns aggregated metrics across a service's providers for a 7, 14, or 30 day window. Use this for overview dashboards rather than provider-specific drilldowns.",
    "parameters": [
     {
      "schema": {
       "type": "number",
       "description": "Time range in days for statistics",
       "example": 7
      },
      "required": true,
      "description": "Time range in days for statistics",
      "name": "range",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Provider statistics retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "nullable": true,
           "description": "Response payload"
          }
         },
         "required": [
          "success"
         ]
        }
       }
      }
     }
    },
    "x-scope": "providers:read"
   }
  },
  "/api/providers/checkpoints/stats": {
   "get": {
    "tags": [
     "Providers"
    ],
    "summary": "Get checkpoint statistics across all providers",
    "description": "Returns aggregated checkpoint metrics across a service's providers for a 7, 14, or 30 day window.",
    "parameters": [
     {
      "schema": {
       "type": "number",
       "description": "Time range in days for statistics",
       "example": 7
      },
      "required": true,
      "description": "Time range in days for statistics",
      "name": "range",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Checkpoint statistics retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "nullable": true,
           "description": "Response payload"
          }
         },
         "required": [
          "success"
         ]
        }
       }
      }
     }
    },
    "x-scope": "providers:read"
   }
  },
  "/api/providers/{id}": {
   "get": {
    "tags": [
     "Providers"
    ],
    "summary": "Get a provider",
    "description": "Returns the full configuration for a single provider, including its checkpoint order, key lifetime settings, user selection settings, and other rules that affect future key issuance.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the provider",
       "example": 1
      },
      "required": true,
      "description": "ID of the provider",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "200": {
      "description": "Provider retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/ProviderResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "providers:read"
   },
   "put": {
    "tags": [
     "Providers"
    ],
    "summary": "Update a provider",
    "description": "Updates part of a provider configuration. Only the fields you send are changed. This is typically used to adjust checkpoint order, key lifetime defaults, or user-facing selection behavior for future key claims.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the provider",
       "example": 1
      },
      "required": true,
      "description": "ID of the provider",
      "name": "id",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/UpdateProviderRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Provider updated successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/ProviderResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "providers:write"
   },
   "delete": {
    "tags": [
     "Providers"
    ],
    "summary": "Delete a provider",
    "description": "Permanently deletes a provider. Use this only when the provider should no longer issue or validate access. This action cannot be undone.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the provider",
       "example": 1
      },
      "required": true,
      "description": "ID of the provider",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "204": {
      "description": "Provider deleted successfully"
     }
    },
    "x-scope": "providers:write"
   }
  },
  "/api/providers/{id}/stats": {
   "get": {
    "tags": [
     "Providers"
    ],
    "summary": "Get provider statistics",
    "description": "Returns detailed metrics for one provider over a 7, 14, or 30 day window. Use this endpoint for provider-level drilldowns when the aggregate stats endpoint is too broad.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the provider",
       "example": 1
      },
      "required": true,
      "description": "ID of the provider",
      "name": "id",
      "in": "path"
     },
     {
      "schema": {
       "type": "number",
       "description": "Time range in days for statistics",
       "example": 7
      },
      "required": true,
      "description": "Time range in days for statistics",
      "name": "range",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Provider statistics retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "nullable": true,
           "description": "Response payload"
          }
         },
         "required": [
          "success"
         ]
        }
       }
      }
     }
    },
    "x-scope": "providers:read"
   }
  },
  "/api/providers/{id}/checkpoints/{checkpointId}/stats": {
   "get": {
    "tags": [
     "Providers"
    ],
    "summary": "Get checkpoint statistics",
    "description": "Returns metrics for a single checkpoint within a provider over a 7, 14, or 30 day window, including completion behavior and checkpoint-level validation outcomes.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the provider",
       "example": 1
      },
      "required": true,
      "description": "ID of the provider",
      "name": "id",
      "in": "path"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the checkpoint",
       "example": 4
      },
      "required": true,
      "description": "ID of the checkpoint",
      "name": "checkpointId",
      "in": "path"
     },
     {
      "schema": {
       "type": "number",
       "description": "Time range in days for statistics",
       "example": 7
      },
      "required": true,
      "description": "Time range in days for statistics",
      "name": "range",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Checkpoint statistics retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "nullable": true,
           "description": "Response payload"
          }
         },
         "required": [
          "success"
         ]
        }
       }
      }
     }
    },
    "x-scope": "providers:read"
   }
  },
  "/api/providers/{id}/copy": {
   "post": {
    "tags": [
     "Providers"
    ],
    "summary": "Copy a provider into another service",
    "description": "A provider belongs to one service. This copies its settings and checkpoints into another service where the caller is an admin; checkpoints use that service's revenues of the same type and name.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the provider",
       "example": 1
      },
      "required": true,
      "description": "ID of the provider",
      "name": "id",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CopyProviderRequest"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Provider copied",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/ProviderResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "providers:write"
   }
  },
  "/api/services": {
   "get": {
    "tags": [
     "Services"
    ],
    "summary": "List services",
    "description": "Returns the services owned by the authenticated user. Use limit and offset to page through larger result sets when building dashboards or admin views.",
    "parameters": [
     {
      "schema": {
       "type": "number",
       "minimum": 1,
       "maximum": 100,
       "default": 20,
       "description": "Number of services per page (1-100)",
       "example": 20
      },
      "required": false,
      "description": "Number of services per page (1-100)",
      "name": "limit",
      "in": "query"
     },
     {
      "schema": {
       "type": "number",
       "nullable": true,
       "minimum": 0,
       "default": 0,
       "description": "Number of services to skip for pagination",
       "example": 0
      },
      "required": false,
      "description": "Number of services to skip for pagination",
      "name": "offset",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Services retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ServiceResponse"
           },
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "services:read"
   },
   "post": {
    "tags": [
     "Services"
    ],
    "summary": "Create a service",
    "description": "Creates a service that will receive keys from one or more providers. Use services to model the app, product, or offer users are trying to access. You can configure premium behavior, optional keyless access windows, and a public slug for user-facing claim flows.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CreateServiceRequest"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Service created successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/ServiceResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        },
        "example": {
         "success": true,
         "data": {
          "id": "660e8400-e29b-41d4-a716-446655440001",
          "name": "Premium Discord Bot",
          "description": "A premium bot with advanced features",
          "isPremium": false,
          "keylessMode": false,
          "keylessWeekdays": [],
          "slug": "premium-discord-bot",
          "hwidResetsAllowed": 3,
          "keysTotal": 0,
          "createdAt": "2026-04-30T15:37:00.604Z",
          "updatedAt": "2026-04-30T15:37:00.604Z"
         }
        }
       }
      }
     }
    },
    "x-scope": "services:write"
   }
  },
  "/api/services/slug/check": {
   "get": {
    "tags": [
     "Services"
    ],
    "summary": "Check service slug availability",
    "description": "Reports whether a slug is free to use for a new or existing service. Use this endpoint to give live feedback on the slug input field. The slug is unique platform-wide; pass exclude_id when editing so the service's own slug is not flagged as taken.",
    "parameters": [
     {
      "schema": {
       "type": "string",
       "maxLength": 64,
       "description": "Slug to test for availability. Empty/whitespace is treated as unset and always reported available.",
       "example": "premium-discord-bot"
      },
      "required": true,
      "description": "Slug to test for availability. Empty/whitespace is treated as unset and always reported available.",
      "name": "slug",
      "in": "query"
     },
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service ID to exclude from the duplicate check. Use this when editing a service so its current slug is not flagged as taken.",
       "example": 1
      },
      "required": false,
      "description": "Service ID to exclude from the duplicate check. Use this when editing a service so its current slug is not flagged as taken.",
      "name": "exclude_id",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Slug availability report",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/CheckServiceSlugResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "services:read"
   }
  },
  "/api/services/{id}": {
   "get": {
    "tags": [
     "Services"
    ],
    "summary": "Get a service",
    "description": "Returns the full configuration for a single service, including premium settings, keyless access settings, public slug, and service-level limits.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the service",
       "example": 1
      },
      "required": true,
      "description": "ID of the service",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "200": {
      "description": "Service retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/ServiceResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "services:read"
   },
   "put": {
    "tags": [
     "Services"
    ],
    "summary": "Update a service",
    "description": "Updates part of a service configuration. Only the fields you send are changed. This is commonly used to rename a service, adjust public slug behavior, or change keyless access settings for future claims.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the service",
       "example": 1
      },
      "required": true,
      "description": "ID of the service",
      "name": "id",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/UpdateServiceRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Service updated successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/ServiceResponse"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "services:write"
   },
   "delete": {
    "tags": [
     "Services"
    ],
    "summary": "Delete a service",
    "description": "Permanently deletes a service. Use this only when the service should no longer appear in admin or public claim flows. This action cannot be undone.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the service",
       "example": 1
      },
      "required": true,
      "description": "ID of the service",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "204": {
      "description": "Service deleted successfully"
     }
    },
    "x-scope": "services:write"
   }
  },
  "/api/services/{id}/providers": {
   "get": {
    "tags": [
     "Services"
    ],
    "summary": "List a service's providers in key-page order",
    "description": "Returns the service's providers with their checkpoints, ordered by priority as shown on the public key page. Readable by every member of the service.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the service",
       "example": 1
      },
      "required": true,
      "description": "ID of the service",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "200": {
      "description": "Providers retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "nullable": true,
           "description": "Response payload"
          }
         },
         "required": [
          "success"
         ]
        }
       }
      }
     }
    },
    "x-scope": "services:read"
   }
  },
  "/api/services/{id}/stats": {
   "get": {
    "tags": [
     "Services"
    ],
    "summary": "Get traffic stats for a service",
    "description": "Traffic and keys for this service over the selected range: totals (incl. keys generated/used), daily series, executors, countries, referrers, per-provider conversion and per-revenue checkpoint passes. Available to every member of the service.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the service",
       "example": 1
      },
      "required": true,
      "description": "ID of the service",
      "name": "id",
      "in": "path"
     },
     {
      "schema": {
       "type": "string",
       "enum": [
        "7",
        "14",
        "30",
        "90",
        "365"
       ],
       "description": "Range in days, capped by the owner's plan",
       "example": "30"
      },
      "required": false,
      "description": "Range in days, capped by the owner's plan",
      "name": "range",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Stats retrieved successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "service_id": {
             "type": "number"
            },
            "range_days": {
             "type": "number"
            },
            "flow_starts": {
             "type": "number"
            },
            "checkpoints_passed": {
             "type": "number"
            },
            "key_validations": {
             "type": "number"
            },
            "keys_generated": {
             "type": "number"
            },
            "keys_used": {
             "type": "number"
            },
            "timeSeries": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "date": {
                "type": "string"
               },
               "flow_starts": {
                "type": "number"
               },
               "checkpoints_passed": {
                "type": "number"
               },
               "key_validations": {
                "type": "number"
               },
               "keys_generated": {
                "type": "number"
               },
               "keys_used": {
                "type": "number"
               }
              },
              "required": [
               "date",
               "flow_starts",
               "checkpoints_passed",
               "key_validations",
               "keys_generated",
               "keys_used"
              ]
             }
            },
            "byExecutor": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "date": {
                "type": "string"
               },
               "executor": {
                "type": "string"
               },
               "count": {
                "type": "number"
               }
              },
              "required": [
               "date",
               "executor",
               "count"
              ]
             }
            },
            "byCountry": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "country": {
                "type": "string"
               },
               "count": {
                "type": "number"
               }
              },
              "required": [
               "country",
               "count"
              ]
             }
            },
            "byReferrer": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "referrer": {
                "type": "string"
               },
               "count": {
                "type": "number"
               }
              },
              "required": [
               "referrer",
               "count"
              ]
             }
            },
            "byProvider": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "provider_id": {
                "type": "number"
               },
               "provider_name": {
                "type": "string"
               },
               "flow_starts": {
                "type": "number"
               },
               "checkpoints_passed": {
                "type": "number"
               }
              },
              "required": [
               "provider_id",
               "provider_name",
               "flow_starts",
               "checkpoints_passed"
              ]
             }
            },
            "byRevenue": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "revenue_id": {
                "type": "number"
               },
               "revenue_name": {
                "type": "string"
               },
               "checkpoints_passed": {
                "type": "number"
               }
              },
              "required": [
               "revenue_id",
               "revenue_name",
               "checkpoints_passed"
              ]
             }
            },
            "locked": {
             "type": "array",
             "items": {
              "type": "string"
             }
            },
            "maxRangeDays": {
             "type": "number"
            }
           },
           "required": [
            "service_id",
            "range_days",
            "flow_starts",
            "checkpoints_passed",
            "key_validations",
            "keys_generated",
            "keys_used",
            "timeSeries",
            "byExecutor",
            "byCountry",
            "byReferrer",
            "byProvider",
            "byRevenue",
            "locked",
            "maxRangeDays"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "services:read"
   }
  },
  "/api/services/{id}/providers/order": {
   "put": {
    "tags": [
     "Services"
    ],
    "summary": "Reorder a service's providers",
    "description": "Sets the order of the service's providers on its public key page. Requires the admin role.",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "ID of the service",
       "example": 1
      },
      "required": true,
      "description": "ID of the service",
      "name": "id",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ReorderServiceProvidersRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Providers reordered successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "nullable": true,
           "description": "Response payload"
          }
         },
         "required": [
          "success"
         ]
        }
       }
      }
     }
    },
    "x-scope": "services:write"
   }
  },
  "/api/public/keys/lookup": {
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Look up your key",
    "description": "For buyers: the status of a key and whether its HWID can be reset right now. Authenticated only by knowing the key.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/KeySelfServiceRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Key status",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/KeySelfServiceStatus"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    }
   }
  },
  "/api/public/keys/reset-hwid": {
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Reset your key's HWID",
    "description": "For buyers: unbinds the key from its device so the next device that uses it binds instead. Allowed only when the service enables self-service resets, within its per-key limit and cooldown.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/KeySelfServiceRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "HWID reset",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/KeySelfServiceResetResult"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    }
   }
  },
  "/api/public/keys/transfer": {
   "post": {
    "tags": [
     "Keys"
    ],
    "summary": "Transfer your key",
    "description": "For buyers: hands the key to someone else. Unbinds its HWID, Roblox account and Discord holder, and binds the new Discord holder if given. Allowed only when the service enables transfers, once per its cooldown; a Discord account banned from the service can't receive keys.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/KeyTransferRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Key transferred",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "$ref": "#/components/schemas/KeySelfServiceTransferResult"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    }
   }
  },
  "/api/v1/webhooks/providers": {
   "get": {
    "tags": [
     "Webhooks"
    ],
    "summary": "List supported webhook providers",
    "responses": {
     "200": {
      "description": "Provider metadata returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "providers": {
             "type": "object",
             "additionalProperties": {
              "type": "object",
              "properties": {
               "name": {
                "type": "string"
               },
               "description": {
                "type": "string"
               },
               "hmacHeader": {
                "type": "string"
               },
               "hmacAlgorithm": {
                "type": "string"
               }
              },
              "required": [
               "name",
               "description",
               "hmacHeader",
               "hmacAlgorithm"
              ]
             }
            }
           },
           "required": [
            "providers"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "webhooks:read"
   }
  },
  "/api/v1/webhooks/actions": {
   "get": {
    "tags": [
     "Webhooks"
    ],
    "summary": "List supported webhook actions",
    "responses": {
     "200": {
      "description": "Webhook action metadata returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "actions": {
             "type": "object",
             "additionalProperties": {
              "type": "object",
              "properties": {
               "name": {
                "type": "string"
               },
               "description": {
                "type": "string"
               },
               "configSchema": {
                "type": "object",
                "additionalProperties": {
                 "nullable": true
                }
               }
              },
              "required": [
               "name",
               "description",
               "configSchema"
              ]
             }
            }
           },
           "required": [
            "actions"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "webhooks:read"
   }
  },
  "/api/v1/webhooks": {
   "get": {
    "tags": [
     "Webhooks"
    ],
    "summary": "List a service's webhooks",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Service the resource belongs to",
       "example": 1
      },
      "required": true,
      "description": "Service the resource belongs to",
      "name": "service_id",
      "in": "query"
     }
    ],
    "responses": {
     "200": {
      "description": "Webhook list returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "webhooks": {
             "type": "array",
             "items": {
              "$ref": "#/components/schemas/WebhookListItem"
             }
            }
           },
           "required": [
            "webhooks"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "webhooks:read"
   },
   "post": {
    "tags": [
     "Webhooks"
    ],
    "summary": "Create a webhook",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CreateWebhookRequest"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Webhook created successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "webhook": {
             "$ref": "#/components/schemas/WebhookResponse"
            }
           },
           "required": [
            "webhook"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "webhooks:write"
   }
  },
  "/api/v1/webhooks/{id}": {
   "get": {
    "tags": [
     "Webhooks"
    ],
    "summary": "Get a webhook",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Webhook ID",
       "example": 123
      },
      "required": true,
      "description": "Webhook ID",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "200": {
      "description": "Webhook returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "webhook": {
             "$ref": "#/components/schemas/WebhookResponse"
            }
           },
           "required": [
            "webhook"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "webhooks:read"
   },
   "put": {
    "tags": [
     "Webhooks"
    ],
    "summary": "Update a webhook",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Webhook ID",
       "example": 123
      },
      "required": true,
      "description": "Webhook ID",
      "name": "id",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/UpdateWebhookRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Webhook updated successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "webhook": {
             "$ref": "#/components/schemas/WebhookResponse"
            }
           },
           "required": [
            "webhook"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "webhooks:write"
   },
   "delete": {
    "tags": [
     "Webhooks"
    ],
    "summary": "Delete a webhook",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Webhook ID",
       "example": 123
      },
      "required": true,
      "description": "Webhook ID",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "204": {
      "description": "Webhook deleted successfully"
     }
    },
    "x-scope": "webhooks:write"
   }
  },
  "/api/v1/webhooks/{id}/logs": {
   "get": {
    "tags": [
     "Webhooks"
    ],
    "summary": "Get webhook delivery logs",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Webhook ID",
       "example": 123
      },
      "required": true,
      "description": "Webhook ID",
      "name": "id",
      "in": "path"
     }
    ],
    "responses": {
     "200": {
      "description": "Webhook logs returned successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "logs": {
             "type": "array",
             "items": {
              "$ref": "#/components/schemas/WebhookLogResponse"
             }
            }
           },
           "required": [
            "logs"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "webhooks:read"
   }
  },
  "/api/v1/webhooks/{id}/test": {
   "post": {
    "tags": [
     "Webhooks"
    ],
    "summary": "Send a test event through a webhook",
    "parameters": [
     {
      "schema": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true,
       "description": "Webhook ID",
       "example": 123
      },
      "required": true,
      "description": "Webhook ID",
      "name": "id",
      "in": "path"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TestWebhookRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Test executed successfully",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "success": {
           "type": "boolean",
           "enum": [
            true
           ],
           "description": "Request success indicator"
          },
          "data": {
           "type": "object",
           "properties": {
            "result": {
             "$ref": "#/components/schemas/TestWebhookResult"
            }
           },
           "required": [
            "result"
           ],
           "description": "Response payload"
          }
         },
         "required": [
          "success",
          "data"
         ]
        }
       }
      }
     }
    },
    "x-scope": "webhooks:write"
   }
  }
 },
 "components": {
  "schemas": {
   "AnalyticsAbVariantResponse": {
    "type": "object",
    "properties": {
     "variant": {
      "type": "string",
      "description": "Experiment variant name",
      "example": "variant-a"
     },
     "flowStarts": {
      "type": "number",
      "description": "Number of flow starts",
      "example": 300
     },
     "flowCompletions": {
      "type": "number",
      "description": "Number of flow completions",
      "example": 180
     },
     "keysGenerated": {
      "type": "number",
      "description": "Number of keys generated",
      "example": 72
     },
     "conversionRate": {
      "type": "number",
      "description": "Variant conversion rate percentage",
      "example": 60
     }
    },
    "required": [
     "variant",
     "flowStarts",
     "flowCompletions",
     "keysGenerated",
     "conversionRate"
    ]
   },
   "AnalyticsAdvancedTimeSeriesPointResponse": {
    "type": "object",
    "properties": {
     "period": {
      "type": "string",
      "description": "Time bucket identifier",
      "example": "2026-04-30"
     },
     "flowStarts": {
      "type": "number",
      "description": "Number of flow starts",
      "example": 120
     },
     "flowCompletions": {
      "type": "number",
      "description": "Number of flow completions",
      "example": 84
     },
     "keysGenerated": {
      "type": "number",
      "description": "Number of keys generated",
      "example": 33
     },
     "keysUsed": {
      "type": "number",
      "description": "Number of keys used",
      "example": 21
     }
    },
    "required": [
     "period",
     "flowStarts",
     "flowCompletions",
     "keysGenerated",
     "keysUsed"
    ]
   },
   "AnalyticsCountryStatsResponse": {
    "type": "object",
    "properties": {
     "country": {
      "type": "string",
      "description": "Country code or name",
      "example": "US"
     },
     "count": {
      "type": "number",
      "description": "Flow count for the country",
      "example": 840
     }
    },
    "required": [
     "country",
     "count"
    ]
   },
   "AnalyticsExecutorDayStatsResponse": {
    "type": "object",
    "properties": {
     "date": {
      "type": "string",
      "description": "Date bucket label",
      "example": "2026-04-30"
     },
     "executor": {
      "type": "string",
      "description": "Executor name",
      "example": "cloudflare-turnstile"
     },
     "count": {
      "type": "number",
      "description": "Execution count",
      "example": 120
     }
    },
    "required": [
     "date",
     "executor",
     "count"
    ]
   },
   "AnalyticsFunnelStepResponse": {
    "type": "object",
    "properties": {
     "stepIndex": {
      "type": "number",
      "description": "Funnel step order index",
      "example": 1
     },
     "stepName": {
      "type": "string",
      "description": "Funnel step name",
      "example": "Discord verification"
     },
     "entered": {
      "type": "number",
      "description": "Users entering the step",
      "example": 200
     },
     "completed": {
      "type": "number",
      "description": "Users completing the step",
      "example": 160
     },
     "dropped": {
      "type": "number",
      "description": "Users dropping at the step",
      "example": 40
     },
     "conversionRate": {
      "type": "number",
      "description": "Step conversion rate percentage",
      "example": 80
     }
    },
    "required": [
     "stepIndex",
     "stepName",
     "entered",
     "completed",
     "dropped",
     "conversionRate"
    ]
   },
   "AnalyticsGeoPointResponse": {
    "type": "object",
    "properties": {
     "country": {
      "type": "string",
      "description": "Country code or name",
      "example": "US"
     },
     "flowStarts": {
      "type": "number",
      "description": "Number of flow starts",
      "example": 150
     },
     "flowCompletions": {
      "type": "number",
      "description": "Number of flow completions",
      "example": 90
     }
    },
    "required": [
     "country",
     "flowStarts",
     "flowCompletions"
    ]
   },
   "AnalyticsProviderStatsResponse": {
    "type": "object",
    "properties": {
     "provider_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Provider UUID",
      "example": "770e8400-e29b-41d4-a716-446655440002"
     },
     "provider_name": {
      "type": "string",
      "description": "Provider display name",
      "example": "Discord Verification"
     },
     "flow_starts": {
      "type": "number",
      "description": "Number of flow starts",
      "example": 410
     },
     "checkpoints_passed": {
      "type": "number",
      "description": "Number of checkpoints passed",
      "example": 245
     }
    },
    "required": [
     "provider_id",
     "provider_name",
     "flow_starts",
     "checkpoints_passed"
    ]
   },
   "AnalyticsReferrerStatsResponse": {
    "type": "object",
    "properties": {
     "referrer": {
      "type": "string",
      "description": "Traffic referrer",
      "example": "discord.gg"
     },
     "count": {
      "type": "number",
      "description": "Flow count for the referrer",
      "example": 220
     }
    },
    "required": [
     "referrer",
     "count"
    ]
   },
   "AnalyticsRevenueStatsResponse": {
    "type": "object",
    "properties": {
     "revenue_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Revenue UUID",
      "example": "880e8400-e29b-41d4-a716-446655440003"
     },
     "revenue_name": {
      "type": "string",
      "description": "Revenue display name",
      "example": "Discord OAuth"
     },
     "checkpoints_passed": {
      "type": "number",
      "description": "Number of checkpoints passed",
      "example": 188
     }
    },
    "required": [
     "revenue_id",
     "revenue_name",
     "checkpoints_passed"
    ]
   },
   "AnalyticsServiceStatsResponse": {
    "type": "object",
    "properties": {
     "service_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service UUID",
      "example": "660e8400-e29b-41d4-a716-446655440001"
     },
     "service_name": {
      "type": "string",
      "description": "Service display name",
      "example": "Premium Access"
     },
     "flow_starts": {
      "type": "number",
      "description": "Number of flow starts",
      "example": 510
     },
     "checkpoints_passed": {
      "type": "number",
      "description": "Number of checkpoints passed",
      "example": 301
     }
    },
    "required": [
     "service_id",
     "service_name",
     "flow_starts",
     "checkpoints_passed"
    ]
   },
   "AnalyticsStatsResponse": {
    "type": "object",
    "properties": {
     "totals": {
      "$ref": "#/components/schemas/AnalyticsTotalsResponse"
     },
     "byExecutor": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/AnalyticsExecutorDayStatsResponse"
      }
     },
     "byCountry": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/AnalyticsCountryStatsResponse"
      }
     },
     "byReferrer": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/AnalyticsReferrerStatsResponse"
      }
     },
     "byService": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/AnalyticsServiceStatsResponse"
      }
     },
     "byProvider": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/AnalyticsProviderStatsResponse"
      }
     },
     "byRevenue": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/AnalyticsRevenueStatsResponse"
      }
     },
     "timeSeries": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/AnalyticsTimeSeriesPointResponse"
      }
     },
     "locked": {
      "type": "array",
      "items": {
       "type": "string",
       "enum": [
        "byExecutor",
        "byCountry",
        "byReferrer",
        "byService",
        "byProvider",
        "byRevenue"
       ]
      },
      "description": "Breakdowns withheld by the plan (basic analytics); they come back as empty arrays."
     },
     "maxRangeDays": {
      "type": "integer",
      "description": "Longest range the plan allows.",
      "example": 90
     },
     "_cached": {
      "type": "boolean",
      "description": "Whether the response was served from cache",
      "example": false
     },
     "_generatedAt": {
      "type": "string",
      "format": "date-time",
      "description": "Timestamp when the analytics payload was generated",
      "example": "2026-04-30T15:38:25.123Z"
     }
    },
    "required": [
     "totals",
     "byExecutor",
     "byCountry",
     "byReferrer",
     "byService",
     "byProvider",
     "byRevenue",
     "timeSeries",
     "locked",
     "maxRangeDays",
     "_cached",
     "_generatedAt"
    ],
    "description": "Response payload"
   },
   "AnalyticsTimeSeriesPointResponse": {
    "type": "object",
    "properties": {
     "date": {
      "type": "string",
      "description": "Date bucket label",
      "example": "2026-04-30"
     },
     "clicks": {
      "type": "number",
      "description": "Number of clicks",
      "example": 342
     },
     "checkpoints": {
      "type": "number",
      "description": "Number of checkpoints completed",
      "example": 198
     },
     "keys": {
      "type": "number",
      "description": "Legacy key count field",
      "example": 0
     },
     "keys_generated": {
      "type": "number",
      "description": "Number of keys generated",
      "example": 75
     },
     "keys_used": {
      "type": "number",
      "description": "Number of keys used",
      "example": 56
     },
     "script_executions": {
      "type": "number",
      "description": "Number of script executions",
      "example": 0
     }
    },
    "required": [
     "date",
     "clicks",
     "checkpoints",
     "keys",
     "keys_generated",
     "keys_used",
     "script_executions"
    ]
   },
   "AnalyticsTotalsResponse": {
    "type": "object",
    "properties": {
     "total_keys": {
      "type": "number",
      "description": "Total number of keys",
      "example": 2500
     },
     "total_generated_keys": {
      "type": "number",
      "description": "Total generated keys",
      "example": 2400
     },
     "total_used_keys": {
      "type": "number",
      "description": "Total used keys",
      "example": 1820
     },
     "expired_keys": {
      "type": "number",
      "description": "Total expired keys",
      "example": 115
     },
     "total_flows": {
      "type": "number",
      "description": "Total flow starts",
      "example": 6400
     },
     "active_revenues": {
      "type": "number",
      "description": "Currently active revenues",
      "example": 8
     },
     "total_script_executions": {
      "type": "number",
      "description": "Total script executions",
      "example": 0
     },
     "total_active_scripts": {
      "type": "number",
      "description": "Total active scripts",
      "example": 0
     }
    },
    "required": [
     "total_keys",
     "total_generated_keys",
     "total_used_keys",
     "expired_keys",
     "total_flows",
     "active_revenues",
     "total_script_executions",
     "total_active_scripts"
    ]
   },
   "BulkKeyActionRequest": {
    "type": "object",
    "properties": {
     "service_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the resource belongs to",
      "example": 1
     },
     "action": {
      "type": "string",
      "enum": [
       "revoke",
       "restore",
       "extend",
       "delete",
       "reset_hwid"
      ],
      "description": "revoke disables the keys, restore re-enables them, extend adds `minutes` (lifetime keys are skipped), delete removes them, reset_hwid unbinds their devices",
      "example": "extend"
     },
     "minutes": {
      "type": "integer",
      "minimum": 1,
      "maximum": 5256000,
      "description": "Minutes to add; required for extend",
      "example": 10080
     },
     "ids": {
      "type": "array",
      "items": {
       "type": "string",
       "format": "uuid"
      },
      "minItems": 1,
      "maxItems": 1000,
      "description": "Explicit key ids (up to 1000). Mutually exclusive with filters.",
      "example": [
       "880e8400-e29b-41d4-a716-446655440003"
      ]
     },
     "filters": {
      "$ref": "#/components/schemas/BulkKeyFilters"
     }
    },
    "required": [
     "service_id",
     "action"
    ]
   },
   "BulkKeyActionResponse": {
    "type": "object",
    "properties": {
     "action": {
      "type": "string",
      "enum": [
       "revoke",
       "restore",
       "extend",
       "delete",
       "reset_hwid"
      ]
     },
     "affected": {
      "type": "integer",
      "description": "Keys that changed",
      "example": 42
     }
    },
    "required": [
     "action",
     "affected"
    ],
    "description": "Response payload"
   },
   "BulkKeyFilters": {
    "type": "object",
    "properties": {
     "keys_view": {
      "type": "string",
      "enum": [
       "premium",
       "standard"
      ],
      "description": "Only premium or only standard keys",
      "example": "premium"
     },
     "search": {
      "type": "string",
      "minLength": 2,
      "description": "Search term (minimum 2 characters)",
      "example": "trial"
     },
     "status": {
      "type": "string",
      "enum": [
       "active",
       "used",
       "expired",
       "disabled"
      ],
      "description": "Only keys with this status",
      "example": "expired"
     },
     "provider_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Only keys of this provider",
      "example": 1
     },
     "folder_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Only keys in this folder",
      "example": 1
     },
     "discord_user_id": {
      "type": "string",
      "pattern": "^\\d{5,25}$",
      "description": "Only keys linked to this Discord user",
      "example": "1550213780359610410"
     },
     "discord": {
      "type": "string",
      "enum": [
       "linked",
       "unlinked"
      ],
      "description": "Only keys with (linked) or without (unlinked) a Discord account",
      "example": "linked"
     },
     "roblox": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Roblox username substring or exact Roblox user id",
      "example": "Builderman"
     },
     "hwid": {
      "type": "string",
      "enum": [
       "bound",
       "unbound"
      ],
      "description": "Only keys bound (or not yet bound) to a device",
      "example": "bound"
     },
     "executor": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Only keys validated at least once from this executor",
      "example": "Wave"
     },
     "expires": {
      "type": "string",
      "enum": [
       "never",
       "24h",
       "7d",
       "30d"
      ],
      "description": "Keys that never expire, or unexpired keys expiring within the window",
      "example": "7d"
     },
     "created_from": {
      "type": "string",
      "nullable": true,
      "format": "date-time",
      "description": "Created at or after this ISO timestamp",
      "example": "2026-09-01T00:00:00.000Z"
     },
     "created_to": {
      "type": "string",
      "nullable": true,
      "format": "date-time",
      "description": "Created at or before this ISO timestamp",
      "example": "2026-09-30T23:59:59.999Z"
     }
    },
    "description": "Apply to every key of the service matching these filters. `{}` selects every key."
   },
   "CheckServiceSlugResponse": {
    "type": "object",
    "properties": {
     "available": {
      "type": "boolean"
     },
     "slug": {
      "type": "string"
     }
    },
    "required": [
     "available",
     "slug"
    ],
    "description": "Response payload"
   },
   "Checkpoint": {
    "type": "object",
    "properties": {
     "revenue_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Revenue ID used as this checkpoint step",
      "example": 4
     },
     "meta": {
      "type": "object",
      "additionalProperties": {
       "nullable": true
      },
      "description": "Optional checkpoint-specific configuration passed to the revenue",
      "example": {
       "threshold": 100
      }
     }
    },
    "required": [
     "revenue_id"
    ]
   },
   "CopyProviderRequest": {
    "type": "object",
    "properties": {
     "target_service_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the resource belongs to",
      "example": 1
     }
    },
    "required": [
     "target_service_id"
    ]
   },
   "CreateFolderRequest": {
    "type": "object",
    "properties": {
     "service_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the resource belongs to",
      "example": 1
     },
     "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 32,
      "description": "Folder name for organizing keys",
      "example": "Production Keys"
     }
    },
    "required": [
     "service_id",
     "name"
    ]
   },
   "CreateKeyRequest": {
    "type": "object",
    "properties": {
     "provider_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "ID of the provider this key belongs to",
      "example": 1
     },
     "service_id": {
      "type": "integer",
      "nullable": true,
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Optional ID of the service this key is associated with",
      "example": 1
     },
     "folder_id": {
      "type": "integer",
      "nullable": true,
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Optional ID of the folder to organize this key",
      "example": 1
     },
     "minutes": {
      "type": "integer",
      "nullable": true,
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Key validity duration in minutes. Cannot be used with expires_in_days",
      "example": 1440
     },
     "expires_in_days": {
      "type": "integer",
      "nullable": true,
      "minimum": 1,
      "maximum": 36500,
      "description": "Key validity duration in days (1-36500). Cannot be used with minutes",
      "example": 30
     },
     "hwid": {
      "type": "string",
      "nullable": true,
      "maxLength": 512,
      "description": "Hardware ID to bind this key to a specific device. Cannot be used with no_hwid=true",
      "example": "HWID-ABC123-XYZ789"
     },
     "key_type": {
      "type": "string",
      "nullable": true,
      "enum": [
       "free",
       "premium",
       null
      ],
      "description": "Key type classification. Options: free, premium. Must match is_premium value",
      "example": "premium"
     },
     "is_premium": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether this is a premium key. Must match key_type if provided",
      "example": false
     },
     "one_time_use": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether the key can only be used once",
      "example": false
     },
     "expiry_on_first_use": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether the key expiry timer starts on first use instead of creation",
      "example": false
     },
     "no_hwid": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether to disable hardware ID binding. Cannot be used with hwid or hwid_limit",
      "example": false
     },
     "hwid_limit": {
      "type": "integer",
      "nullable": true,
      "minimum": 1,
      "description": "Maximum number of hardware IDs that can use this key. Cannot be used with no_hwid=true",
      "example": 1
     },
     "key_name": {
      "type": "string",
      "nullable": true,
      "maxLength": 64,
      "description": "Optional custom name for the key",
      "example": "Trial Key for John"
     }
    },
    "required": [
     "provider_id"
    ]
   },
   "CreateKeyResponse": {
    "type": "object",
    "properties": {
     "keyId": {
      "type": "string",
      "format": "uuid",
      "description": "Created key unique identifier",
      "example": "880e8400-e29b-41d4-a716-446655440003"
     },
     "key": {
      "type": "string",
      "description": "The actual key value",
      "example": "MYAPP-ABC1-DEF2-GHI3"
     },
     "expiresInMinutes": {
      "type": "integer",
      "nullable": true,
      "description": "Key expiration duration in minutes",
      "example": 1440
     },
     "oneTimeUse": {
      "type": "boolean",
      "description": "Whether the key can only be used once",
      "example": false
     },
     "expiryOnFirstUse": {
      "type": "boolean",
      "description": "Whether the key expiry timer starts on first use",
      "example": false
     },
     "validityMinutes": {
      "type": "integer",
      "nullable": true,
      "description": "Key validity duration in minutes",
      "example": 1440
     },
     "hwidLimit": {
      "type": "integer",
      "nullable": true,
      "description": "Maximum number of hardware IDs that can use this key",
      "example": 1
     }
    },
    "required": [
     "keyId",
     "key",
     "expiresInMinutes",
     "oneTimeUse",
     "expiryOnFirstUse",
     "validityMinutes",
     "hwidLimit"
    ],
    "description": "Response payload"
   },
   "CreateKeysBatchRequest": {
    "type": "object",
    "properties": {
     "provider_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "ID of the provider this key belongs to",
      "example": 1
     },
     "service_id": {
      "type": "integer",
      "nullable": true,
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Optional ID of the service this key is associated with",
      "example": 1
     },
     "folder_id": {
      "type": "integer",
      "nullable": true,
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Optional ID of the folder to organize this key",
      "example": 1
     },
     "minutes": {
      "type": "integer",
      "nullable": true,
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Key validity duration in minutes. Cannot be used with expires_in_days",
      "example": 1440
     },
     "expires_in_days": {
      "type": "integer",
      "nullable": true,
      "minimum": 1,
      "maximum": 36500,
      "description": "Key validity duration in days (1-36500). Cannot be used with minutes",
      "example": 30
     },
     "hwid": {
      "type": "string",
      "nullable": true,
      "maxLength": 512,
      "description": "Hardware ID to bind this key to a specific device. Cannot be used with no_hwid=true",
      "example": "HWID-ABC123-XYZ789"
     },
     "key_type": {
      "type": "string",
      "nullable": true,
      "enum": [
       "free",
       "premium",
       null
      ],
      "description": "Key type classification. Options: free, premium. Must match is_premium value",
      "example": "premium"
     },
     "is_premium": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether this is a premium key. Must match key_type if provided",
      "example": false
     },
     "one_time_use": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether the key can only be used once",
      "example": false
     },
     "expiry_on_first_use": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether the key expiry timer starts on first use instead of creation",
      "example": false
     },
     "no_hwid": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether to disable hardware ID binding. Cannot be used with hwid or hwid_limit",
      "example": false
     },
     "hwid_limit": {
      "type": "integer",
      "nullable": true,
      "minimum": 1,
      "description": "Maximum number of hardware IDs that can use this key. Cannot be used with no_hwid=true",
      "example": 1
     },
     "count": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "description": "Number of keys to generate in batch (1-100)",
      "example": 10
     }
    },
    "required": [
     "provider_id",
     "count"
    ]
   },
   "CreateProviderRequest": {
    "type": "object",
    "properties": {
     "service_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the resource belongs to",
      "example": 1
     },
     "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Display name for the provider shown in admin and user flows",
      "example": "My Discord Server"
     },
     "key_valid_minutes": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "default": 60,
      "description": "Default lifetime, in minutes, for keys issued through this provider",
      "example": 1440
     },
     "one_time_use": {
      "type": "boolean",
      "default": false,
      "description": "Whether keys issued by this provider can be redeemed only once",
      "example": false
     },
     "expiry_on_first_use": {
      "type": "boolean",
      "default": false,
      "description": "Whether the key lifetime starts on first use instead of at creation time",
      "example": false
     },
     "hwid_limit": {
      "type": "integer",
      "minimum": 1,
      "default": 1,
      "description": "Maximum number of hardware IDs that can be bound to a key from this provider",
      "example": 1
     },
     "checkpoints": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/Checkpoint"
      },
      "maxItems": 10,
      "default": [],
      "description": "Ordered checkpoint steps for this provider (max 10)",
      "example": [
       {
        "revenue_id": "990e8400-e29b-41d4-a716-446655440004",
        "meta": {
         "threshold": 100
        }
       }
      ]
     }
    },
    "required": [
     "service_id",
     "name"
    ]
   },
   "CreateRevenueRequest": {
    "type": "object",
    "properties": {
     "service_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the resource belongs to",
      "example": 1
     },
     "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Revenue name for identification",
      "example": "My Linkvertise Revenue"
     },
     "type": {
      "type": "string",
      "enum": [
       "linkvertise-dynamic",
       "linkvertise-token",
       "work.ink",
       "lootlabs",
       "boostellar",
       "shrtfly",
       "cutyio",
       "shortjambo",
       "shrinkearn",
       "linkify",
       "flyinc",
       "linkunlocker",
       "santilink"
      ],
      "description": "Revenue type. Options: linkvertise-dynamic, linkvertise-token, work.ink, lootlabs, boostellar, shrtfly, cutyio, shortjambo, shrinkearn, linkify, flyinc, linkunlocker, santilink",
      "example": "linkvertise-dynamic"
     },
     "config": {
      "type": "object",
      "additionalProperties": {
       "nullable": true
      },
      "description": "Revenue-specific configuration object. Structure varies by revenue type",
      "example": {
       "api_key": "your-api-key",
       "user_id": "12345"
      }
     }
    },
    "required": [
     "service_id",
     "name",
     "type",
     "config"
    ]
   },
   "CreateServiceBan": {
    "type": "object",
    "properties": {
     "service_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the resource belongs to",
      "example": 1
     },
     "subject_type": {
      "type": "string",
      "enum": [
       "discord",
       "hwid"
      ],
      "description": "What to ban",
      "example": "discord"
     },
     "subject": {
      "type": "string",
      "minLength": 1,
      "maxLength": 512,
      "description": "Discord user id or HWID",
      "example": "1550213780359610410"
     },
     "reason": {
      "type": "string",
      "nullable": true,
      "maxLength": 256,
      "description": "Shown to your team only"
     },
     "duration_minutes": {
      "type": "integer",
      "nullable": true,
      "minimum": 1,
      "maximum": 525600,
      "description": "Omit for a permanent ban",
      "example": 10080
     }
    },
    "required": [
     "service_id",
     "subject_type",
     "subject"
    ]
   },
   "CreateServiceRequest": {
    "type": "object",
    "properties": {
     "name": {
      "type": "string",
      "minLength": 2,
      "maxLength": 64,
      "description": "Human-readable service name shown in dashboards and public flows",
      "example": "Premium Discord Bot"
     },
     "description": {
      "type": "string",
      "nullable": true,
      "maxLength": 2000,
      "description": "Optional internal description for the service",
      "example": "A premium bot with advanced features"
     },
     "is_premium": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether the service should be treated as a premium offering",
      "example": false
     },
     "keyless_mode": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "default": false,
      "description": "Whether users can access the service without receiving a generated key",
      "example": false
     },
     "keyless_weekdays": {
      "type": "array",
      "items": {
       "type": "integer",
       "minimum": 0,
       "maximum": 6,
       "description": "Weekday number (0=Sunday, 1=Monday, ..., 6=Saturday)",
       "example": 1
      },
      "default": [],
      "description": "Weekdays when keyless mode is active. Use 0 for Sunday through 6 for Saturday. Leave empty when keyless_mode is false.",
      "example": [
       1,
       3,
       5
      ]
     },
     "slug": {
      "type": "string",
      "nullable": true,
      "maxLength": 64,
      "description": "Optional public slug used in end-user claim links and public service pages. Send empty string or null to leave unset.",
      "example": "premium-discord-bot"
     }
    },
    "required": [
     "name"
    ]
   },
   "CreateWebhookRequest": {
    "type": "object",
    "properties": {
     "service_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the resource belongs to",
      "example": 1
     },
     "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "description": "Webhook name for identification",
      "example": "Shopify Premium Orders"
     },
     "store_provider": {
      "type": "string",
      "minLength": 1,
      "maxLength": 32,
      "description": "Store platform identifier (e.g., shopify, woocommerce, stripe)",
      "example": "shopify"
     },
     "action_type": {
      "type": "string",
      "default": "generate_keys",
      "description": "Action to perform when webhook is triggered",
      "example": "generate_keys"
     },
     "action_config": {
      "$ref": "#/components/schemas/WebhookActionConfig"
     },
     "hmac_secret": {
      "type": "string",
      "minLength": 16,
      "maxLength": 512,
      "description": "Secret key for HMAC signature verification (at least 16 characters)",
      "example": "whsec_abc123def456ghi789jkl012mno345pqr678stu901vwx234yz"
     },
     "is_active": {
      "type": "boolean",
      "default": true,
      "description": "Whether the webhook is active and will process incoming requests",
      "example": true
     },
     "custom_hmac_header": {
      "type": "string",
      "nullable": true,
      "maxLength": 128,
      "description": "Custom HTTP header name for HMAC signature (e.g., X-Shopify-Hmac-SHA256)",
      "example": "X-Shopify-Hmac-SHA256"
     },
     "custom_hmac_algorithm": {
      "type": "string",
      "enum": [
       "sha1",
       "sha256",
       "sha512"
      ],
      "default": "sha256",
      "description": "HMAC algorithm for signature verification. Options: sha1, sha256, sha512",
      "example": "sha256"
     },
     "custom_conditions": {
      "type": "object",
      "nullable": true,
      "properties": {
       "operator": {
        "type": "string",
        "enum": [
         "and",
         "or"
        ],
        "default": "and",
        "description": "Logical operator for combining condition groups. Options: and, or",
        "example": "and"
       },
       "groups": {
        "type": "array",
        "items": {
         "$ref": "#/components/schemas/WebhookConditionGroup"
        },
        "default": [],
        "description": "List of condition groups",
        "example": [
         {
          "operator": "and",
          "conditions": [
           {
            "field": "item.product_id",
            "type": "equals",
            "value": "premium_product",
            "case_sensitive": false
           }
          ]
         }
        ]
       }
      },
      "description": "Optional custom conditions for filtering webhook events"
     }
    },
    "required": [
     "service_id",
     "name",
     "store_provider",
     "action_config",
     "hmac_secret"
    ]
   },
   "DiscordKeyHolder": {
    "type": "object",
    "properties": {
     "discord_user_id": {
      "type": "string"
     },
     "ban": {
      "$ref": "#/components/schemas/ServiceBan"
     },
     "profile": {
      "type": "object",
      "nullable": true,
      "properties": {
       "username": {
        "type": "string"
       },
       "global_name": {
        "type": "string",
        "nullable": true
       },
       "avatar_url": {
        "type": "string",
        "nullable": true
       }
      },
      "required": [
       "username",
       "global_name",
       "avatar_url"
      ],
      "description": "Discord account details; null when Discord can't be reached"
     },
     "stats": {
      "type": "object",
      "properties": {
       "keys_total": {
        "type": "integer"
       },
       "keys_active": {
        "type": "integer"
       },
       "hwids": {
        "type": "integer"
       },
       "validations": {
        "type": "integer"
       },
       "first_seen": {
        "type": "string",
        "nullable": true,
        "format": "date-time"
       },
       "last_activity": {
        "type": "string",
        "nullable": true,
        "format": "date-time"
       }
      },
      "required": [
       "keys_total",
       "keys_active",
       "hwids",
       "validations",
       "first_seen",
       "last_activity"
      ]
     },
     "keys": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/KeyListItem"
      },
      "description": "Up to 100 most recent keys"
     },
     "hwids": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "hwid": {
         "type": "string"
        },
        "key_count": {
         "type": "integer"
        },
        "key_values": {
         "type": "array",
         "items": {
          "type": "string"
         }
        },
        "last_bound_at": {
         "type": "string",
         "format": "date-time"
        },
        "ban_id": {
         "type": "string",
         "nullable": true
        }
       },
       "required": [
        "hwid",
        "key_count",
        "key_values",
        "last_bound_at",
        "ban_id"
       ]
      }
     },
     "usage": {
      "type": "object",
      "properties": {
       "executors": {
        "type": "array",
        "items": {
         "type": "object",
         "properties": {
          "name": {
           "type": "string"
          },
          "count": {
           "type": "integer"
          }
         },
         "required": [
          "name",
          "count"
         ]
        }
       },
       "countries": {
        "type": "array",
        "items": {
         "type": "object",
         "properties": {
          "code": {
           "type": "string"
          },
          "count": {
           "type": "integer"
          }
         },
         "required": [
          "code",
          "count"
         ]
        }
       }
      },
      "required": [
       "executors",
       "countries"
      ]
     },
     "activity": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "type": {
         "type": "string",
         "enum": [
          "key_created",
          "key_validated",
          "discord_action",
          "giveaway_claim"
         ]
        },
        "at": {
         "type": "string",
         "format": "date-time"
        },
        "label": {
         "type": "string",
         "nullable": true,
         "description": "Discord panel event (redeem, reset_hwid, …) or giveaway title"
        },
        "outcome": {
         "type": "string",
         "nullable": true
        },
        "detail": {
         "type": "string",
         "nullable": true
        },
        "key_id": {
         "type": "string",
         "nullable": true
        },
        "key_value": {
         "type": "string",
         "nullable": true
        },
        "provider_name": {
         "type": "string",
         "nullable": true
        },
        "executor": {
         "type": "string",
         "nullable": true
        },
        "country": {
         "type": "string",
         "nullable": true
        }
       },
       "required": [
        "type",
        "at",
        "label",
        "outcome",
        "detail",
        "key_id",
        "key_value",
        "provider_name",
        "executor",
        "country"
       ]
      },
      "description": "Merged timeline, newest first (up to 100 entries)"
     }
    },
    "required": [
     "discord_user_id",
     "ban",
     "profile",
     "stats",
     "keys",
     "hwids",
     "usage",
     "activity"
    ],
    "description": "Response payload"
   },
   "DiscordProfiles": {
    "type": "array",
    "items": {
     "type": "object",
     "properties": {
      "id": {
       "type": "string"
      },
      "username": {
       "type": "string"
      },
      "global_name": {
       "type": "string",
       "nullable": true
      },
      "avatar_url": {
       "type": "string",
       "nullable": true
      }
     },
     "required": [
      "id",
      "username",
      "global_name",
      "avatar_url"
     ]
    },
    "description": "Response payload"
   },
   "FolderResponse": {
    "type": "object",
    "properties": {
     "id": {
      "type": "integer",
      "description": "Folder unique identifier",
      "example": 123
     },
     "serviceId": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the folder belongs to"
     },
     "name": {
      "type": "string",
      "description": "Folder name for organizing keys",
      "example": "Production Keys"
     },
     "createdAt": {
      "type": "string",
      "description": "Creation timestamp (ISO 8601)",
      "example": "2026-04-30T15:37:35.645Z"
     }
    },
    "required": [
     "id",
     "serviceId",
     "name",
     "createdAt"
    ],
    "description": "Response payload"
   },
   "KeyFilterOptions": {
    "type": "object",
    "properties": {
     "executors": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "Executors that validated this service's keys in the last 90 days, most used first",
      "example": [
       "Wave",
       "Synapse Z"
      ]
     }
    },
    "required": [
     "executors"
    ],
    "description": "Response payload"
   },
   "KeyListItem": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string",
      "format": "uuid",
      "description": "Key unique identifier",
      "example": "880e8400-e29b-41d4-a716-446655440003"
     },
     "key_value": {
      "type": "string",
      "description": "The actual key value",
      "example": "MYAPP-ABC1-DEF2-GHI3"
     },
     "key_name": {
      "type": "string",
      "nullable": true,
      "description": "Optional custom name for the key",
      "example": "Trial Key for John"
     },
     "status": {
      "type": "string",
      "enum": [
       "active",
       "used",
       "expired",
       "disabled"
      ],
      "description": "Current key status",
      "example": "active"
     },
     "expires_at": {
      "type": "string",
      "nullable": true,
      "format": "date-time",
      "description": "Key expiration timestamp",
      "example": "2026-05-30T15:35:47.850Z"
     },
     "used_at": {
      "type": "string",
      "nullable": true,
      "format": "date-time",
      "description": "Timestamp when key was first used",
      "example": "2026-04-30T15:35:47.850Z"
     },
     "provider_name": {
      "type": "string",
      "nullable": true,
      "description": "Name of the provider this key belongs to",
      "example": "My Application"
     },
     "service_id": {
      "type": "integer",
      "nullable": true,
      "description": "ID of the service this key is associated with",
      "example": 1
     },
     "service_name": {
      "type": "string",
      "nullable": true,
      "description": "Name of the service this key is associated with",
      "example": "Premium Service"
     },
     "folder_name": {
      "type": "string",
      "nullable": true,
      "description": "Name of the folder this key is organized in",
      "example": "Trial Keys"
     },
     "is_premium": {
      "type": "boolean",
      "description": "Whether this is a premium key",
      "example": false
     },
     "one_time_use": {
      "type": "boolean",
      "description": "Whether the key can only be used once",
      "example": false
     },
     "expiry_on_first_use": {
      "type": "boolean",
      "description": "Whether the key expiry timer starts on first use",
      "example": false
     },
     "no_hwid": {
      "type": "boolean",
      "description": "Whether hardware ID binding is disabled",
      "example": false
     },
     "hwid_limit": {
      "type": "integer",
      "nullable": true,
      "description": "Maximum number of hardware IDs that can use this key",
      "example": 1
     },
     "roblox_username": {
      "type": "string",
      "nullable": true,
      "description": "Roblox username associated with this key",
      "example": "RobloxPlayer123"
     },
     "roblox_user_id": {
      "type": "string",
      "nullable": true,
      "description": "Roblox user id associated with this key",
      "example": "156"
     },
     "discord_user_id": {
      "type": "string",
      "nullable": true,
      "description": "Discord user id the key is linked to",
      "example": "1550213780359610410"
     },
     "hwid": {
      "type": "string",
      "nullable": true,
      "description": "Device the key is bound to, if any",
      "example": "a1b2c3d4"
     },
     "folder_id": {
      "type": "integer",
      "nullable": true,
      "description": "Folder the key is organized in, if any",
      "example": 1
     },
     "validity_minutes": {
      "type": "integer",
      "nullable": true,
      "description": "Duration (minutes) granted on activation for expiry_on_first_use keys that haven't started yet",
      "example": 43200
     },
     "is_disabled": {
      "type": "boolean",
      "description": "Whether the key was manually disabled",
      "example": false
     },
     "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "Creation timestamp",
      "example": "2026-04-30T15:35:47.850Z"
     }
    },
    "required": [
     "id",
     "key_value",
     "key_name",
     "status",
     "expires_at",
     "used_at",
     "provider_name",
     "service_id",
     "service_name",
     "folder_name",
     "is_premium",
     "one_time_use",
     "expiry_on_first_use",
     "no_hwid",
     "hwid_limit",
     "roblox_username",
     "roblox_user_id",
     "discord_user_id",
     "hwid",
     "folder_id",
     "validity_minutes",
     "is_disabled",
     "created_at"
    ]
   },
   "KeySelfServiceRequest": {
    "type": "object",
    "properties": {
     "key": {
      "type": "string",
      "minLength": 4,
      "maxLength": 256,
      "description": "The key value",
      "example": "SANTI-ABCD-EFGH-IJKL"
     }
    },
    "required": [
     "key"
    ]
   },
   "KeySelfServiceResetResult": {
    "type": "object",
    "properties": {
     "resets_left": {
      "type": "integer",
      "nullable": true
     },
     "next_reset_at": {
      "type": "string",
      "nullable": true
     }
    },
    "required": [
     "resets_left",
     "next_reset_at"
    ],
    "description": "Response payload"
   },
   "KeySelfServiceStatus": {
    "type": "object",
    "properties": {
     "service_name": {
      "type": "string"
     },
     "status": {
      "type": "string",
      "enum": [
       "active",
       "used",
       "expired",
       "disabled"
      ]
     },
     "expires_at": {
      "type": "string",
      "nullable": true
     },
     "validity_minutes": {
      "type": "integer",
      "nullable": true
     },
     "no_hwid": {
      "type": "boolean"
     },
     "hwid_bound": {
      "type": "boolean"
     },
     "hwid_reset": {
      "type": "object",
      "properties": {
       "enabled": {
        "type": "boolean",
        "description": "The service lets buyers reset the HWID themselves"
       },
       "resets_left": {
        "type": "integer",
        "nullable": true,
        "description": "Resets left; null = unlimited"
       },
       "next_reset_at": {
        "type": "string",
        "nullable": true,
        "description": "When the cooldown ends; null = no cooldown"
       },
       "available": {
        "type": "boolean",
        "description": "A reset would succeed right now"
       }
      },
      "required": [
       "enabled",
       "resets_left",
       "next_reset_at",
       "available"
      ]
     },
     "transfer": {
      "type": "object",
      "properties": {
       "enabled": {
        "type": "boolean",
        "description": "The service lets buyers transfer their key"
       },
       "next_transfer_at": {
        "type": "string",
        "nullable": true,
        "description": "When the cooldown ends; null = no cooldown"
       },
       "available": {
        "type": "boolean",
        "description": "A transfer would succeed right now"
       },
       "rotates": {
        "type": "boolean",
        "description": "A transfer issues a new key value; the old one stops working"
       }
      },
      "required": [
       "enabled",
       "next_transfer_at",
       "available",
       "rotates"
      ]
     }
    },
    "required": [
     "service_name",
     "status",
     "expires_at",
     "validity_minutes",
     "no_hwid",
     "hwid_bound",
     "hwid_reset",
     "transfer"
    ],
    "description": "Response payload"
   },
   "KeySelfServiceTransferResult": {
    "type": "object",
    "properties": {
     "next_transfer_at": {
      "type": "string",
      "nullable": true
     },
     "new_key": {
      "type": "string",
      "description": "The key's new value when the service rotates keys on transfer"
     }
    },
    "required": [
     "next_transfer_at"
    ],
    "description": "Response payload"
   },
   "KeySharingAlert": {
    "type": "object",
    "properties": {
     "id": {
      "type": "integer"
     },
     "key_id": {
      "type": "string"
     },
     "key_value": {
      "type": "string"
     },
     "key_name": {
      "type": "string",
      "nullable": true
     },
     "hwid": {
      "type": "string",
      "nullable": true
     },
     "discord_user_id": {
      "type": "string",
      "nullable": true
     },
     "kind": {
      "type": "string",
      "enum": [
       "many_ips",
       "hwid_mismatch"
      ],
      "description": "many_ips — too many distinct IPs in an hour; hwid_mismatch — repeated tries from other HWIDs"
     },
     "observed": {
      "type": "integer"
     },
     "threshold": {
      "type": "integer"
     },
     "status": {
      "type": "string",
      "enum": [
       "open",
       "resolved"
      ]
     },
     "resolution": {
      "type": "string",
      "nullable": true,
      "enum": [
       "dismissed",
       "revoked",
       "reset_hwid",
       null
      ]
     },
     "resolved_at": {
      "type": "string",
      "nullable": true,
      "format": "date-time"
     },
     "created_at": {
      "type": "string",
      "format": "date-time"
     }
    },
    "required": [
     "id",
     "key_id",
     "key_value",
     "key_name",
     "hwid",
     "discord_user_id",
     "kind",
     "observed",
     "threshold",
     "status",
     "resolution",
     "resolved_at",
     "created_at"
    ]
   },
   "KeyTransferRequest": {
    "allOf": [
     {
      "$ref": "#/components/schemas/KeySelfServiceRequest"
     },
     {
      "type": "object",
      "properties": {
       "discord_user_id": {
        "type": "string",
        "pattern": "^\\d{17,20}$",
        "description": "The new holder's Discord user id; omit to just release the key",
        "example": "1550213780359610410"
       }
      }
     }
    ]
   },
   "ProviderResponse": {
    "type": "object",
    "properties": {
     "serviceId": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the provider belongs to"
     },
     "priority": {
      "type": "integer",
      "description": "Order on the service's key page; lowest first"
     },
     "id": {
      "type": "integer",
      "description": "Provider unique identifier",
      "example": 1
     },
     "name": {
      "type": "string",
      "description": "Provider name displayed to users",
      "example": "My Discord Server"
     },
     "keyValidMinutes": {
      "type": "integer",
      "description": "Default lifetime, in minutes, for keys issued through this provider",
      "example": 1440
     },
     "oneTimeUse": {
      "type": "boolean",
      "description": "Whether keys from this provider can only be used once",
      "example": false
     },
     "expiryOnFirstUse": {
      "type": "boolean",
      "description": "Whether key expiry timer starts on first use instead of creation",
      "example": false
     },
     "hwidLimit": {
      "type": "integer",
      "description": "Maximum number of hardware IDs that can use keys from this provider",
      "example": 1
     },
     "checkpoints": {
      "type": "array",
      "items": {
       "type": "integer"
      },
      "description": "Checkpoint revenue IDs in execution order",
      "example": [
       4
      ]
     },
     "revenue_sequence": {
      "type": "string",
      "description": "Comma-separated checkpoint revenue IDs in execution order",
      "example": "990e8400-e29b-41d4-a716-446655440004"
     },
     "revenue_type": {
      "type": "string",
      "description": "Comma-separated checkpoint revenue types in execution order",
      "example": "discord"
     },
     "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Creation timestamp",
      "example": "2026-04-30T15:36:46.951Z"
     },
     "updatedAt": {
      "type": "string",
      "format": "date-time",
      "description": "Last update timestamp",
      "example": "2026-04-30T15:36:46.951Z"
     }
    },
    "required": [
     "serviceId",
     "priority",
     "id",
     "name",
     "keyValidMinutes",
     "oneTimeUse",
     "expiryOnFirstUse",
     "hwidLimit",
     "checkpoints",
     "revenue_sequence",
     "revenue_type",
     "createdAt",
     "updatedAt"
    ],
    "description": "Response payload"
   },
   "RefundKeysRequest": {
    "type": "object",
    "properties": {
     "service_id": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the resource belongs to",
      "example": 1
     },
     "keys": {
      "type": "array",
      "items": {
       "type": "string",
       "minLength": 1,
       "maxLength": 256
      },
      "minItems": 1,
      "maxItems": 100,
      "description": "Key values delivered for the refunded order (up to 100)",
      "example": [
       "SANTI-ABCD-EFGH-IJKL"
      ]
     },
     "reason": {
      "type": "string",
      "enum": [
       "refund",
       "chargeback"
      ],
      "description": "Why the keys are revoked",
      "example": "chargeback"
     },
     "order_id": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "description": "The store's order id, recorded in the audit log",
      "example": "ord_8f2c1a"
     },
     "ban": {
      "type": "boolean",
      "description": "Also ban the key holders (Discord account and bound HWID) from the service",
      "example": false
     }
    },
    "required": [
     "service_id",
     "keys",
     "reason"
    ]
   },
   "RefundKeysResponse": {
    "type": "object",
    "properties": {
     "revoked": {
      "type": "integer",
      "description": "Keys revoked by this call",
      "example": 1
     },
     "not_found": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "Values that are not keys of this service"
     },
     "banned": {
      "type": "integer",
      "description": "Holders banned (Discord accounts + HWIDs)",
      "example": 0
     }
    },
    "required": [
     "revoked",
     "not_found",
     "banned"
    ],
    "description": "Response payload"
   },
   "ReorderServiceProvidersRequest": {
    "type": "object",
    "properties": {
     "provider_ids": {
      "type": "array",
      "items": {
       "type": "integer",
       "minimum": 0,
       "exclusiveMinimum": true
      },
      "maxItems": 200,
      "description": "Provider ids of this service in their new order (first = shown first). Providers left out keep their relative order after these.",
      "example": [
       1,
       2
      ]
     }
    },
    "required": [
     "provider_ids"
    ]
   },
   "ResolveKeySharingAlertBody": {
    "type": "object",
    "properties": {
     "resolution": {
      "type": "string",
      "enum": [
       "dismissed",
       "revoked",
       "reset_hwid"
      ],
      "description": "dismissed — only close; revoked — revoke the key; reset_hwid — unbind its HWID"
     }
    },
    "required": [
     "resolution"
    ]
   },
   "RevenueResponse": {
    "type": "object",
    "properties": {
     "serviceId": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the revenue belongs to"
     },
     "configMasked": {
      "type": "boolean",
      "description": "True when config values are hidden because the caller is below admin"
     },
     "id": {
      "type": "integer",
      "description": "Revenue unique identifier",
      "example": 4
     },
     "name": {
      "type": "string",
      "description": "Revenue name for identification",
      "example": "My Linkvertise Revenue"
     },
     "type": {
      "type": "string",
      "description": "Revenue type",
      "example": "linkvertise-dynamic"
     },
     "config": {
      "type": "object",
      "additionalProperties": {
       "nullable": true
      },
      "description": "Revenue-specific configuration object",
      "example": {
       "api_key": "your-api-key",
       "user_id": "12345"
      }
     },
     "isActive": {
      "type": "boolean",
      "description": "Whether the revenue is active",
      "example": true
     },
     "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Creation timestamp",
      "example": "2026-04-30T15:37:13.595Z"
     },
     "updatedAt": {
      "type": "string",
      "format": "date-time",
      "description": "Last update timestamp",
      "example": "2026-04-30T15:37:13.595Z"
     }
    },
    "required": [
     "serviceId",
     "configMasked",
     "id",
     "name",
     "type",
     "config",
     "isActive",
     "createdAt",
     "updatedAt"
    ]
   },
   "RevenueTypeResponse": {
    "type": "object",
    "properties": {
     "type": {
      "type": "string",
      "description": "Revenue type identifier",
      "example": "linkvertise-dynamic"
     },
     "supported": {
      "type": "boolean",
      "description": "Whether this revenue type is currently supported",
      "example": true
     },
     "displayName": {
      "type": "string",
      "description": "Human-readable display name",
      "example": "Linkvertise Dynamic"
     }
    },
    "required": [
     "type",
     "supported",
     "displayName"
    ]
   },
   "ServiceBan": {
    "type": "object",
    "nullable": true,
    "properties": {
     "id": {
      "type": "string"
     },
     "subject_type": {
      "type": "string",
      "enum": [
       "discord",
       "hwid"
      ]
     },
     "subject": {
      "type": "string"
     },
     "reason": {
      "type": "string",
      "nullable": true
     },
     "expires_at": {
      "type": "string",
      "nullable": true,
      "format": "date-time",
      "description": "null = permanent"
     },
     "created_at": {
      "type": "string",
      "format": "date-time"
     }
    },
    "required": [
     "id",
     "subject_type",
     "subject",
     "reason",
     "expires_at",
     "created_at"
    ],
    "description": "Active service ban on this account"
   },
   "ServiceResponse": {
    "type": "object",
    "properties": {
     "id": {
      "type": "integer",
      "description": "Service unique identifier",
      "example": 1
     },
     "name": {
      "type": "string",
      "description": "Service name",
      "example": "Premium Discord Bot"
     },
     "description": {
      "type": "string",
      "nullable": true,
      "description": "Service description",
      "example": "A premium bot with advanced features"
     },
     "isPremium": {
      "type": "boolean",
      "description": "Whether the service is marked as premium",
      "example": false
     },
     "keylessMode": {
      "type": "boolean",
      "description": "Whether the service can be accessed without issuing a key",
      "example": false
     },
     "keylessWeekdays": {
      "type": "array",
      "items": {
       "type": "integer"
      },
      "description": "Weekdays when keyless mode is active (0=Sunday, 1=Monday, ..., 6=Saturday)",
      "example": [
       1,
       3,
       5
      ]
     },
     "slug": {
      "type": "string",
      "nullable": true,
      "description": "Public slug used in end-user key claim flows",
      "example": "premium-discord-bot"
     },
     "hwidResetsAllowed": {
      "type": "integer",
      "description": "How many times a key for this service can reset its bound hardware ID",
      "example": 3
     },
     "keysTotal": {
      "type": "integer",
      "description": "Total number of keys for this service",
      "example": 0
     },
     "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Creation timestamp",
      "example": "2026-04-30T15:37:00.604Z"
     },
     "updatedAt": {
      "type": "string",
      "format": "date-time",
      "description": "Last update timestamp",
      "example": "2026-04-30T15:37:00.604Z"
     }
    },
    "required": [
     "id",
     "name",
     "description",
     "isPremium",
     "keylessMode",
     "keylessWeekdays",
     "slug",
     "hwidResetsAllowed",
     "keysTotal",
     "createdAt",
     "updatedAt"
    ],
    "description": "Response payload"
   },
   "TestWebhookRequest": {
    "type": "object",
    "properties": {
     "payload": {
      "type": "object",
      "additionalProperties": {
       "nullable": true
      },
      "description": "JSON payload to send through the pipeline. The dashboard pre-fills a sensible default that hits the configured `quantity_field`.",
      "example": {
       "item": {
        "product_id": "test-product",
        "quantity": 1
       }
      }
     }
    },
    "required": [
     "payload"
    ]
   },
   "TestWebhookResult": {
    "type": "object",
    "properties": {
     "status": {
      "type": "number",
      "description": "Final status from the webhook execution pipeline (200, 204, 400, 404 or 500)",
      "example": 200
     },
     "reason": {
      "type": "string",
      "nullable": true,
      "description": "Reason / error message when status is not 200",
      "example": null
     },
     "keysGenerated": {
      "type": "number",
      "description": "Number of keys generated when status is 200, otherwise 0",
      "example": 1
     },
     "keys": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "Generated API keys when the action ran successfully",
      "example": [
       "key_abc123"
      ]
     },
     "responseBody": {
      "type": "string",
      "description": "Raw response body the public endpoint would have returned",
      "example": "key_abc123"
     },
     "processingTimeMs": {
      "type": "number",
      "description": "End-to-end execution time in milliseconds",
      "example": 142
     },
     "sentPayload": {
      "type": "string",
      "description": "Stringified payload that was sent through the pipeline",
      "example": "{\"test\":true,\"item\":{\"quantity\":1}}"
     },
     "signatureHeader": {
      "type": "string",
      "description": "Header that carried the HMAC signature in the simulated request",
      "example": "X-Signature"
     },
     "signature": {
      "type": "string",
      "description": "Hex-encoded HMAC signature computed for the test payload",
      "example": "8b6c…"
     },
     "signatureAlgorithm": {
      "type": "string",
      "description": "HMAC algorithm used to compute the signature",
      "example": "sha256"
     }
    },
    "required": [
     "status",
     "reason",
     "keysGenerated",
     "keys",
     "responseBody",
     "processingTimeMs",
     "sentPayload",
     "signatureHeader",
     "signature",
     "signatureAlgorithm"
    ]
   },
   "UpdateFolderRequest": {
    "type": "object",
    "properties": {
     "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 32,
      "description": "Updated folder name",
      "example": "Staging Keys"
     }
    }
   },
   "UpdateKeyBody": {
    "type": "object",
    "properties": {
     "key_name": {
      "type": "string",
      "nullable": true,
      "maxLength": 64,
      "description": "Custom name for the key. Send null to clear, omit to leave unchanged.",
      "example": "Trial Key for John"
     },
     "is_premium": {
      "type": "boolean",
      "description": "Whether the key counts as a premium-tier key",
      "example": false
     },
     "one_time_use": {
      "type": "boolean",
      "description": "Mark the key as single-use after first successful validation. Mutually exclusive with expiry_on_first_use.",
      "example": false
     },
     "expiry_on_first_use": {
      "type": "boolean",
      "description": "Start the validity countdown from the first successful validation. Mutually exclusive with one_time_use.",
      "example": false
     },
     "no_hwid": {
      "type": "boolean",
      "description": "Disable HWID locking. When set, hwid_limit is cleared.",
      "example": false
     },
     "hwid_limit": {
      "type": "integer",
      "nullable": true,
      "minimum": 1,
      "description": "Maximum number of distinct HWIDs that can use this key. Ignored when no_hwid is true.",
      "example": 1
     },
     "folder_id": {
      "type": "integer",
      "nullable": true,
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Move the key into a folder of the same service. Send null to remove it from its folder.",
      "example": 1
     },
     "discord_user_id": {
      "type": "string",
      "nullable": true,
      "pattern": "^\\d{17,20}$",
      "description": "Link the key to a Discord user. Send null to unlink.",
      "example": "1550213780359610410"
     },
     "expires_at": {
      "type": "string",
      "nullable": true,
      "format": "date-time",
      "description": "Absolute expiry timestamp. Send null for a key that never expires. For expiry_on_first_use keys that haven't started, use validity_minutes instead.",
      "example": "2026-12-31T23:59:00.000Z"
     },
     "validity_minutes": {
      "type": "integer",
      "nullable": true,
      "minimum": 1,
      "maximum": 52560000,
      "description": "Duration granted on activation for expiry_on_first_use keys that haven't started. Send null for lifetime.",
      "example": 43200
     },
     "disabled": {
      "type": "boolean",
      "description": "Disable (true) or re-enable (false) the key.",
      "example": false
     },
     "reset_hwid": {
      "type": "boolean",
      "enum": [
       true
      ],
      "description": "Unbind the key from its current device.",
      "example": true
     },
     "reset_roblox": {
      "type": "boolean",
      "enum": [
       true
      ],
      "description": "Unlink the Roblox account bound to the key.",
      "example": true
     },
     "reset_usage": {
      "type": "boolean",
      "enum": [
       true
      ],
      "description": "Clear the used-at mark so a one-time key can be redeemed again.",
      "example": true
     }
    }
   },
   "UpdateProviderRequest": {
    "type": "object",
    "properties": {
     "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Updated display name for the provider",
      "example": "My Discord Server"
     },
     "key_valid_minutes": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Updated default lifetime, in minutes, for keys issued through this provider",
      "example": 1440
     },
     "one_time_use": {
      "type": "boolean",
      "description": "Whether keys issued by this provider can be redeemed only once",
      "example": false
     },
     "expiry_on_first_use": {
      "type": "boolean",
      "description": "Whether the key lifetime starts on first use instead of at creation time",
      "example": false
     },
     "hwid_limit": {
      "type": "integer",
      "minimum": 1,
      "description": "Maximum number of hardware IDs that can be bound to a key from this provider",
      "example": 1
     },
     "checkpoints": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/Checkpoint"
      },
      "maxItems": 10,
      "description": "Updated ordered checkpoint steps for this provider (max 10)",
      "example": [
       {
        "revenue_id": "990e8400-e29b-41d4-a716-446655440004",
        "meta": {
         "threshold": 100
        }
       }
      ]
     }
    },
    "required": [
     "checkpoints"
    ]
   },
   "UpdateRevenueRequest": {
    "type": "object",
    "properties": {
     "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Revenue name for identification",
      "example": "My Linkvertise Revenue"
     },
     "config": {
      "type": "object",
      "additionalProperties": {
       "nullable": true
      },
      "description": "Revenue-specific configuration object. Structure varies by revenue type",
      "example": {
       "api_key": "your-api-key",
       "user_id": "12345"
      }
     },
     "isActive": {
      "type": "boolean",
      "description": "Whether the revenue is active",
      "example": true
     }
    }
   },
   "UpdateServiceRequest": {
    "type": "object",
    "properties": {
     "name": {
      "type": "string",
      "minLength": 2,
      "maxLength": 64,
      "description": "Updated service name shown in dashboards and public flows",
      "example": "Premium Discord Bot"
     },
     "description": {
      "type": "string",
      "nullable": true,
      "maxLength": 2000,
      "description": "Updated internal description for the service",
      "example": "A premium bot with advanced features"
     },
     "is_premium": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "description": "Whether the service should be treated as a premium offering",
      "example": false
     },
     "keyless_mode": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "number",
        "enum": [
         0
        ]
       },
       {
        "type": "number",
        "enum": [
         1
        ]
       }
      ],
      "description": "Whether users can access the service without receiving a generated key",
      "example": false
     },
     "keyless_weekdays": {
      "type": "array",
      "items": {
       "type": "integer",
       "minimum": 0,
       "maximum": 6,
       "description": "Weekday number (0=Sunday, 1=Monday, ..., 6=Saturday)",
       "example": 1
      },
      "description": "Weekdays when keyless mode is active. Use 0 for Sunday through 6 for Saturday. Leave empty when keyless_mode is false.",
      "example": [
       1,
       3,
       5
      ]
     },
     "slug": {
      "type": "string",
      "nullable": true,
      "maxLength": 64,
      "description": "Optional public slug used in end-user claim links and public service pages",
      "example": "premium-discord-bot"
     },
     "hwid_resets_allowed": {
      "type": "integer",
      "minimum": 0,
      "description": "How many times a key for this service can reset its bound hardware ID",
      "example": 3
     }
    }
   },
   "UpdateWebhookRequest": {
    "type": "object",
    "properties": {
     "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "description": "Webhook name for identification",
      "example": "Shopify Premium Orders"
     },
     "store_provider": {
      "type": "string",
      "minLength": 1,
      "maxLength": 32,
      "description": "Store platform identifier (e.g., shopify, woocommerce, stripe)",
      "example": "shopify"
     },
     "action_type": {
      "type": "string",
      "default": "generate_keys",
      "description": "Action to perform when webhook is triggered",
      "example": "generate_keys"
     },
     "action_config": {
      "$ref": "#/components/schemas/WebhookActionConfig"
     },
     "hmac_secret": {
      "type": "string",
      "minLength": 16,
      "maxLength": 512,
      "description": "Secret key for HMAC signature verification (at least 16 characters)",
      "example": "whsec_abc123def456ghi789jkl012mno345pqr678stu901vwx234yz"
     },
     "is_active": {
      "type": "boolean",
      "default": true,
      "description": "Whether the webhook is active and will process incoming requests",
      "example": true
     },
     "custom_hmac_header": {
      "type": "string",
      "nullable": true,
      "maxLength": 128,
      "description": "Custom HTTP header name for HMAC signature (e.g., X-Shopify-Hmac-SHA256)",
      "example": "X-Shopify-Hmac-SHA256"
     },
     "custom_hmac_algorithm": {
      "type": "string",
      "enum": [
       "sha1",
       "sha256",
       "sha512"
      ],
      "default": "sha256",
      "description": "HMAC algorithm for signature verification. Options: sha1, sha256, sha512",
      "example": "sha256"
     },
     "custom_conditions": {
      "type": "object",
      "nullable": true,
      "properties": {
       "operator": {
        "type": "string",
        "enum": [
         "and",
         "or"
        ],
        "default": "and",
        "description": "Logical operator for combining condition groups. Options: and, or",
        "example": "and"
       },
       "groups": {
        "type": "array",
        "items": {
         "$ref": "#/components/schemas/WebhookConditionGroup"
        },
        "default": [],
        "description": "List of condition groups",
        "example": [
         {
          "operator": "and",
          "conditions": [
           {
            "field": "item.product_id",
            "type": "equals",
            "value": "premium_product",
            "case_sensitive": false
           }
          ]
         }
        ]
       }
      },
      "description": "Optional custom conditions for filtering webhook events"
     }
    },
    "required": [
     "name",
     "store_provider",
     "action_config",
     "hmac_secret"
    ]
   },
   "WebhookActionConfig": {
    "type": "object",
    "properties": {
     "provider_id": {
      "type": "integer",
      "nullable": true,
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "ID of one of the webhook service's providers to issue keys from. When omitted, keys come from the service's highest-priority provider",
      "example": 123
     },
     "validity_hours": {
      "type": "number",
      "nullable": true,
      "minimum": 1,
      "maximum": 8760,
      "description": "Key validity duration in hours (1-8760). Null for unlimited",
      "example": 720
     },
     "unlimited_validity": {
      "type": "boolean",
      "default": false,
      "description": "Whether keys have unlimited validity",
      "example": false
     },
     "one_time_use": {
      "type": "boolean",
      "default": false,
      "description": "Whether keys can only be used once",
      "example": false
     },
     "expiry_on_first_use": {
      "type": "boolean",
      "default": false,
      "description": "Whether keys expire immediately after first use",
      "example": false
     },
     "is_premium": {
      "type": "boolean",
      "default": false,
      "description": "Whether generated keys are marked as premium",
      "example": true
     },
     "quantity_field": {
      "type": "string",
      "minLength": 1,
      "default": "item.quantity",
      "description": "JSON path to quantity field in webhook payload",
      "example": "item.quantity"
     },
     "hwid_limit": {
      "type": "number",
      "nullable": true,
      "minimum": 1,
      "maximum": 100,
      "description": "Maximum number of hardware IDs that can use each key (1-100). Null for unlimited",
      "example": 3
     },
     "order_id_field": {
      "type": "string",
      "nullable": true,
      "maxLength": 255,
      "description": "JSON path to the store's order id. When it resolves, issued keys are linked to the order so a refund delivery can revoke them",
      "example": "id"
     },
     "refund_conditions": {
      "type": "object",
      "nullable": true,
      "properties": {
       "operator": {
        "type": "string",
        "enum": [
         "and",
         "or"
        ],
        "default": "and",
        "description": "Logical operator for combining condition groups. Options: and, or",
        "example": "and"
       },
       "groups": {
        "type": "array",
        "items": {
         "$ref": "#/components/schemas/WebhookConditionGroup"
        },
        "default": [],
        "description": "List of condition groups",
        "example": [
         {
          "operator": "and",
          "conditions": [
           {
            "field": "item.product_id",
            "type": "equals",
            "value": "premium_product",
            "case_sensitive": false
           }
          ]
         }
        ]
       }
      },
      "description": "Conditions that mark a delivery as a refund/chargeback. A matching delivery revokes the keys of its order (needs order_id_field) instead of issuing keys"
     },
     "refund_ban": {
      "type": "boolean",
      "default": false,
      "description": "On refund, also ban the holders (Discord account and bound HWID) of the revoked keys",
      "example": false
     }
    },
    "description": "Configuration for the webhook action"
   },
   "WebhookCondition": {
    "type": "object",
    "properties": {
     "field": {
      "type": "string",
      "default": "",
      "description": "JSON path to the field to evaluate in webhook payload",
      "example": "item.product_id"
     },
     "type": {
      "type": "string",
      "enum": [
       "contains",
       "equals",
       "regex",
       "exists",
       "greater_than",
       "less_than"
      ],
      "default": "contains",
      "description": "Condition type. Options: contains, equals, regex, exists, greater_than, less_than",
      "example": "equals"
     },
     "value": {
      "type": "string",
      "default": "",
      "description": "Value to compare against (not used for 'exists' type)",
      "example": "premium_product"
     },
     "case_sensitive": {
      "type": "boolean",
      "default": false,
      "description": "Whether string comparisons should be case-sensitive",
      "example": false
     }
    }
   },
   "WebhookConditionGroup": {
    "type": "object",
    "properties": {
     "operator": {
      "type": "string",
      "enum": [
       "and",
       "or"
      ],
      "default": "and",
      "description": "Logical operator for combining conditions. Options: and, or",
      "example": "and"
     },
     "conditions": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/WebhookCondition"
      },
      "default": [],
      "description": "List of conditions in this group",
      "example": [
       {
        "field": "item.product_id",
        "type": "equals",
        "value": "premium_product",
        "case_sensitive": false
       },
       {
        "field": "item.quantity",
        "type": "greater_than",
        "value": "0",
        "case_sensitive": false
       }
      ]
     }
    }
   },
   "WebhookCustomConditions": {
    "type": "object",
    "nullable": true,
    "properties": {
     "operator": {
      "type": "string",
      "enum": [
       "and",
       "or"
      ],
      "default": "and",
      "description": "Logical operator for combining condition groups. Options: and, or",
      "example": "and"
     },
     "groups": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/WebhookConditionGroup"
      },
      "default": [],
      "description": "List of condition groups",
      "example": [
       {
        "operator": "and",
        "conditions": [
         {
          "field": "item.product_id",
          "type": "equals",
          "value": "premium_product",
          "case_sensitive": false
         }
        ]
       }
      ]
     }
    },
    "description": "Custom conditions for filtering webhook events"
   },
   "WebhookListItem": {
    "allOf": [
     {
      "$ref": "#/components/schemas/WebhookResponse"
     },
     {
      "type": "object",
      "properties": {
       "lastLog": {
        "$ref": "#/components/schemas/WebhookLogResponse"
       }
      },
      "required": [
       "lastLog"
      ]
     }
    ]
   },
   "WebhookLogResponse": {
    "type": "object",
    "nullable": true,
    "properties": {
     "id": {
      "type": "number",
      "description": "Log entry identifier",
      "example": 101
     },
     "webhookId": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Webhook this log belongs to",
      "example": 123
     },
     "responseStatus": {
      "type": "number",
      "description": "HTTP-style status the pipeline produced (200, 204, 400, 404, 500)",
      "example": 200
     },
     "requestBody": {
      "type": "string",
      "description": "Raw payload that was processed",
      "example": "{\"item\":{\"product_id\":\"premium_product\",\"quantity\":2}}"
     },
     "responseBody": {
      "type": "string",
      "description": "Body the public endpoint returned to the caller",
      "example": "{\"success\":true}"
     },
     "errorMessage": {
      "type": "string",
      "nullable": true,
      "description": "Error message if the delivery did not complete cleanly",
      "example": null
     },
     "processingTimeMs": {
      "type": "number",
      "description": "End-to-end processing time in milliseconds",
      "example": 184
     },
     "retryCount": {
      "type": "number",
      "description": "Number of retry attempts that have been made",
      "example": 0
     },
     "nextRetryAt": {
      "type": "string",
      "nullable": true,
      "description": "ISO timestamp of the next scheduled retry, when applicable",
      "example": null
     },
     "status": {
      "type": "string",
      "enum": [
       "done",
       "pending_retry",
       "failed"
      ],
      "description": "Lifecycle state of this delivery attempt",
      "example": "done"
     },
     "createdAt": {
      "type": "string",
      "description": "Creation timestamp",
      "example": "2026-04-30T15:40:00.000Z"
     }
    },
    "required": [
     "id",
     "webhookId",
     "responseStatus",
     "requestBody",
     "responseBody",
     "errorMessage",
     "processingTimeMs",
     "retryCount",
     "nextRetryAt",
     "status",
     "createdAt"
    ],
    "description": "Most recent delivery log for this webhook, or null when no deliveries have been recorded yet."
   },
   "WebhookResponse": {
    "type": "object",
    "properties": {
     "id": {
      "type": "integer",
      "description": "Webhook unique identifier",
      "example": 123
     },
     "serviceId": {
      "type": "integer",
      "minimum": 0,
      "exclusiveMinimum": true,
      "description": "Service the webhook issues keys for",
      "example": "660e8400-e29b-41d4-a716-446655440001"
     },
     "name": {
      "type": "string",
      "description": "Webhook name for identification",
      "example": "Shopify Premium Orders"
     },
     "storeProvider": {
      "type": "string",
      "description": "Store platform identifier",
      "example": "shopify"
     },
     "webhookGuid": {
      "type": "string",
      "format": "uuid",
      "description": "Webhook GUID for public endpoint access",
      "example": "550e8400-e29b-41d4-a716-446655440000"
     },
     "actionType": {
      "type": "string",
      "description": "Action to perform when webhook is triggered",
      "example": "generate_keys"
     },
     "actionConfig": {
      "$ref": "#/components/schemas/WebhookActionConfig"
     },
     "hmacSecret": {
      "type": "string",
      "description": "Secret key for HMAC signature verification",
      "example": "whsec_abc123def456ghi789jkl012mno345pqr678stu901vwx234yz"
     },
     "isActive": {
      "type": "boolean",
      "description": "Whether the webhook is active",
      "example": true
     },
     "customHmacHeader": {
      "type": "string",
      "nullable": true,
      "description": "Custom HTTP header name for HMAC signature",
      "example": "X-Shopify-Hmac-SHA256"
     },
     "customHmacAlgorithm": {
      "type": "string",
      "description": "HMAC algorithm for signature verification",
      "example": "sha256"
     },
     "customConditions": {
      "$ref": "#/components/schemas/WebhookCustomConditions"
     },
     "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "Creation timestamp",
      "example": "2026-04-30T15:37:22.511Z"
     },
     "updatedAt": {
      "type": "string",
      "format": "date-time",
      "description": "Last update timestamp",
      "example": "2026-04-30T15:37:22.511Z"
     }
    },
    "required": [
     "id",
     "serviceId",
     "name",
     "storeProvider",
     "webhookGuid",
     "actionType",
     "actionConfig",
     "hmacSecret",
     "isActive",
     "customHmacHeader",
     "customHmacAlgorithm",
     "customConditions",
     "createdAt",
     "updatedAt"
    ]
   }
  }
 }
}
