{
  "openapi": "3.1.0",
  "info": {
    "title": "PropFirmConnector License and Download API",
    "version": "1.1.0",
    "description": "Server-to-server license management and addon downloads for approved prop firms. Use one production Vendor Admin key with the permissions required by each endpoint. Keep the key only in your server secret manager; never expose it in browser code, customer URLs, analytics, or logs. The credential determines the vendor and environment. Request bodies and query strings cannot select either.\n\nOnboarding determines whether your integration uses monthly/lifetime resale access or sponsored access, which plans are allowed, and applicable limits. A key cannot switch its approved mode by changing a request. Resale requests require plan on every PUT; sponsored-access requests must omit plan. Lifetime access is available only when explicitly approved. Commercial terms are agreed separately.\n\nExamples are illustrative and contain no usable credentials or download links. There is no public sandbox. Develop against local mocked responses and coordinate the first live activation; production requests create real licenses and applicable charges. See [launch requirements](https://propfirmconnector.com/vendor-api/licensing/#launch)."
  },
  "servers": [
    {
      "url": "https://propfirmconnector.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Integration guide",
    "url": "https://propfirmconnector.com/vendor-api/licensing/"
  },
  "security": [
    {
      "VendorBearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Licenses",
      "description": "One vendor-issued license slot per stable customer identity."
    },
    {
      "name": "Downloads",
      "description": "Short-lived links to the currently promoted production addon."
    },
    {
      "name": "Audit log",
      "description": "Read your organization’s own audit log."
    },
    {
      "name": "Webhooks",
      "description": "Optional signed events for license changes your server did not make."
    }
  ],
  "paths": {
    "/vendor/v1/sponsored-licenses/{external_customer_id}": {
      "parameters": [
        {
          "name": "external_customer_id",
          "in": "path",
          "required": true,
          "description": "Your stable customer identifier. Reuse it across monthly changes, reactivation, and a lifetime upgrade. Do not use an email address or a per-purchase identifier. Case-insensitive; responses return it in lowercase. No query parameters are accepted.",
          "schema": {
            "$ref": "#/components/schemas/ExternalCustomerId"
          },
          "example": "customer_7b8a1f3d_42e6_4cb9_a2f1"
        }
      ],
      "put": {
        "tags": [
          "Licenses"
        ],
        "operationId": "setCustomerLicense",
        "summary": "Enable, disable, or upgrade a customer license",
        "description": "Requires sponsored_licenses:write and approved direct license access. Your configured mode determines the request shape: monthly/lifetime resale access requires plan on every PUT; sponsored access rejects plan and uses the approved sponsorship automatically. Use only the mode and plans enabled during onboarding.\n\nFor a new email, PropFirmConnector creates an account; for an existing account it adds a separate license slot and preserves independently purchased licenses. You decide which email holds a monthly or sponsored license: a higher revision with a different email moves it. previous_email is an optional guard. The same request may also change active, but not plan. Responses set email_changed=true on the revision that moved it. The license, its revision history and its billing move to the account for the new email, which is created if needed. The previous account loses only this license and its email may be provisioned again under another customer ID. Nothing is charged. The customer signs in with the new email and activates their device again. There is one slot per vendor and customer account. A new customer identifier for an email whose license from you is active is refused with SPONSORED_LICENSE_EMAIL_IN_USE before anything is recorded. If that license is cancelled, the new identifier takes the account over and is billed as a new license; reuse the original identifier to keep paid coverage. Monthly and sponsored access stay enabled until explicitly disabled with active=false and a higher revision. Approved lifetime access permits active=true only and displays as ordinary Lifetime access. An existing monthly slot may be upgraded to lifetime using a higher revision, if approved; this converts the slot instead of adding another slot. Lifetime cannot be disabled, changed back to monthly, or moved to another email. This API does not send an onboarding email or return a license key.\n\nSend Content-Type: application/json (optionally charset=utf-8). The body must be JSON, at most 4096 UTF-8 bytes, with no extra fields or query parameters. Retain each outgoing change durably with one idempotency key. After a timeout or transient failure, retry the exact same body, revision, and key. A successful retry returns current state. A stale update normally returns current state with stale=true, but any attempt to disable, downgrade or re-email lifetime is rejected. For status=pending, inspect provisioning.state and provisioning.retryable. A blocked result requires operator intervention: stop automatic retries and preserve the original customer ID and outgoing job. For a retryable pending result, retry the original PUT with the same body, revision and idempotency key using bounded backoff. GET is read-only and cannot finish provisioning. active is desired state, not evidence of a completed grant. Direct-license changes do not emit lifecycle webhooks. Reconcile using durable outgoing jobs and GET; coupon webhooks do not report these changes. See [retry rules](https://propfirmconnector.com/vendor-api/licensing/#retries).\n\nMonthly resale accrues a charge every 30 days while enabled, starting with successful activation; no renewal PUT is required. Disabling stops future renewals without a prorated credit. Reactivation keeps unexpired paid coverage and its renewal date; after expiry it starts a new paid period, without billing disabled time. Lifetime is charged once at the agreed lifetime rate. An upgrade charges that full agreed lifetime rate and stops monthly renewals, without automatic credit for past monthly charges. Reads, downloads and safe retries have no separate license charge. Rates and sponsored-access terms are agreed during onboarding. See [billing rules](https://propfirmconnector.com/vendor-api/licensing/#billing).\n\nCurrent default limits: 30 writes per vendor per fixed minute, shared across its keys, plus 100 first-activation reservations per UTC day. An email change uses one only when the new email needs a new account. Failed account creation consumes its reservation; retries and later reactivations reuse it. Confirm limits during onboarding and honor Retry-After.\n\nEvery response includes X-Correlation-ID, a server-generated UUID for support and request tracing. Retain it with the HTTP status and error code; never log bearer secrets or signed download URLs.",
        "x-required-scopes": [
          "sponsored_licenses:write"
        ],
        "x-max-request-bytes": 4096,
        "x-rate-limits": [
          {
            "scope": "vendor",
            "requests": 30,
            "windowSeconds": 60
          },
          {
            "scope": "vendor-first-activation-reservations",
            "requests": 100,
            "window": "UTC calendar day"
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LicenseWriteRequest"
              },
              "examples": {
                "enableMonthly": {
                  "summary": "Enable monthly access (approved resale mode example)",
                  "value": {
                    "email": "customer@example.com",
                    "plan": "monthly",
                    "active": true,
                    "revision": 1
                  }
                },
                "disableMonthly": {
                  "summary": "Disable monthly access (approved resale mode example)",
                  "value": {
                    "email": "customer@example.com",
                    "plan": "monthly",
                    "active": false,
                    "revision": 2
                  }
                },
                "changeMonthlyEmail": {
                  "summary": "Change a monthly customer's email (approved resale mode example)",
                  "value": {
                    "email": "new-address@example.com",
                    "plan": "monthly",
                    "active": true,
                    "revision": 3
                  }
                },
                "changeMonthlyEmailGuarded": {
                  "summary": "Change a monthly customer's email only if the email on record is the expected one",
                  "value": {
                    "email": "new-address@example.com",
                    "previous_email": "customer@example.com",
                    "plan": "monthly",
                    "active": true,
                    "revision": 3
                  }
                },
                "enableLifetime": {
                  "summary": "Enable lifetime access (approved resale mode example)",
                  "value": {
                    "email": "customer@example.com",
                    "plan": "lifetime",
                    "active": true,
                    "revision": 1
                  }
                },
                "upgradeToLifetime": {
                  "summary": "Upgrade an existing monthly license (approved resale mode example)",
                  "value": {
                    "email": "customer@example.com",
                    "plan": "lifetime",
                    "active": true,
                    "revision": 3
                  }
                },
                "enableSponsored": {
                  "summary": "Enable access (approved sponsored mode example)",
                  "value": {
                    "email": "customer@example.com",
                    "active": true,
                    "revision": 1
                  }
                },
                "disableSponsored": {
                  "summary": "Disable access (approved sponsored mode example)",
                  "value": {
                    "email": "customer@example.com",
                    "active": false,
                    "revision": 2
                  }
                },
                "changeSponsoredEmail": {
                  "summary": "Change a sponsored customer's email (approved sponsored mode example)",
                  "value": {
                    "email": "new-address@example.com",
                    "active": true,
                    "revision": 3
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current license state. For status=pending, inspect provisioning.state and provisioning.retryable. A blocked result requires operator intervention: stop automatic retries and preserve the original customer ID and outgoing job. For a retryable pending result, retry the original PUT with the same body, revision and idempotency key using bounded backoff. GET is read-only and cannot finish provisioning. active is desired state, not evidence of a completed grant.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LicenseWriteResponse"
                },
                "examples": {
                  "resale": {
                    "summary": "Approved monthly resale access (example)",
                    "value": {
                      "id": "slic_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                      "external_customer_id": "customer_7b8a1f3d_42e6_4cb9_a2f1",
                      "active": true,
                      "plan": "monthly",
                      "revision": 1,
                      "status": "active",
                      "email_changed": false,
                      "account_url": "https://propfirmconnector.com/account",
                      "replayed": false,
                      "stale": false,
                      "provisioning": {
                        "state": "ready",
                        "error_code": null,
                        "retryable": false
                      }
                    }
                  },
                  "sponsored": {
                    "summary": "Approved sponsored access (example)",
                    "value": {
                      "id": "slic_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                      "external_customer_id": "customer_7b8a1f3d_42e6_4cb9_a2f1",
                      "active": true,
                      "revision": 1,
                      "status": "active",
                      "email_changed": false,
                      "account_url": "https://propfirmconnector.com/account",
                      "replayed": false,
                      "stale": false,
                      "provisioning": {
                        "state": "ready",
                        "error_code": null,
                        "retryable": false
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidLicenseRequest"
          },
          "401": {
            "$ref": "#/components/responses/AuthenticationFailed"
          },
          "403": {
            "$ref": "#/components/responses/LicenseForbidden"
          },
          "404": {
            "description": "previous_email was sent for a customer identifier that has no license mapping. There is no email to change; provision the customer without previous_email.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "Sponsored license was not found.",
                  "code": "SPONSORED_LICENSE_NOT_FOUND"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/LicenseConflict"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedLicenseContentType"
          },
          "429": {
            "$ref": "#/components/responses/LicenseRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalFailure"
          },
          "503": {
            "$ref": "#/components/responses/LicenseUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Licenses"
        ],
        "operationId": "getCustomerLicense",
        "summary": "Read the current customer license state",
        "description": "Requires sponsored_licenses:read and approved direct license access. Returns only the license associated with this vendor and customer identifier. Does not return the customer email, license key, unrelated licenses, or payment history. Do not send a request body or query parameters. Idempotency-Key is not required. Resale responses include plan; sponsored-access responses omit it. GET does not retry or repair a pending change; retry the original PUT if necessary. Current default limit: 120 reads per vendor per fixed minute, shared across its keys. Confirm limits during onboarding and honor Retry-After.\n\nFor status=pending, inspect provisioning.state and provisioning.retryable. A blocked result requires operator intervention: stop automatic retries and preserve the original customer ID and outgoing job. For a retryable pending result, retry the original PUT with the same body, revision and idempotency key using bounded backoff. GET is read-only and cannot finish provisioning. active is desired state, not evidence of a completed grant.\n\nEvery response includes X-Correlation-ID, a server-generated UUID for support and request tracing. Retain it with the HTTP status and error code; never log bearer secrets or signed download URLs.",
        "x-required-scopes": [
          "sponsored_licenses:read"
        ],
        "x-rate-limits": [
          {
            "scope": "vendor",
            "requests": 120,
            "windowSeconds": 60
          }
        ],
        "responses": {
          "200": {
            "description": "Current desired and applied state.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LicenseState"
                },
                "examples": {
                  "resale": {
                    "summary": "Approved monthly resale access (example)",
                    "value": {
                      "id": "slic_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                      "external_customer_id": "customer_7b8a1f3d_42e6_4cb9_a2f1",
                      "active": true,
                      "plan": "monthly",
                      "revision": 1,
                      "status": "active",
                      "email_changed": false,
                      "account_url": "https://propfirmconnector.com/account",
                      "provisioning": {
                        "state": "ready",
                        "error_code": null,
                        "retryable": false
                      }
                    }
                  },
                  "sponsored": {
                    "summary": "Approved sponsored access (example)",
                    "value": {
                      "id": "slic_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                      "external_customer_id": "customer_7b8a1f3d_42e6_4cb9_a2f1",
                      "active": true,
                      "revision": 1,
                      "status": "active",
                      "email_changed": false,
                      "account_url": "https://propfirmconnector.com/account",
                      "provisioning": {
                        "state": "ready",
                        "error_code": null,
                        "retryable": false
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidLicenseRequest"
          },
          "401": {
            "$ref": "#/components/responses/AuthenticationFailed"
          },
          "403": {
            "$ref": "#/components/responses/LicenseForbidden"
          },
          "404": {
            "description": "No license mapping exists for this customer identifier under this vendor.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "Sponsored license was not found.",
                  "code": "SPONSORED_LICENSE_NOT_FOUND"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/LicenseConflict"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/LicenseRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalFailure"
          },
          "503": {
            "$ref": "#/components/responses/LicenseUnavailable"
          }
        }
      }
    },
    "/vendor/v1/addon-downloads": {
      "post": {
        "tags": [
          "Downloads"
        ],
        "operationId": "createAddonDownload",
        "summary": "Create a short-lived addon download link",
        "description": "Requires addon:download and approved addon download access. Your authenticated server route must derive external_customer_id from the signed-in customer. Do not trust a browser-supplied customer identifier or email. Request a link only after a deliberate download click. The exact license issued by this vendor must be active and fully applied; another purchased license alone does not qualify. This endpoint never provisions or repairs a license. Send Content-Type: application/json (optionally charset=utf-8), with at most 1024 UTF-8 bytes and no extra fields or query parameters. No Idempotency-Key is required. Each successful call returns a new five-minute bearer link to the currently promoted production ZIP. The link may be reused until expiration. Do not persist, email, log, or send it to analytics. Your server may redirect the customer to the returned URL with HTTP 303, Cache-Control: no-store and Referrer-Policy: no-referrer. Current default limits: 120 requests per vendor per fixed minute and 30 per customer per fixed hour, shared across keys. A download attempt can consume its limit even if a later step fails. Confirm limits during onboarding and honor Retry-After.\n\nEvery response includes X-Correlation-ID, a server-generated UUID for support and request tracing. Retain it with the HTTP status and error code; never log bearer secrets or signed download URLs.",
        "x-required-scopes": [
          "addon:download"
        ],
        "x-max-request-bytes": 1024,
        "x-download-url-lifetime-seconds": 300,
        "x-rate-limits": [
          {
            "scope": "vendor",
            "requests": 120,
            "windowSeconds": 60
          },
          {
            "scope": "customer",
            "requests": 30,
            "windowSeconds": 3600
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DownloadRequest"
              },
              "example": {
                "external_customer_id": "customer_7b8a1f3d_42e6_4cb9_a2f1"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A temporary link and metadata for the currently promoted production addon. Example version, release, hashes and URL are illustrative only.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DownloadResponse"
                },
                "example": {
                  "url": "https://storage.googleapis.com/example-release/PropFirmConnector.zip?example=not-a-valid-download-link",
                  "file_name": "PropFirmConnector.zip",
                  "release_id": "pfc-1.2.3-20300101-a1b2c3d4",
                  "version": "1.2.3",
                  "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                  "size_bytes": 1048576,
                  "expires_at": "2030-01-01T00:05:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Malformed path, body, encoding, fields, or query parameters.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "The download request is invalid.",
                  "code": "ADDON_DOWNLOAD_INVALID_REQUEST"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired, revoked, wrong-environment, or insufficient-scope API key. Check the key and its permissions; these cases intentionally return the same response.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "Vendor authentication failed.",
                  "code": "VENDOR_AUTHENTICATION_FAILED"
                }
              }
            }
          },
          "403": {
            "description": "The required vendor-issued license or identity is unavailable, or the vendor is not permitted to download. Do not provision a replacement license to bypass this error.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "An active license issued by this vendor is required to download the addon.",
                  "code": "ADDON_DOWNLOAD_FORBIDDEN"
                }
              }
            }
          },
          "405": {
            "description": "Unsupported HTTP method. The error response also includes allowed.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "Method not allowed.",
                  "code": "METHOD_NOT_ALLOWED",
                  "allowed": "POST"
                }
              }
            }
          },
          "409": {
            "description": "The license is still being applied, or changed while the link was being prepared. Retry after a short delay; reconcile the original provisioning request if needed.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "The license is still being prepared. Please try again shortly.",
                  "code": "ADDON_DOWNLOAD_PENDING"
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds the endpoint byte limit.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "Request body is too large.",
                  "code": "VENDOR_REQUEST_TOO_LARGE"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json, optionally with charset=utf-8.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "Content-Type must be application/json.",
                  "code": "ADDON_DOWNLOAD_INVALID_REQUEST"
                }
              }
            }
          },
          "429": {
            "description": "Vendor or customer download limit reached. Codes: VENDOR_RATE_LIMITED or ADDON_DOWNLOAD_RATE_LIMITED. Honor Retry-After; it is 60 seconds for the vendor limit and up to 3600 seconds for the customer limit.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Retry-After": {
                "description": "When present, seconds to wait before retrying. Honor this value before applying backoff.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9]+$"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "Too many addon download requests. Please try again later.",
                  "code": "ADDON_DOWNLOAD_RATE_LIMITED"
                }
              }
            }
          },
          "503": {
            "description": "Temporary download, authentication-service, or configuration failure. Retry with bounded exponential backoff and jitter. Codes include ADDON_DOWNLOAD_UNAVAILABLE and VENDOR_PROMOTION_SERVICE_UNAVAILABLE.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Referrer-Policy": {
                "schema": {
                  "type": "string",
                  "const": "no-referrer"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "The download is temporarily unavailable. Please try again later.",
                  "code": "ADDON_DOWNLOAD_UNAVAILABLE"
                }
              }
            }
          }
        }
      }
    },
    "/vendor/v1/audit-events": {
      "get": {
        "tags": [
          "Audit log"
        ],
        "operationId": "listAuditEvents",
        "summary": "Read your organization’s audit log",
        "description": "Requires audit_events:read, which only a key created with the Vendor Admin purpose \"Audit log (read only)\" has; license, download and discount-code keys cannot read the log because it names your administrators. Returns the same events as the Audit log page of Vendor Admin, newest first: every authenticated API request, every portal change, and changes PropFirmConnector support made to administrator access, for 90 days. Events contain no customer email, customer identifier or request body. external_customer_id finds one customer’s events through a stored hash and may be combined only with from and to. event_id returns one event and may not be combined with anything. To continue, send page.next_cursor unchanged with the same filters. Reading the log is itself an audited request. Current default limit: 60 reads per vendor per fixed minute, shared across its keys.\n\nEvery response includes X-Correlation-ID.",
        "x-required-scopes": [
          "audit_events:read"
        ],
        "x-rate-limits": [
          {
            "scope": "vendor",
            "requests": 60,
            "windowSeconds": 60
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Events per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "page.next_cursor from the previous page, with the same filters.",
            "schema": {
              "type": "string",
              "maxLength": 1024
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "api, portal, or one event group such as api.sponsored_license_sync, api.authentication_failed or portal.api_keys.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "outcome",
            "in": "query",
            "required": false,
            "description": "success, failed (rejected or failed), or error (server errors and unfinished requests).",
            "schema": {
              "type": "string",
              "enum": [
                "success",
                "failed",
                "error"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Earliest event time, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-09-01T00:00:00Z"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Latest event time, UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "key_id",
            "in": "query",
            "required": false,
            "description": "Only requests made with this API key ID.",
            "schema": {
              "type": "string",
              "pattern": "^k[a-f0-9]{24}$"
            }
          },
          {
            "name": "external_customer_id",
            "in": "query",
            "required": false,
            "description": "Only events for this exact customer ID. Combine only with from and to.",
            "schema": {
              "$ref": "#/components/schemas/ExternalCustomerId"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "One event by its ID or X-Correlation-ID. Cannot be combined with other parameters.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of events, newest first.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditEventPage"
                },
                "example": {
                  "data": [
                    {
                      "id": "7f0c2a4e-1b3d-4c5e-8f60-123456789abc",
                      "source": "api",
                      "type": "api.sponsored_license_sync",
                      "title": "License update",
                      "summary": "PUT /vendor/v1/sponsored-licenses/{external_customer_id}",
                      "occurred_at": "2026-09-19T14:03:11.482Z",
                      "outcome": "failed",
                      "actor": {
                        "type": "api_key",
                        "key_id": "k0123456789abcdef01234567",
                        "label": "Production server",
                        "last_four": "Zx9Q",
                        "status": "revoked"
                      },
                      "http_status": 401,
                      "error_code": "VENDOR_AUTHENTICATION_FAILED",
                      "failure_reason": "key_revoked",
                      "source_ip": "203.0.113.7"
                    }
                  ],
                  "page": {
                    "limit": 50,
                    "has_more": false,
                    "next_cursor": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "The query is invalid, or the cursor was issued for different filters.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "The audit events query is invalid.",
                  "code": "AUDIT_EVENTS_INVALID_REQUEST"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AuthenticationFailed"
          },
          "404": {
            "description": "No event has that event_id.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "No event with that ID was found.",
                  "code": "NOT_FOUND"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "description": "Too many requests. Honor Retry-After.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "Too many vendor requests. Please try again later.",
                  "code": "VENDOR_RATE_LIMITED"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable. Retry with bounded backoff.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "Audit events are temporarily unavailable. Retry this request.",
                  "code": "AUDIT_EVENTS_UNAVAILABLE"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "vendor.license.cancelled": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A license was cancelled in Vendor Admin",
        "description": "An administrator cancelled a monthly or sponsored license on the Usage page. The cancellation used the next revision. Make the same change in your system: your next PUT states email and active again.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LicenseWebhookEvent"
              },
              "examples": {
                "event": {
                  "value": {
                    "id": "7d3f0a52-6c1e-4b8a-9f27-2f4f6f0c9a11",
                    "type": "vendor.license.cancelled",
                    "api_version": "v1",
                    "created_at": "2026-09-19T16:04:12.000Z",
                    "environment": "live",
                    "vendor_id": "your-vendor-id",
                    "data": {
                      "source": "vendor_admin",
                      "revision": 8,
                      "license": {
                        "id": "slic_4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f",
                        "external_customer_id": "customer-1842",
                        "email": "customer@example.com",
                        "active": false,
                        "plan": "monthly",
                        "revision": 8
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx within four seconds. Anything else is retried, up to 12 attempts."
          }
        }
      }
    },
    "vendor.license.email_changed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A license email was changed in Vendor Admin",
        "description": "An administrator moved a license to another email on the Usage page. data.license.email is the new email. Record it, because your next PUT states the email again and the license follows it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LicenseWebhookEvent"
              },
              "examples": {
                "event": {
                  "value": {
                    "id": "7d3f0a52-6c1e-4b8a-9f27-2f4f6f0c9a11",
                    "type": "vendor.license.email_changed",
                    "api_version": "v1",
                    "created_at": "2026-09-19T16:04:12.000Z",
                    "environment": "live",
                    "vendor_id": "your-vendor-id",
                    "data": {
                      "source": "vendor_admin",
                      "revision": 8,
                      "license": {
                        "id": "slic_4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f",
                        "external_customer_id": "customer-1842",
                        "email": "new-address@example.com",
                        "active": true,
                        "plan": "monthly",
                        "revision": 8
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx within four seconds. Anything else is retried, up to 12 attempts."
          }
        }
      }
    },
    "vendor.license.provisioning_blocked": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A license became blocked",
        "description": "Provisioning stopped with a blocked error code that a retry will not clear. The request that caused it already returned this code; the event reaches your server when nobody was watching that response.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LicenseWebhookEvent"
              },
              "examples": {
                "event": {
                  "value": {
                    "id": "7d3f0a52-6c1e-4b8a-9f27-2f4f6f0c9a11",
                    "type": "vendor.license.provisioning_blocked",
                    "api_version": "v1",
                    "created_at": "2026-09-19T16:04:12.000Z",
                    "environment": "live",
                    "vendor_id": "your-vendor-id",
                    "data": {
                      "source": "provisioning",
                      "revision": 8,
                      "error_code": "SPONSORED_LICENSE_EMAIL_IN_USE",
                      "license": {
                        "id": "slic_4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f",
                        "external_customer_id": "customer-1842",
                        "email": "customer@example.com",
                        "active": true,
                        "plan": "monthly",
                        "revision": 8
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx within four seconds. Anything else is retried, up to 12 attempts."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "VendorBearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Production Vendor Admin API key. Use Authorization: Bearer <your server-side key>. Choose the key type in Vendor Admin when you create the key: License management and downloads (sponsored_licenses:write, sponsored_licenses:read, addon:download) when one server does both; License management (sponsored_licenses:write, sponsored_licenses:read) when another server requests downloads; Addon download only (addon:download) for a delivery server that must not change licenses; Audit log (read only) (audit_events:read) for a monitoring tool. A Discount codes key belongs to the Coupon API and cannot call these endpoints. Vendor Admin lists only the types your organization is approved for, and a key keeps its type.\n\nA new key must make its first authenticated API request within 24 hours or it expires. With sponsored_licenses:read, GET an unused valid customer ID: 404 SPONSORED_LICENSE_NOT_FOUND confirms authentication and activates the key without issuing a license. Rotation preserves existing permissions; deploy and verify the replacement before revoking the old key. The portal permits at most two non-expired pending or active keys per environment, with at most one pending. See [credential setup](https://propfirmconnector.com/vendor-api/licensing/#credentials).\n\nMissing, invalid, expired, revoked, wrong-environment, or insufficient-scope credentials intentionally share the same 401 response."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "A stable random identifier for one outgoing change. Generate once and reuse unchanged for retries. Must be 16–128 ASCII characters, start with a letter or digit, and otherwise contain only letters, digits, dot, underscore, colon, or hyphen. Do not encode customer information. A UUID is suitable. Idempotency records are retained for 90 days; customer bindings and revision ordering remain durable. Expiry does not permit reusing a customer identity or resetting revisions.",
        "schema": {
          "type": "string",
          "minLength": 16,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{15,127}$"
        },
        "example": "57a0f2c7-2b35-49fa-a2ac-92ac646e584e"
      }
    },
    "schemas": {
      "ExternalCustomerId": {
        "type": "string",
        "minLength": 1,
        "maxLength": 128,
        "pattern": "^[A-Za-z0-9_-]{1,128}$",
        "description": "Stable customer identifier from your own account records. Case-insensitive: Trader_01 and trader_01 are the same customer. Responses return it in lowercase.",
        "examples": [
          "customer_7b8a1f3d_42e6_4cb9_a2f1"
        ]
      },
      "Revision": {
        "type": "integer",
        "minimum": 1,
        "maximum": 9007199254740991,
        "description": "Monotonically increasing desired-state revision for this customer. Increment for each committed state change. Never reset it or reuse a revision with different state.",
        "examples": [
          1
        ]
      },
      "LicenseWriteRequest": {
        "description": "The server selects the allowed schema using the authenticated vendor configuration. These alternatives describe separate approved modes; callers cannot choose an unapproved mode or plan.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/ResaleMonthlyLicenseWriteRequest"
          },
          {
            "$ref": "#/components/schemas/ResaleLifetimeLicenseWriteRequest"
          },
          {
            "$ref": "#/components/schemas/SponsoredLicenseWriteRequest"
          }
        ]
      },
      "LicenseState": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "external_customer_id",
          "active",
          "revision",
          "status",
          "email_changed",
          "account_url",
          "provisioning"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^slic_[a-f0-9]{64}$",
            "description": "Opaque public API resource identifier. It is not a license key or customer account ID."
          },
          "external_customer_id": {
            "$ref": "#/components/schemas/ExternalCustomerId"
          },
          "active": {
            "type": "boolean",
            "description": "Current desired access state. Use status to confirm whether that state has been applied."
          },
          "plan": {
            "type": "string",
            "enum": [
              "monthly",
              "lifetime"
            ],
            "description": "Present for monthly/lifetime resale access. Omitted for sponsored access. The response never returns plan=sponsored."
          },
          "revision": {
            "$ref": "#/components/schemas/Revision"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive",
              "pending"
            ],
            "description": "Applied active/inactive state, or pending when the desired revision is not applied. Inspect provisioning to distinguish a retryable delay from a blocked request."
          },
          "email_changed": {
            "type": "boolean",
            "description": "True while the current revision is the one that moved the license to a different email. Check it to notice a move you did not intend."
          },
          "account_url": {
            "type": "string",
            "format": "uri",
            "const": "https://propfirmconnector.com/account",
            "description": "Send the customer to this account page to sign in with the same email supplied by your firm and complete email verification. After installing the addon, they use its Activate this device browser flow; no license-key entry is required. Your website or confirmation email must supply this link because the API sends no onboarding email. See https://propfirmconnector.com/vendor-api/licensing/#customer-identity."
          },
          "provisioning": {
            "$ref": "#/components/schemas/ProvisioningState"
          }
        }
      },
      "LicenseWriteResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "external_customer_id",
          "active",
          "revision",
          "status",
          "email_changed",
          "account_url",
          "replayed",
          "stale",
          "provisioning"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^slic_[a-f0-9]{64}$",
            "description": "Opaque public API resource identifier. It is not a license key or customer account ID."
          },
          "external_customer_id": {
            "$ref": "#/components/schemas/ExternalCustomerId"
          },
          "active": {
            "type": "boolean",
            "description": "Current desired access state. Use status to confirm whether that state has been applied."
          },
          "plan": {
            "type": "string",
            "enum": [
              "monthly",
              "lifetime"
            ],
            "description": "Present for monthly/lifetime resale access. Omitted for sponsored access. The response never returns plan=sponsored."
          },
          "revision": {
            "$ref": "#/components/schemas/Revision"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive",
              "pending"
            ],
            "description": "Applied active/inactive state, or pending when the desired revision is not applied. Inspect provisioning to distinguish a retryable delay from a blocked request."
          },
          "email_changed": {
            "type": "boolean",
            "description": "True while the current revision is the one that moved the license to a different email. Check it to notice a move you did not intend."
          },
          "account_url": {
            "type": "string",
            "format": "uri",
            "const": "https://propfirmconnector.com/account",
            "description": "Send the customer to this account page to sign in with the same email supplied by your firm and complete email verification. After installing the addon, they use its Activate this device browser flow; no license-key entry is required. Your website or confirmation email must supply this link because the API sends no onboarding email. See https://propfirmconnector.com/vendor-api/licensing/#customer-identity."
          },
          "replayed": {
            "type": "boolean",
            "description": "The request or revision had already been seen. The response still represents current state."
          },
          "stale": {
            "type": "boolean",
            "description": "A higher revision already exists. This older request did not overwrite it."
          },
          "provisioning": {
            "$ref": "#/components/schemas/ProvisioningState"
          }
        }
      },
      "DownloadRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "external_customer_id"
        ],
        "properties": {
          "external_customer_id": {
            "$ref": "#/components/schemas/ExternalCustomerId"
          }
        }
      },
      "DownloadResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url",
          "file_name",
          "release_id",
          "version",
          "sha256",
          "size_bytes",
          "expires_at"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "Temporary bearer link. Follow it only for the customer who requested it. It expires five minutes after issuance."
          },
          "file_name": {
            "type": "string",
            "const": "PropFirmConnector.zip"
          },
          "release_id": {
            "type": "string",
            "pattern": "^pfc-((?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*))-[0-9]{8}-[a-f0-9]{8}$",
            "description": "Public identifier for the promoted addon release."
          },
          "version": {
            "type": "string",
            "pattern": "^(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)$",
            "description": "Version of the promoted production addon."
          },
          "sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$",
            "description": "SHA-256 digest of the complete ZIP."
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 104857600,
            "description": "ZIP size in bytes."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "UTC expiration time of this link."
          }
        }
      },
      "ApiError": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Readable explanation. Do not parse this text for control flow."
          },
          "code": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]{2,63}$",
            "description": "Machine-readable error code."
          },
          "allowed": {
            "type": "string",
            "description": "Present on METHOD_NOT_ALLOWED; lists permitted methods."
          },
          "conflicting_external_customer_id": {
            "$ref": "#/components/schemas/ExternalCustomerId",
            "description": "Present on SPONSORED_LICENSE_EMAIL_IN_USE: your customer ID whose active license holds that email."
          }
        }
      },
      "ResaleMonthlyLicenseWriteRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Available only to integrations approved for monthly resale licenses. This mode requires plan on every PUT, including disable and reactivation.",
        "required": [
          "email",
          "plan",
          "active",
          "revision"
        ],
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 254,
            "format": "email",
            "description": "Customer email from your verified account records. Send the normalized address; the API trims it and converts it to lowercase. Send the email that should hold the license: a newer revision with a different email moves the license to it.",
            "examples": [
              "customer@example.com"
            ]
          },
          "plan": {
            "type": "string",
            "const": "monthly"
          },
          "active": {
            "type": "boolean",
            "description": "Desired monthly access. Stays enabled until explicitly disabled."
          },
          "revision": {
            "$ref": "#/components/schemas/Revision"
          },
          "previous_email": {
            "type": "string",
            "maxLength": 254,
            "format": "email",
            "description": "Optional guard for an email change: the address you believe is on record. If it is not, the request fails with SPONSORED_LICENSE_PREVIOUS_EMAIL_MISMATCH and nothing moves. An email change may also change active, but not plan.",
            "examples": [
              "customer@example.com"
            ]
          }
        }
      },
      "ResaleLifetimeLicenseWriteRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Available only to integrations approved for lifetime resale licenses. Creates permanent ordinary Lifetime access or upgrades the existing monthly slot; cannot disable or downgrade.",
        "required": [
          "email",
          "plan",
          "active",
          "revision"
        ],
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 254,
            "format": "email",
            "description": "Customer email from your verified account records. Send the normalized address; the API trims it and converts it to lowercase. A lifetime license is locked to this email; it cannot be changed.",
            "examples": [
              "customer@example.com"
            ]
          },
          "plan": {
            "type": "string",
            "const": "lifetime"
          },
          "active": {
            "type": "boolean",
            "const": true,
            "description": "Lifetime access can only be enabled."
          },
          "revision": {
            "$ref": "#/components/schemas/Revision"
          }
        }
      },
      "SponsoredLicenseWriteRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Use only if your integration was approved for sponsored access. Omit plan entirely; specifying it is rejected. Sponsored access is reversible and displays the approved sponsor name in the customer account.",
        "required": [
          "email",
          "active",
          "revision"
        ],
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 254,
            "format": "email",
            "description": "Customer email from your verified account records. Send the normalized address; the API trims it and converts it to lowercase. Send the email that should hold the license: a newer revision with a different email moves the license to it.",
            "examples": [
              "customer@example.com"
            ]
          },
          "active": {
            "type": "boolean",
            "description": "Desired sponsored access. Disable only when none of the customer subscriptions or accounts covered by your integration remain active."
          },
          "revision": {
            "$ref": "#/components/schemas/Revision"
          },
          "previous_email": {
            "type": "string",
            "maxLength": 254,
            "format": "email",
            "description": "Optional guard for an email change: the address you believe is on record. If it is not, the request fails with SPONSORED_LICENSE_PREVIOUS_EMAIL_MISMATCH and nothing moves. An email change may also change active.",
            "examples": [
              "customer@example.com"
            ]
          }
        }
      },
      "ProvisioningState": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "state",
          "error_code",
          "retryable"
        ],
        "description": "For status=pending, inspect provisioning.state and provisioning.retryable. A blocked result requires operator intervention: stop automatic retries and preserve the original customer ID and outgoing job. For a retryable pending result, retry the original PUT with the same body, revision and idempotency key using bounded backoff. GET is read-only and cannot finish provisioning. active is desired state, not evidence of a completed grant.",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "ready",
              "pending",
              "blocked"
            ],
            "description": "ready means the current revision is applied; pending can be retried; blocked requires an account or identity correction by an operator."
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              null,
              "SPONSORED_LICENSE_IDENTITY_CONFLICT",
              "SPONSORED_LICENSE_EMAIL_IN_USE",
              "SPONSORED_LICENSE_LEGACY_REVIEW_REQUIRED",
              "SPONSORED_ENTITLEMENT_CONFLICT",
              "SPONSORED_LICENSE_PROJECTION_PENDING",
              "SPONSORED_LICENSE_BILLING_PENDING",
              "SPONSORED_LICENSE_PROVISIONING_PENDING"
            ],
            "description": "Safe reason for the current provisioning delay or block. Null when ready or no failure has been recorded."
          },
          "retryable": {
            "type": "boolean",
            "description": "True only when the pending change can be retried. False for ready and blocked states."
          }
        }
      },
      "AuditEvent": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "id",
          "source",
          "type",
          "title",
          "occurred_at",
          "outcome",
          "actor"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "For an API request this is the X-Correlation-ID that request returned."
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "portal",
              "support"
            ]
          },
          "type": {
            "type": "string",
            "description": "Stable event type, for example api.sponsored_license_sync or portal.api_key.revoked."
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": [
              "string",
              "null"
            ],
            "description": "For an API request, the method and documented route. Never a customer identifier."
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "success",
              "failed",
              "error",
              "incomplete"
            ]
          },
          "actor": {
            "type": "object",
            "additionalProperties": true,
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "api_key",
                  "admin",
                  "support"
                ]
              },
              "key_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "label": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "last_four": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "role": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "http_status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "failure_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "secret_mismatch",
              "key_revoked",
              "key_expired",
              "key_inactive",
              "scope_denied",
              "key_invalid",
              null
            ],
            "description": "Why a request using one of your key IDs was rejected. The rejected caller only ever receives the generic 401."
          },
          "source_ip": {
            "type": [
              "string",
              "null"
            ],
            "description": "The address the request arrived from."
          }
        }
      },
      "AuditEventPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AuditEvent"
            }
          },
          "page": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "limit",
              "has_more",
              "next_cursor"
            ],
            "description": "Omitted when event_id is used.",
            "properties": {
              "limit": {
                "type": "integer"
              },
              "has_more": {
                "type": "boolean"
              },
              "next_cursor": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "LicenseWebhookEvent": {
        "type": "object",
        "description": "Signed exactly like Coupon API events: verify X-PFC-Webhook-Signature over the raw body before parsing it. Deduplicate by id. Delivery is at-least-once and may arrive out of order.",
        "additionalProperties": false,
        "required": [
          "id",
          "type",
          "api_version",
          "created_at",
          "environment",
          "vendor_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Event ID. Equals the X-PFC-Webhook-Id header. A replay keeps it."
          },
          "type": {
            "type": "string",
            "enum": [
              "vendor.license.cancelled",
              "vendor.license.email_changed",
              "vendor.license.provisioning_blocked"
            ]
          },
          "api_version": {
            "type": "string",
            "const": "v1"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the change happened."
          },
          "environment": {
            "type": "string",
            "enum": [
              "live"
            ]
          },
          "vendor_id": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "source",
              "revision",
              "license"
            ],
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "vendor_admin",
                  "provisioning"
                ],
                "description": "vendor_admin: an administrator made the change in Vendor Admin. provisioning: the license became blocked while it was being applied."
              },
              "revision": {
                "$ref": "#/components/schemas/Revision"
              },
              "error_code": {
                "type": "string",
                "description": "Only on vendor.license.provisioning_blocked: the same blocked code GET returns in provisioning.error_code.",
                "enum": [
                  "SPONSORED_LICENSE_IDENTITY_CONFLICT",
                  "SPONSORED_LICENSE_LEGACY_REVIEW_REQUIRED",
                  "SPONSORED_ENTITLEMENT_CONFLICT",
                  "SPONSORED_LICENSE_EMAIL_IN_USE"
                ]
              },
              "license": {
                "type": "object",
                "description": "The license when the event was sent. Apply it only if its revision is higher than the one you hold.",
                "additionalProperties": false,
                "required": [
                  "id",
                  "external_customer_id",
                  "email",
                  "active",
                  "revision"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "pattern": "^slic_[a-f0-9]{64}$"
                  },
                  "external_customer_id": {
                    "$ref": "#/components/schemas/ExternalCustomerId"
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "lifetime"
                    ],
                    "description": "Absent for sponsored access."
                  },
                  "revision": {
                    "$ref": "#/components/schemas/Revision"
                  }
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "InvalidLicenseRequest": {
        "description": "Invalid path, headers, body, encoding, or query parameters. Check the strict request schema and immutable customer mapping.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "The sponsored license request is invalid.",
              "code": "SPONSORED_LICENSE_INVALID_REQUEST"
            }
          }
        }
      },
      "AuthenticationFailed": {
        "description": "Missing, invalid, expired, revoked, wrong-environment, or insufficient-scope API key. Check the key and its permissions; these cases intentionally return the same response.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Vendor authentication failed.",
              "code": "VENDOR_AUTHENTICATION_FAILED"
            }
          }
        }
      },
      "LicenseForbidden": {
        "description": "Direct license access is not permitted for this vendor.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Sponsored license access is not authorized.",
              "code": "SPONSORED_LICENSE_FORBIDDEN"
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "Unsupported HTTP method. The error response also includes allowed.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Method not allowed.",
              "code": "METHOD_NOT_ALLOWED",
              "allowed": "PUT, GET"
            }
          }
        }
      },
      "LicenseConflict": {
        "description": "Do not automatically retry without understanding the conflict. Codes: SPONSORED_LICENSE_IDENTITY_CONFLICT (account records for that email need support review), SPONSORED_LICENSE_IDEMPOTENCY_CONFLICT (key reused for different content), SPONSORED_LICENSE_REVISION_CONFLICT (same revision, different state), SPONSORED_LICENSE_LIFETIME_PERMANENT (lifetime disable, downgrade or email change), SPONSORED_LICENSE_PREVIOUS_EMAIL_MISMATCH (the optional previous_email guard is not the email on record; nothing moved), SPONSORED_LICENSE_EMAIL_IN_USE (that email has an active license from your firm under the customer ID in conflicting_external_customer_id; nothing was recorded), SPONSORED_LICENSE_LEGACY_REVIEW_REQUIRED (existing access needs support review), SPONSORED_ENTITLEMENT_CONFLICT (conflicting customer grants). Contact PropFirmConnector support for identity or existing-access conflicts.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Lifetime licenses cannot be disabled or changed to monthly.",
              "code": "SPONSORED_LICENSE_LIFETIME_PERMANENT"
            }
          }
        }
      },
      "BodyTooLarge": {
        "description": "Request exceeds the endpoint byte limit.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Request body is too large.",
              "code": "VENDOR_REQUEST_TOO_LARGE"
            }
          }
        }
      },
      "UnsupportedLicenseContentType": {
        "description": "Content-Type must be application/json, optionally with charset=utf-8.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Content-Type must be application/json.",
              "code": "SPONSORED_LICENSE_INVALID_REQUEST"
            }
          }
        }
      },
      "LicenseRateLimited": {
        "description": "Codes: VENDOR_RATE_LIMITED (minute limit) or SPONSORED_LICENSE_DAILY_LIMIT (first-activation daily limit). Honor Retry-After: 60 seconds for the minute limit, or seconds until the next UTC day for the daily limit. Retry the same outgoing change without changing its idempotency key or revision.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "Retry-After": {
            "description": "When present, seconds to wait before retrying. Honor this value before applying backoff.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Too many vendor requests. Please try again later.",
              "code": "VENDOR_RATE_LIMITED"
            }
          }
        }
      },
      "InternalFailure": {
        "description": "Unexpected service failure. The desired change may already have been accepted. Retain the outgoing job, retry the exact same PUT with bounded backoff, and reconcile through GET.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Something went wrong on our end. Please try again in a moment.",
              "code": "SERVICE_UNAVAILABLE"
            }
          }
        }
      },
      "LicenseUnavailable": {
        "description": "Temporary service or reconciliation failure. Codes include SPONSORED_LICENSE_UNAVAILABLE, SPONSORED_LICENSE_PROJECTION_PENDING, SPONSORED_LICENSE_BILLING_PENDING, and VENDOR_PROMOTION_SERVICE_UNAVAILABLE. Retry the same PUT with bounded exponential backoff and jitter. SPONSORED_LICENSE_BILLING_PENDING includes Retry-After: 60. Do not replace the customer identity, revision, or idempotency key simply to bypass a pending operation.",
        "headers": {
          "Cache-Control": {
            "description": "Responses must not be cached.",
            "schema": {
              "type": "string",
              "const": "no-store"
            }
          },
          "Retry-After": {
            "description": "When present, seconds to wait before retrying. Honor this value before applying backoff.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            }
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "A newer license update is being applied. Retry this request.",
              "code": "SPONSORED_LICENSE_PROJECTION_PENDING"
            }
          }
        }
      }
    },
    "headers": {
      "CorrelationId": {
        "description": "Server-generated request UUID. Include this value when reporting a failure. It is not an API key or customer identifier.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    }
  }
}
