{
  "openapi": "3.0.3",
  "info": {
    "title": "Purchase Code Generation API — Firma Digital",
    "version": "1.0.0",
    "description": "Lets a partner generate its own purchase codes without manual intervention, and check their status. Every purchase code is born blank (no flow assigned yet): redeem behavior (full or reduced flow) is decided by the partner's configuration at redeem time, not at generation time — see the Partner API.\n\nThis is v1: the version lives in the URL (`/api/partner/v1`). A future v2 would coexist as a separate section, without breaking this one."
  },
  "servers": [
    { "url": "https://partner-pcode-dev.tsp.workers.dev/api/partner/v1", "description": "Development" },
    { "url": "https://partner-pcode.tsp.workers.dev/api/partner/v1", "description": "Production" }
  ],
  "components": {
    "securitySchemes": {
      "PartnerApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-gw",
        "description": "Partner API Key."
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": false },
          "error": { "type": "string" }
        }
      },
      "PCode": {
        "type": "object",
        "properties": {
          "pcode": { "type": "string", "example": "MYPARTNER-26-FV01-A1B2C3D4" },
          "partnerName": { "type": "string" },
          "category": { "type": "integer", "example": 0 },
          "status": {
            "type": "string",
            "enum": ["sin_utilizar", "en_onboarding", "emitido", "bloqueado", "expirado"],
            "description": "Derived on every read, not a stored column. expirado: the 14-day window to start using it expired — unrelated to the validity of an already-issued certificate."
          },
          "processing": { "type": "integer" },
          "batch_id": { "type": "string", "nullable": true },
          "external_reference_id": { "type": "string", "nullable": true },
          "expires_at": { "type": "string", "format": "date-time", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      }
    }
  },
  "security": [{ "PartnerApiKey": [] }],
  "paths": {
    "/pcode": {
      "post": {
        "summary": "Generate a purchase code",
        "description": "If `external_reference_id` is sent and a still-valid purchase code (unused, not blocked, within its 14-day window) already exists for that same reference, that SAME purchase code is returned (`reused: true`) instead of creating a new one — unless `force_new: true` is passed, which also voids any previous purchase code under that reference.",
        "operationId": "createPCode",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["productCode"],
                "properties": {
                  "productCode": { "type": "string", "enum": ["FV00", "FV01", "FV02", "FV03"], "description": "FV00=6 months, FV01=1 year, FV02=2 years, FV03=3 years." },
                  "external_reference_id": { "type": "string", "description": "The partner's own identifier, for idempotency." },
                  "force_new": { "type": "boolean", "default": false }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Purchase code created (or reused — see `data.reused`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "example": true },
                    "data": {
                      "type": "object",
                      "properties": {
                        "pCode": { "type": "string" },
                        "productCode": { "type": "string" },
                        "status": { "type": "string", "example": "ACTIVE" },
                        "reused": { "type": "boolean" },
                        "createdAt": { "type": "string", "format": "date-time" }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid/missing productCode, or the partner doesn't have that product enabled (`partner_config.allowed_products` — if that column is NULL or no config row exists, there's no restriction and any valid productCode passes).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "401": { "description": "Invalid API Key." },
          "429": { "description": "Rate limit exceeded (60 requests/60s per partner)." }
        }
      }
    },
    "/pcode/bulk": {
      "post": {
        "summary": "Generate purchase codes in bulk",
        "description": "Up to 500 per request. All of them share the same `batchId`, useful for reviewing them later as a group with `GET /pcodes?batch_id=`.",
        "operationId": "createBulkPCodes",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["productCode", "quantity"],
                "properties": {
                  "productCode": { "type": "string", "enum": ["FV00", "FV01", "FV02", "FV03"] },
                  "quantity": { "type": "integer", "minimum": 1, "maximum": 500 }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Batch created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "example": true },
                    "data": {
                      "type": "object",
                      "properties": {
                        "batchId": { "type": "string" },
                        "pcodes": { "type": "array", "items": { "type": "string" } }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "description": "Invalid productCode or quantity, or the partner doesn't have that product enabled (`partner_config.allowed_products`)." },
          "401": { "description": "Invalid API Key." },
          "429": { "description": "Rate limit exceeded." }
        }
      }
    },
    "/pcodes": {
      "get": {
        "summary": "List the partner's purchase codes",
        "operationId": "listPCodes",
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["sin_utilizar", "en_onboarding", "emitido", "bloqueado", "expirado"] } },
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 100 } },
          { "name": "created_from", "in": "query", "schema": { "type": "string" }, "description": "UTC date/time (`YYYY-MM-DD` or full timestamp)." },
          { "name": "created_to", "in": "query", "schema": { "type": "string" }, "description": "UTC date/time. A bare date includes the whole day." },
          { "name": "external_reference_id", "in": "query", "schema": { "type": "string" } },
          { "name": "batch_id", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "example": true },
                    "data": {
                      "type": "object",
                      "properties": {
                        "pcodes": { "type": "array", "items": { "$ref": "#/components/schemas/PCode" } },
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": { "type": "integer" },
                            "page": { "type": "integer" },
                            "limit": { "type": "integer" }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "description": "Invalid API Key." }
        }
      }
    }
  }
}
