{
  "openapi": "3.1.0",
  "info": {
    "title": "ProofKit Partner API",
    "version": "1.0.0",
    "description": "Server-to-server API for resellers: create subscribers, hand them VPN configs or a whole branded subscription, and read usage for reconciliation. Subscribers have no ProofKit login.\n\n- **Authentication**: every request carries your key in `X-Partner-Key` (`pk_`, 8 hex, `_`, 48 hex). Call it from your backend: browsers on other origins cannot send the header.\n- **Money** is in nanoTON integers (1 TON = 1,000,000,000). A GB is 1,073,741,824 bytes, upload and download together.\n- **Errors** use one envelope, `{\"error\": {\"code\", \"message\"}}`: dispatch on `error.code`. Requests the framework rejects before a handler runs (malformed JSON, a wrong `Content-Type`, a missing field, an unparseable query or path parameter) come back as plain text with `400`, `415` or `422`. These checks run before the key check, so such a request gets them with no key or a wrong one. A body over 1 MB is refused by our proxy before it reaches the API: `413` with an HTML page and none of the headers below.\n- An unknown path under `/api/v1/partner` answers `404 NOT_FOUND` (\"Unknown partner API endpoint\"); a listed path with the wrong method answers `405 METHOD_NOT_ALLOWED`.\n- **Rate limits**: 300 requests per minute per key, 1,200 for subscription fetches, with `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` on every response except those of the per-IP layer in front (its `429` and `500`) and the proxy's `413`.\n- Every response is scoped to your partner: another partner's subscriber or session answers `404`.\n\nThe full reference, with the rules behind each field: [Partner API documentation](https://proofkit.org/partner-docs). Money, keys and subscription settings live on the Partner page (`https://proofkit.org/#/partner`), not in this API.",
    "termsOfService": "https://proofkit.org/partner-terms",
    "contact": {
      "name": "ProofKit",
      "email": "legal@proofkit.org",
      "url": "https://proofkit.org/partners"
    }
  },
  "externalDocs": {
    "description": "Partner API documentation",
    "url": "https://proofkit.org/partner-docs"
  },
  "servers": [
    {
      "url": "https://proofkit.org/api/v1/partner",
      "description": "Production"
    }
  ],
  "security": [
    {
      "partnerKey": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Plan, balance and trial."
    },
    {
      "name": "Catalogue",
      "description": "Nodes you may sell."
    },
    {
      "name": "Subscribers",
      "description": "Create, list, read, disable and reset subscribers."
    },
    {
      "name": "Subscription",
      "description": "The subscriber's whole subscription, for Happ, Ghostlane or your own format."
    },
    {
      "name": "Sessions",
      "description": "One route at a time: connect, list, configs, end."
    },
    {
      "name": "olcRTC",
      "description": "olcRTC lines in one call."
    },
    {
      "name": "Usage",
      "description": "Traffic and cost over a window."
    }
  ],
  "paths": {
    "/account": {
      "get": {
        "operationId": "getAccount",
        "tags": [
          "Account"
        ],
        "summary": "Plan, balance, trial, wallet and deposit link",
        "description": "The state of your account, so you can act before it turns into a refusal: whether traffic is charged (`billing_plan`, `trial.active`), how far the balance is from the connect gate (`balance_nano > -credit_limit_nano`, after the gate subtracts any stake locked in nodes your billing account owns), and where deposits go.\n\nNot reported here or anywhere in this API: whether subscriptions or olcRTC are switched on for your partner, your olcRTC share and your subscription settings. The Partner page shows them. [Reference](https://proofkit.org/partner-docs#get-account)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your account.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAccount"
                },
                "example": {
                  "partner_id": "0e5a1c2b-7d3f-4a9e-8b6c-000000000001",
                  "billable": true,
                  "billing_mode": "metered",
                  "billing_plan": "trial",
                  "balance_nano": 0,
                  "credit_limit_nano": 0,
                  "suspended": false,
                  "rate_limit_per_min": 300,
                  "wallet": null,
                  "escrow_address": "0:4ed1…",
                  "deposit_link": null,
                  "trial": {
                    "until": "2026-10-03T12:00:00Z",
                    "max_subscribers": 10,
                    "subscribers": 3,
                    "max_bytes": 53687091200,
                    "bytes_used": 2147483648,
                    "active": true
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/catalogue": {
      "get": {
        "operationId": "getCatalogue",
        "tags": [
          "Catalogue"
        ],
        "summary": "Nodes you may sell, with the price you pay",
        "description": "Every node you may sell: active public nodes, plus nodes granted to you, including private ones. Ordered by country, then price. Origins (exits) and edges (entries) are both listed.\n\nDo not pick an olcRTC node from the slot and key numbers and then connect: another caller can take the last place in between. `POST /subscribers/{external_id}/olcrtc` chooses and claims in one call. [Reference](https://proofkit.org/partner-docs#get-catalogue)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The nodes, ordered by country, then price.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CatalogueNode"
                  }
                },
                "example": [
                  {
                    "node_id": "0f2a9c4e-6b1d-4e7f-8a3c-000000000201",
                    "node_type": "origin",
                    "country": "DE",
                    "price_per_gb_nano": 10000000,
                    "total_price_per_gb_nano": 12000000,
                    "fee_bps": 2000,
                    "supported_transports": [
                      "vless_reality",
                      "hysteria2",
                      "vless_xhttp",
                      "olcrtc"
                    ],
                    "olcrtc_carriers": [
                      "telemost",
                      "wbstream",
                      "salutejazz"
                    ],
                    "olcrtc_slots_total": 8,
                    "olcrtc_slots_free": 5,
                    "olcrtc_keys_total": 16,
                    "olcrtc_keys_free": 4,
                    "is_public": true,
                    "sessions_count": 12,
                    "max_users": 0,
                    "max_users_effective": 500,
                    "agent_online": true
                  },
                  {
                    "node_id": "17e6d2a8-4c9f-4b3e-9d5a-000000000301",
                    "node_type": "edge",
                    "country": "RU",
                    "price_per_gb_nano": 10000000,
                    "total_price_per_gb_nano": 12000000,
                    "fee_bps": 2000,
                    "entry_region": "RU",
                    "supported_transports": [
                      "vless_reality",
                      "hysteria2",
                      "vless_xhttp"
                    ],
                    "olcrtc_carriers": [],
                    "is_public": true,
                    "sessions_count": 32,
                    "max_users": 0,
                    "max_users_effective": 500,
                    "agent_online": true
                  }
                ]
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers": {
      "get": {
        "operationId": "listSubscribers",
        "tags": [
          "Subscribers"
        ],
        "summary": "Your subscribers, a page at a time",
        "description": "Every subscriber you created, disabled ones included, oldest first: by creation time, then by `account_id` for two created in the same instant. Pass the answer's `next_cursor` as `cursor` to read the next page; it is `null` on the last one. A subscriber created while you page lands on a later page, never on one already read.\n\n`q` keeps the `external_id`s that start with it, byte for byte: case-sensitive, and `%` and `_` are ordinary characters. `disabled=true` keeps the disabled subscribers, `disabled=false` the enabled ones.\n\nEach entry is what `GET /subscribers/{external_id}` answers, without `sub_token` and `created`: a list carries no credential. [Reference](https://proofkit.org/partner-docs#get-subscribers)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Subscribers per page, 1 to 200. Defaults to 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "example": 100
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `next_cursor` of the previous page, as it came. Absent or empty for the first page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Keep only the `external_id`s that start with this, byte for byte.",
            "schema": {
              "type": "string"
            },
            "example": "tg-"
          },
          {
            "name": "disabled",
            "in": "query",
            "required": false,
            "description": "`true`: only disabled subscribers; `false`: only enabled ones. Absent: both.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriberList"
                },
                "example": {
                  "subscribers": [
                    {
                      "external_id": "user-42",
                      "account_id": "8c1d4f2a-3b5e-4c7d-9a1f-000000000042",
                      "disabled": false,
                      "created_at": "2026-09-26T10:00:00Z"
                    },
                    {
                      "external_id": "user-43",
                      "account_id": "8c1d4f2a-3b5e-4c7d-9a1f-000000000043",
                      "disabled": true,
                      "created_at": "2026-09-26T10:05:00Z"
                    }
                  ],
                  "next_cursor": "MTc5MDQxNzEwMDAwMDAwMC44YzFkNGYyYTNiNWU0YzdkOWExZjAwMDAwMDAwMDA0Mw"
                }
              }
            }
          },
          "400": {
            "description": "`limit` outside 1 to 200, or a `cursor` that no list answered with.\n\nCodes: `INVALID_LIMIT`, `INVALID_CURSOR`.\n\nA plain-text body (no JSON envelope) means the framework refused the request before our handler ran: a `limit` that is not a number, or a `disabled` that is not `true` or `false`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_LIMIT": {
                    "summary": "limit outside 1 to 200",
                    "value": {
                      "error": {
                        "code": "INVALID_LIMIT",
                        "message": "limit must be between 1 and 200"
                      }
                    }
                  },
                  "INVALID_CURSOR": {
                    "summary": "A cursor no list answered with",
                    "value": {
                      "error": {
                        "code": "INVALID_CURSOR",
                        "message": "cursor is not one a subscriber list answered with"
                      }
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Failed to deserialize query string: disabled: provided string was not `true` or `false`"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createSubscriber",
        "tags": [
          "Subscribers"
        ],
        "summary": "Create or re-enable a subscriber (idempotent)",
        "description": "Creates a subscriber, or returns the existing one. Idempotent on `external_id`, including under concurrent calls. The status is `200` either way: `created` says whether this call made the subscriber. Calling it for a disabled subscriber re-enables them (`created: false, disabled: false`, same `sub_token`).\n\nCreating a subscriber starts no session and touches no node. [Reference](https://proofkit.org/partner-docs#post-subscribers)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSubscriberRequest"
              },
              "example": {
                "external_id": "user-42"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The subscriber, new or existing.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscriber"
                },
                "example": {
                  "external_id": "user-42",
                  "account_id": "8c1d4f2a-3b5e-4c7d-9a1f-000000000042",
                  "sub_token": "b7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0de",
                  "created": true,
                  "disabled": false,
                  "created_at": "2026-09-26T10:00:00Z"
                }
              }
            }
          },
          "400": {
            "description": "A request we refuse.\n\nCodes: `INVALID_EXTERNAL_ID`, `PARTNER_NOT_BILLABLE`.\n\nA plain-text body (no JSON envelope) means the framework refused the request before our handler ran.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_EXTERNAL_ID": {
                    "summary": "external_id empty, blank or over 128 bytes after trimming",
                    "value": {
                      "error": {
                        "code": "INVALID_EXTERNAL_ID",
                        "message": "external_id must be 1..=128 characters"
                      }
                    }
                  },
                  "PARTNER_NOT_BILLABLE": {
                    "summary": "Your billing account is not set up yet",
                    "value": {
                      "error": {
                        "code": "PARTNER_NOT_BILLABLE",
                        "message": "Partner has no billing account yet — set wholesale terms first"
                      }
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Failed to parse the request body as JSON: expected value at line 1 column 1"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The free trial's subscriber cap.\n\nCodes: `TRIAL_LIMIT`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "TRIAL_LIMIT": {
                    "summary": "Inside a free trial that already covers max_subscribers subscribers (a new subscriber only)",
                    "value": {
                      "error": {
                        "code": "TRIAL_LIMIT",
                        "message": "The free trial covers 10 subscribers. To add more, top up the balance in the partner dashboard and ask ProofKit to end the trial."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers/{external_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "operationId": "getSubscriber",
        "tags": [
          "Subscribers"
        ],
        "summary": "One subscriber's state",
        "description": "The subscriber in the same shape as `POST /subscribers`, with `created: false`. Disabled subscribers are returned too, with `disabled: true`. `GET /subscribers` lists them a page at a time, without the `sub_token`. [Reference](https://proofkit.org/partner-docs#get-subscribersexternal_id)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The subscriber.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscriber"
                },
                "example": {
                  "external_id": "user-42",
                  "account_id": "8c1d4f2a-3b5e-4c7d-9a1f-000000000042",
                  "sub_token": "b7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0de",
                  "created": false,
                  "disabled": false,
                  "created_at": "2026-09-26T10:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidPathParameter"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not yours, or does not exist.\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "Not your subscriber, or none by that external_id",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Subscriber not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "disableSubscriber",
        "tags": [
          "Subscribers"
        ],
        "summary": "Disable a subscriber",
        "description": "Disables the subscriber and cuts off their access: every live session ends and its credential leaves its node at once; the hosted subscription URLs and `GET …/subscription` answer `404`; `connect` answers `403 FORBIDDEN`. Their olcRTC keys drop out of the room at the next room-health pass, about 5 minutes later. The account, its usage and ledger history stay.\n\nUndone by `POST /subscribers` **and by `POST /subscribers/{external_id}/olcrtc`** with the same `external_id`. Calling it again also returns `204`. [Reference](https://proofkit.org/partner-docs#delete-subscribersexternal_id)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "responses": {
          "204": {
            "description": "Disabled (or already disabled).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidPathParameter"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not yours, or does not exist.\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "Not your subscriber, or none by that external_id",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Subscriber not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers/{external_id}/reset": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "post": {
        "operationId": "resetSubscriber",
        "tags": [
          "Subscribers"
        ],
        "summary": "New subscription token and credentials, for a leaked link",
        "description": "Starts a subscriber over. A new `sub_token` is issued (the old hosted URLs answer `404` from this moment), every live session ends and leaves its node, and the subscriber's olcRTC keys are revoked with each room told at once. The subscriber stays enabled or disabled as it was; the next subscription fetch provisions fresh credentials. No body.\n\n**Not idempotent**: every call issues a new token and ends the sessions again. [Reference](https://proofkit.org/partner-docs#post-subscribersexternal_idreset)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The subscriber with its new `sub_token`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscriber"
                },
                "example": {
                  "external_id": "user-42",
                  "account_id": "8c1d4f2a-3b5e-4c7d-9a1f-000000000042",
                  "sub_token": "e1d2c3b4e1d2c3b4e1d2c3b4e1d2c3b4e1d2c3b4e1d2c3b4e1d2c3b4e1d2c3b4",
                  "created": false,
                  "disabled": false,
                  "created_at": "2026-09-26T10:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidPathParameter"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not yours, or does not exist.\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "Not your subscriber, or none by that external_id",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Subscriber not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers/{external_id}/subscription": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "operationId": "getSubscription",
        "tags": [
          "Subscription"
        ],
        "summary": "The subscriber's whole subscription, branded",
        "description": "Every route the subscriber may use that your route filters keep, and every live session they already hold, each with every transport the route carries, named and branded for you. Proxy it under your own domain and pass the body **and** the headers on: the app reads both.\n\nThe first fetch provisions the routes the subscriber lacks (at most about 5 seconds is spent on it; the next fetch starts the rest). A route that cannot be made now is left out and the rest is returned. Later fetches return the same lines while nothing changes.\n\n`ghostlane` and `json` carry olcRTC lines when olcRTC is on for your partner, and a fetch in those formats can reserve olcRTC keys; a `happ` fetch never does.\n\nMust be switched on for your partner first (`403 SUBSCRIPTION_NOT_ENABLED` until then, provisioning nothing). An invalid `format` (`400`) and an unknown or disabled subscriber (`404`) are answered before that. Counted in its own rate-limit bucket: 1,200 per minute per key. [Reference](https://proofkit.org/partner-docs#get-subscribersexternal_idsubscription)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "`happ` (default; also when absent or empty): base64 list for Happ, Hiddify, v2rayNG, never olcRTC. `ghostlane`: plain lines plus olcRTC. `json`: the lines as data plus olcRTC. Any other value answers `400 INVALID_FORMAT`.",
            "schema": {
              "type": "string",
              "enum": [
                "happ",
                "ghostlane",
                "json"
              ],
              "default": "happ"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The subscription. `happ` and `ghostlane` are `text/plain; charset=utf-8`; `json` is `application/json`. When your filters keep nothing the status is still `200`: an empty body for `happ` and `ghostlane`, `{\"entries\": [], …}` for `json`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "profile-title": {
                "description": "`base64:` + base64 of your title. Omitted when no title is set.",
                "schema": {
                  "type": "string"
                },
                "example": "base64:TXkgVlBO"
              },
              "profile-update-interval": {
                "description": "Hours between the app's refreshes, 1 to 168; `1` unless set.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 168
                },
                "example": 1
              },
              "support-url": {
                "description": "Your support link. Omitted when not set.",
                "schema": {
                  "type": "string"
                },
                "example": "https://t.me/myvpn_support"
              },
              "profile-web-page-url": {
                "description": "Your web page. Omitted when not set.",
                "schema": {
                  "type": "string"
                }
              },
              "subscription-userinfo": {
                "description": "This calendar month's traffic (UTC). We set no quota and no expiry (`total=0; expire=0`); you may rewrite `total=` and `expire=` in your proxy.",
                "schema": {
                  "type": "string"
                },
                "example": "upload=214748364; download=858993460; total=0; expire=0"
              },
              "Cache-Control": {
                "description": "Always `no-store`.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Content-Disposition": {
                "description": "Always `inline`.",
                "schema": {
                  "type": "string",
                  "const": "inline"
                }
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "`happ`: base64 of the newline-joined share links. `ghostlane`: the same links as plain lines, then the olcRTC lines."
                },
                "examples": {
                  "happ": {
                    "summary": "format=happ (base64)",
                    "value": "dmxlc3M6Ly88dXVpZD5AMjAzLjAuMTEzLjEwOjQ0Mz90eXBlPXRjcCZzZWN1cml0eT1yZWFsaXR5Ji4uLiNERSUyMERpcmVjdA=="
                  },
                  "ghostlane": {
                    "summary": "format=ghostlane (plain lines)",
                    "value": "vless://<uuid>@203.0.113.10:443?type=tcp&security=reality&sni=<sni>&fp=chrome&pbk=<pbk>&sid=<sid>&flow=xtls-rprx-vision#DE%20Direct\nhysteria2://<uuid>@203.0.113.10:30211?sni=<sni>&obfs=salamander&obfs-password=<pw>&insecure=0&pinSHA256=<pin>#DE%20Direct%20%C2%B7%20Hysteria2\nolcrtc://telemost?vp8channel@https://telemost.yandex.ru/j/00000000000000#<key>$DE · olcRTC"
                  }
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionJson"
                },
                "example": {
                  "entries": [
                    {
                      "url": "vless://<uuid>@203.0.113.10:443?type=tcp&security=reality&sni=<sni>&fp=chrome&pbk=<pbk>&sid=<sid>&flow=xtls-rprx-vision#DE%20Direct",
                      "transport": "vless_reality",
                      "remark": "DE Direct",
                      "session_id": "3d91b7c2-5e4a-4f8b-a2c6-000000000101",
                      "origin_id": "0f2a9c4e-6b1d-4e7f-8a3c-000000000201",
                      "edge_id": null,
                      "exit_country": "DE",
                      "entry_country": null
                    },
                    {
                      "url": "hysteria2://<uuid>@203.0.113.20:30211?…#DE%20via%20RU%20%C2%B7%20Hysteria2",
                      "transport": "hysteria2",
                      "remark": "DE via RU · Hysteria2",
                      "session_id": "a51c6e3d-8f2b-4d9a-b7e1-000000000102",
                      "origin_id": "0f2a9c4e-6b1d-4e7f-8a3c-000000000201",
                      "edge_id": "17e6d2a8-4c9f-4b3e-9d5a-000000000301",
                      "exit_country": "DE",
                      "entry_country": "RU"
                    },
                    {
                      "url": "olcrtc://wbstream?vp8channel@<room code>#<key>$DE · olcRTC · WB",
                      "transport": "olcrtc",
                      "carrier": "wbstream",
                      "remark": "DE · olcRTC · WB",
                      "session_id": "3d91b7c2-5e4a-4f8b-a2c6-000000000101",
                      "origin_id": "0f2a9c4e-6b1d-4e7f-8a3c-000000000201",
                      "edge_id": null,
                      "exit_country": "DE",
                      "entry_country": null
                    }
                  ],
                  "upload": 214748364,
                  "download": 858993460
                }
              }
            }
          },
          "400": {
            "description": "An invalid `format`.\n\nCodes: `INVALID_FORMAT`.\n\nA plain-text body (no JSON envelope) is an `external_id` that is not valid UTF-8 once percent-decoded, e.g. `%FF`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_FORMAT": {
                    "summary": "format is not happ, ghostlane or json",
                    "value": {
                      "error": {
                        "code": "INVALID_FORMAT",
                        "message": "format must be happ, ghostlane or json"
                      }
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Invalid URL: Invalid UTF-8 in `external_id`"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Subscriptions are not switched on for your partner.\n\nCodes: `SUBSCRIPTION_NOT_ENABLED`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "SUBSCRIPTION_NOT_ENABLED": {
                    "summary": "Ask ProofKit to switch subscriptions on",
                    "value": {
                      "error": {
                        "code": "SUBSCRIPTION_NOT_ENABLED",
                        "message": "Subscriptions are not switched on for this partner; ask ProofKit to enable them"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not yours, does not exist, or disabled.\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "Not your subscriber, or none by that external_id",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Subscriber not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimitedSubscription"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers/{external_id}/connect": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "post": {
        "operationId": "connectSubscriber",
        "tags": [
          "Sessions"
        ],
        "summary": "Start (or return) one session",
        "description": "Starts one session for the subscriber, or returns the one they already hold on that route (same origin, same edge or none). It runs the same code a signed-in ProofKit user's connect runs: node access, your price, the payer's balance and node capacity. One of `exit_country` or `origin_id` is required.\n\n`entry_country` with no edge available creates the session **direct**, without an error: check `edge_id` in the answer. Node selection ignores olcRTC: use `POST /subscribers/{external_id}/olcrtc` for that. A session on a node whose agent is offline is created `pending`. [Reference](https://proofkit.org/partner-docs#post-subscribersexternal_idconnect)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectRequest"
              },
              "examples": {
                "via_ru": {
                  "summary": "Exit DE, enter through Russia",
                  "value": {
                    "exit_country": "DE",
                    "entry_country": "RU"
                  }
                },
                "origin": {
                  "summary": "An exact origin from the catalogue",
                  "value": {
                    "origin_id": "0f2a9c4e-6b1d-4e7f-8a3c-000000000201"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The session, new or the one already held on this route.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectResponse"
                },
                "example": {
                  "session_id": "a51c6e3d-8f2b-4d9a-b7e1-000000000102",
                  "sub_token": "b7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0de",
                  "origin_id": "0f2a9c4e-6b1d-4e7f-8a3c-000000000201",
                  "edge_id": "17e6d2a8-4c9f-4b3e-9d5a-000000000301",
                  "total_price_per_gb_nano": 24000000,
                  "platform_fee_nano": 4000000
                }
              }
            }
          },
          "400": {
            "description": "A request we refuse, or a node that cannot take the session.\n\nCodes: `MISSING_PARAM`, `TRANSPORT_NOT_SUPPORTED`, `SESSION_LIMIT`, `NODE_OVERLOADED`, `NODE_AT_CAPACITY`, `TORRENT_GUARD_COOLDOWN`, `INSUFFICIENT_BALANCE`, `AGENT_OFFLINE`.\n\nA plain-text body (no JSON envelope) means the framework refused the request before our handler ran.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "MISSING_PARAM": {
                    "summary": "Neither exit_country nor origin_id was given",
                    "value": {
                      "error": {
                        "code": "MISSING_PARAM",
                        "message": "Either exit_country, origin_id, or service_id is required"
                      }
                    }
                  },
                  "TRANSPORT_NOT_SUPPORTED": {
                    "summary": "transport: \"hysteria2\" on a route that does not carry it",
                    "value": {
                      "error": {
                        "code": "TRANSPORT_NOT_SUPPORTED",
                        "message": "This route does not carry Hysteria2. Pick another route or use Auto."
                      }
                    }
                  },
                  "SESSION_LIMIT": {
                    "summary": "The subscriber already holds 50 live sessions",
                    "value": {
                      "error": {
                        "code": "SESSION_LIMIT",
                        "message": "Maximum 50 active sessions reached"
                      }
                    }
                  },
                  "NODE_OVERLOADED": {
                    "summary": "The named origin is refusing new sessions on its live load",
                    "value": {
                      "error": {
                        "code": "NODE_OVERLOADED",
                        "message": "This node is currently overloaded, try again later"
                      }
                    }
                  },
                  "NODE_AT_CAPACITY": {
                    "summary": "The named origin has reached its user limit",
                    "value": {
                      "error": {
                        "code": "NODE_AT_CAPACITY",
                        "message": "This node has reached its maximum session limit"
                      }
                    }
                  },
                  "TORRENT_GUARD_COOLDOWN": {
                    "summary": "Torrent Guard ended this subscriber's session on this origin in the last hour",
                    "value": {
                      "error": {
                        "code": "TORRENT_GUARD_COOLDOWN",
                        "message": "You were recently disconnected from this node for exceeding traffic limits. Try again in 1 hour or connect to a different node."
                      }
                    }
                  },
                  "INSUFFICIENT_BALANCE": {
                    "summary": "Your balance does not allow a new session (see GET /account)",
                    "value": {
                      "error": {
                        "code": "INSUFFICIENT_BALANCE",
                        "message": "Deposit TON or subscribe to a plan before connecting. This node charges per GB."
                      }
                    }
                  },
                  "AGENT_OFFLINE": {
                    "summary": "The command to the node could not be sent",
                    "value": {
                      "error": {
                        "code": "AGENT_OFFLINE",
                        "message": "Origin node agent is not connected"
                      }
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Failed to parse the request body as JSON: expected value at line 1 column 1"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "A disabled subscriber, or a private node not granted to you.\n\nCodes: `FORBIDDEN`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "FORBIDDEN": {
                    "summary": "The subscriber is disabled",
                    "value": {
                      "error": {
                        "code": "FORBIDDEN",
                        "message": "Your access has been disabled by your provider."
                      }
                    }
                  },
                  "FORBIDDEN_1": {
                    "summary": "A private origin not granted to you",
                    "value": {
                      "error": {
                        "code": "FORBIDDEN",
                        "message": "Access to this private node requires an invitation"
                      }
                    }
                  },
                  "FORBIDDEN_2": {
                    "summary": "A private edge not granted to you",
                    "value": {
                      "error": {
                        "code": "FORBIDDEN",
                        "message": "Access to this private edge node requires an invitation"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown subscriber, a named node that is not active, or no origin in the country can take a session right now.\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "Not your subscriber, or none by that external_id",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Subscriber not found"
                      }
                    }
                  },
                  "NOT_FOUND_1": {
                    "summary": "origin_id is not an active origin",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Specified origin node not found or not active"
                      }
                    }
                  },
                  "NOT_FOUND_2": {
                    "summary": "edge_id is not an active edge",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Specified edge node not found or not active"
                      }
                    }
                  },
                  "NOT_FOUND_3": {
                    "summary": "No origin in exit_country can take a session",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "No active origin nodes available for country 'XX'"
                      }
                    }
                  },
                  "NOT_FOUND_4": {
                    "summary": "exit_country with edge_id: no origin there is linked to the edge",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "No accessible origin in 'XX' is linked to this edge. Try a different entry, or ask the operator for access."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers/{external_id}/sessions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "operationId": "listSessions",
        "tags": [
          "Sessions"
        ],
        "summary": "The subscriber's live sessions",
        "description": "The subscriber's `active` and `pending` sessions, newest first, with the ids the configs call needs. Closed sessions are not listed. `price_per_gb_nano` here is the session's locked price **with** the network fee. Byte counters are cumulative for the whole session; for a time window use `/usage`. [Reference](https://proofkit.org/partner-docs#get-subscribersexternal_idsessions)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The live sessions, newest first; `[]` when there are none.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SubscriberSession"
                  }
                },
                "example": [
                  {
                    "session_id": "3d91b7c2-5e4a-4f8b-a2c6-000000000101",
                    "status": "active",
                    "origin_id": "0f2a9c4e-6b1d-4e7f-8a3c-000000000201",
                    "edge_id": null,
                    "price_per_gb_nano": 12000000,
                    "bytes": 1073741824,
                    "bytes_up": 214748364,
                    "bytes_down": 858993460,
                    "started_at": "2026-09-26T09:12:00Z"
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidPathParameter"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not yours, or does not exist.\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "Not your subscriber, or none by that external_id",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Subscriber not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers/{external_id}/sessions/{session_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        },
        {
          "$ref": "#/components/parameters/SessionId"
        }
      ],
      "delete": {
        "operationId": "closeSession",
        "tags": [
          "Sessions"
        ],
        "summary": "End one session",
        "description": "Ends one session and leaves the subscriber and their other sessions alone. The session's credential is removed from its node at once. The subscriber's olcRTC key on that node drops out at the next room-health pass only if they have no other live session there. Returns `204`, also when the session was already closed. A route in a subscription whose session you end is provisioned again on the next fetch. [Reference](https://proofkit.org/partner-docs#delete-subscribersexternal_idsessionssession_id)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "responses": {
          "204": {
            "description": "Ended (or already closed).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            }
          },
          "400": {
            "description": "A `session_id` that is not a UUID, or an `external_id` that is not valid UTF-8 once percent-decoded (plain text, no JSON envelope).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Invalid URL: Cannot parse `session_id` with value `not-a-uuid`: UUID parsing failed: invalid character: …"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not your subscriber, or not this subscriber's session.\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "Not your subscriber, or none by that external_id",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Subscriber not found"
                      }
                    }
                  },
                  "NOT_FOUND_1": {
                    "summary": "The session is not this subscriber's",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Session not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers/{external_id}/sessions/{session_id}/configs": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        },
        {
          "$ref": "#/components/parameters/SessionId"
        }
      ],
      "get": {
        "operationId": "getSessionConfigs",
        "tags": [
          "Sessions"
        ],
        "summary": "Every transport for one session",
        "description": "Every transport of one session (always three entries: Reality, Hysteria2, XHTTP), this session's own base64 bundle, and its olcRTC lines when the origin serves olcRTC. QR codes are included unless `qr=false`.\n\nOn an origin that serves olcRTC this call can reserve an olcRTC key for the subscriber, as `POST …/olcrtc` does: do not poll it for sessions you do not hand out. A closed session still returns links, but they no longer work. [Reference](https://proofkit.org/partner-docs#get-subscribersexternal_idsessionssession_idconfigs)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "parameters": [
          {
            "name": "qr",
            "in": "query",
            "required": false,
            "description": "`false` leaves out every `qr_svg` and `subscription_qr_svg`.",
            "schema": {
              "type": "boolean",
              "default": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The session's configs.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionConfigs"
                },
                "example": {
                  "session_id": "3d91b7c2-5e4a-4f8b-a2c6-000000000101",
                  "sub_transport": "vless_reality",
                  "sub_token": "b7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0de",
                  "subscription": "dmxlc3M6Ly8…",
                  "olcrtc_url": "olcrtc://telemost?vp8channel@https://telemost.yandex.ru/j/00000000000000#<key>$DE · olcRTC",
                  "olcrtc_urls": [
                    {
                      "carrier": "telemost",
                      "url": "olcrtc://telemost?vp8channel@https://telemost.yandex.ru/j/00000000000000#<key>$DE · olcRTC"
                    },
                    {
                      "carrier": "wbstream",
                      "url": "olcrtc://wbstream?vp8channel@<room code>#<key>$DE · olcRTC · WB"
                    }
                  ],
                  "transports": [
                    {
                      "transport": "vless_reality",
                      "label": "VLESS Reality",
                      "eligible": true,
                      "url": "vless://<uuid>@203.0.113.10:443?type=tcp&security=reality&sni=<sni>&fp=chrome&pbk=<pbk>&sid=<sid>&flow=xtls-rprx-vision#DE%20Direct"
                    },
                    {
                      "transport": "hysteria2",
                      "label": "Hysteria2",
                      "eligible": true,
                      "url": "hysteria2://<uuid>@203.0.113.10:30211?sni=<sni>&obfs=salamander&obfs-password=<pw>&insecure=0&pinSHA256=<pin>#DE%20Direct%20%C2%B7%20Hysteria2"
                    },
                    {
                      "transport": "vless_xhttp",
                      "label": "VLESS XHTTP",
                      "eligible": false,
                      "reason": "transport not offered by this route"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A `session_id` that is not a UUID, a `qr` that is not `true`/`false`, or an `external_id` that is not valid UTF-8 once percent-decoded (plain text, no JSON envelope).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Failed to deserialize query string: qr: provided string was not `true` or `false`"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not your subscriber or session, or the session's origin or entry edge has been suspended (or is gone).\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "Not your subscriber, or none by that external_id",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Subscriber not found"
                      }
                    }
                  },
                  "NOT_FOUND_1": {
                    "summary": "The session is not this subscriber's",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Session not found"
                      }
                    }
                  },
                  "NOT_FOUND_2": {
                    "summary": "The session's origin or entry edge has been suspended, or is gone",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Session has no reachable node"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers/{external_id}/olcrtc": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "post": {
        "operationId": "provisionOlcrtc",
        "tags": [
          "olcRTC"
        ],
        "summary": "Provision, connect and return olcRTC lines in one call",
        "description": "Creates the subscriber if needed (and **re-enables a disabled one**), finds or starts a session on an origin that serves Telemost, and returns the olcRTC lines: `olcrtc_url` is the Telemost line, `olcrtc_urls` one line per carrier, all on one key. Idempotent: called again, it returns the same live session.\n\n**Without subscriptions** the room's capacity is the only bound. **With subscriptions on** for your partner it follows the subscription's olcRTC rules: `403 OLCRTC_NOT_ENABLED` (nothing created or re-enabled) unless olcRTC is on for you, your share, one place per subscriber, a new place only on an exit your filters keep.\n\nThe body is optional: nothing, `{}`, or `{\"exit_country\": \"DE\"}`. A body sent with no `Content-Type` header is ignored (the pick is fleet-wide); a body with any other non-JSON `Content-Type` (curl's `-d` sends `application/x-www-form-urlencoded`) is a plain-text `415`; `Content-Type: application/json` with an empty body is a plain-text `400`. `NO_OLCRTC_NODE` is a normal answer: fall back to another transport. [Reference](https://proofkit.org/partner-docs#post-subscribersexternal_idolcrtc)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OlcrtcRequest"
              },
              "examples": {
                "anywhere": {
                  "summary": "Anywhere with a room",
                  "value": {}
                },
                "de": {
                  "summary": "Only exits in Germany",
                  "value": {
                    "exit_country": "DE"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The session and its olcRTC lines, with the node's capacity read after this subscriber's key was issued.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OlcrtcLink"
                },
                "example": {
                  "session_id": "3d91b7c2-5e4a-4f8b-a2c6-000000000101",
                  "origin_id": "0f2a9c4e-6b1d-4e7f-8a3c-000000000201",
                  "sub_token": "b7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0deb7f3c0de",
                  "olcrtc_url": "olcrtc://telemost?vp8channel@https://telemost.yandex.ru/j/00000000000000#<key>$DE · olcRTC",
                  "olcrtc_urls": [
                    {
                      "carrier": "telemost",
                      "url": "olcrtc://telemost?vp8channel@https://telemost.yandex.ru/j/00000000000000#<key>$DE · olcRTC"
                    },
                    {
                      "carrier": "wbstream",
                      "url": "olcrtc://wbstream?vp8channel@<room code>#<key>$DE · olcRTC · WB"
                    },
                    {
                      "carrier": "salutejazz",
                      "url": "olcrtc://salutejazz?datachannel@<code>:<password>#<key>$DE · olcRTC · SJ"
                    }
                  ],
                  "slots_total": 8,
                  "slots_free": 4,
                  "keys_total": 16,
                  "keys_free": 9
                }
              }
            }
          },
          "400": {
            "description": "A request we refuse, or a connect refusal.\n\nCodes: `INVALID_EXTERNAL_ID`, `PARTNER_NOT_BILLABLE`, `SESSION_LIMIT`, `NODE_OVERLOADED`, `NODE_AT_CAPACITY`, `TORRENT_GUARD_COOLDOWN`, `INSUFFICIENT_BALANCE`, `AGENT_OFFLINE`.\n\nA plain-text body (no JSON envelope) means the framework refused the request before our handler ran.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_EXTERNAL_ID": {
                    "summary": "external_id empty, blank or over 128 bytes after trimming",
                    "value": {
                      "error": {
                        "code": "INVALID_EXTERNAL_ID",
                        "message": "external_id must be 1..=128 characters"
                      }
                    }
                  },
                  "PARTNER_NOT_BILLABLE": {
                    "summary": "Your billing account is not set up yet",
                    "value": {
                      "error": {
                        "code": "PARTNER_NOT_BILLABLE",
                        "message": "Partner has no billing account yet — set wholesale terms first"
                      }
                    }
                  },
                  "SESSION_LIMIT": {
                    "summary": "The subscriber already holds 50 live sessions",
                    "value": {
                      "error": {
                        "code": "SESSION_LIMIT",
                        "message": "Maximum 50 active sessions reached"
                      }
                    }
                  },
                  "NODE_OVERLOADED": {
                    "summary": "The named origin is refusing new sessions on its live load",
                    "value": {
                      "error": {
                        "code": "NODE_OVERLOADED",
                        "message": "This node is currently overloaded, try again later"
                      }
                    }
                  },
                  "NODE_AT_CAPACITY": {
                    "summary": "The named origin has reached its user limit",
                    "value": {
                      "error": {
                        "code": "NODE_AT_CAPACITY",
                        "message": "This node has reached its maximum session limit"
                      }
                    }
                  },
                  "TORRENT_GUARD_COOLDOWN": {
                    "summary": "Torrent Guard ended this subscriber's session on this origin in the last hour",
                    "value": {
                      "error": {
                        "code": "TORRENT_GUARD_COOLDOWN",
                        "message": "You were recently disconnected from this node for exceeding traffic limits. Try again in 1 hour or connect to a different node."
                      }
                    }
                  },
                  "INSUFFICIENT_BALANCE": {
                    "summary": "Your balance does not allow a new session (see GET /account)",
                    "value": {
                      "error": {
                        "code": "INSUFFICIENT_BALANCE",
                        "message": "Deposit TON or subscribe to a plan before connecting. This node charges per GB."
                      }
                    }
                  },
                  "AGENT_OFFLINE": {
                    "summary": "The command to the node could not be sent",
                    "value": {
                      "error": {
                        "code": "AGENT_OFFLINE",
                        "message": "Origin node agent is not connected"
                      }
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Failed to parse the request body as JSON: EOF while parsing a value at line 1 column 0"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "olcRTC not switched on (with subscriptions on), the trial's cap, or (with subscriptions on) a subscriber whose olcRTC place is on a private node no longer granted to you.\n\nCodes: `OLCRTC_NOT_ENABLED`, `TRIAL_LIMIT`, `FORBIDDEN`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "OLCRTC_NOT_ENABLED": {
                    "summary": "Subscriptions are on for your partner and olcRTC is not; nothing was created or re-enabled",
                    "value": {
                      "error": {
                        "code": "OLCRTC_NOT_ENABLED",
                        "message": "olcRTC is not switched on for this partner; ask ProofKit to enable it"
                      }
                    }
                  },
                  "TRIAL_LIMIT": {
                    "summary": "A new subscriber past the free trial's cap",
                    "value": {
                      "error": {
                        "code": "TRIAL_LIMIT",
                        "message": "The free trial covers 10 subscribers. To add more, top up the balance in the partner dashboard and ask ProofKit to end the trial."
                      }
                    }
                  },
                  "FORBIDDEN": {
                    "summary": "With subscriptions on: the subscriber's place is on a private node whose grant to you was revoked",
                    "value": {
                      "error": {
                        "code": "FORBIDDEN",
                        "message": "Access to this private node requires an invitation"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The origin was not active when the session was made. After a fresh pick this is a race, and a retry picks again. With subscriptions on, a subscriber holding its olcRTC place on that origin and no live session there is answered from that origin only, so it gets this on every call until the node is active again or the place is released (`POST …/reset` releases it, with everything else the subscriber holds).\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "The origin was not active when the session was made",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Specified origin node not found or not active"
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "description": "A body whose `Content-Type` is present and is not JSON (plain text, no JSON envelope). A body with no `Content-Type` header at all is not refused: it is ignored.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Expected request with `Content-Type: application/json`"
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "No olcRTC line right now.\n\n`NO_OLCRTC_NODE`: retry later, or fall back to another transport.\n\n`OLCRTC_UNAVAILABLE` on a session the subscriber already holds (the door without subscriptions always reuses a live session on an origin serving Telemost): retries reuse it and answer the same until its room frees a place. End that session (`DELETE …/sessions/{session_id}`) to be placed elsewhere, pass another `exit_country`, or fall back.\n\nCodes: `NO_OLCRTC_NODE`, `OLCRTC_UNAVAILABLE`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NO_OLCRTC_NODE": {
                    "summary": "No origin serving Telemost has an online agent and a free slot and key",
                    "value": {
                      "error": {
                        "code": "NO_OLCRTC_NODE",
                        "message": "No olcRTC-capable origin is available with capacity"
                      }
                    }
                  },
                  "NO_OLCRTC_NODE_1": {
                    "summary": "With subscriptions on: your share is in use",
                    "value": {
                      "error": {
                        "code": "NO_OLCRTC_NODE",
                        "message": "Your subscribers in olcRTC rooms fill your olcRTC share; retry when one leaves"
                      }
                    }
                  },
                  "NO_OLCRTC_NODE_2": {
                    "summary": "With subscriptions on: the subscriber's place is outside this request",
                    "value": {
                      "error": {
                        "code": "NO_OLCRTC_NODE",
                        "message": "This subscriber holds its olcRTC place on an origin outside this request (another exit, or no Telemost served there right now)"
                      }
                    }
                  },
                  "OLCRTC_UNAVAILABLE": {
                    "summary": "The session's node could not give a line (no free slot or key, or its room went away); on a reused session a retry answers the same until the room frees a place",
                    "value": {
                      "error": {
                        "code": "OLCRTC_UNAVAILABLE",
                        "message": "The session's origin has no olcRTC room"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/usage": {
      "get": {
        "operationId": "getUsage",
        "tags": [
          "Usage"
        ],
        "summary": "Usage and cost for every subscriber over a window",
        "description": "Traffic and cost per subscriber over the half-open window `[from, to)`, for reconciliation. Every subscriber you created is listed, including those who moved nothing and disabled ones, ordered by `bytes`, largest first. No pagination. Figures are the metered intervals, not session counters; `cost_nano` is copied from the charge itself, `0` on `exempt` and inside an active trial. [Reference](https://proofkit.org/partner-docs#get-usage)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "RFC 3339 start of the window, inclusive. Defaults to 30 days before `to`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-09-01T00:00:00Z"
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "RFC 3339 end of the window, exclusive. Defaults to now.",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-10-01T00:00:00Z"
          }
        ],
        "responses": {
          "200": {
            "description": "The report.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageReport"
                },
                "example": {
                  "from": "2026-09-01T00:00:00Z",
                  "to": "2026-10-01T00:00:00Z",
                  "total_bytes": 1073741824,
                  "total_bytes_up": 214748364,
                  "total_bytes_down": 858993460,
                  "total_cost_nano": 12000000,
                  "subscribers": [
                    {
                      "external_id": "user-42",
                      "bytes": 1073741824,
                      "bytes_up": 214748364,
                      "bytes_down": 858993460,
                      "cost_nano": 12000000
                    },
                    {
                      "external_id": "user-43",
                      "bytes": 0,
                      "bytes_up": 0,
                      "bytes_down": 0,
                      "cost_nano": 0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`from` after `to`.\n\nCodes: `INVALID_RANGE`.\n\nA plain-text body (no JSON envelope) means the framework refused the request before our handler ran.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_RANGE": {
                    "summary": "from is after to",
                    "value": {
                      "error": {
                        "code": "INVALID_RANGE",
                        "message": "from must not be after to"
                      }
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Failed to deserialize query string: from: input contains invalid characters"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/subscribers/{external_id}/usage": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "operationId": "getSubscriberUsage",
        "tags": [
          "Usage"
        ],
        "summary": "Usage and cost for one subscriber",
        "description": "The same figures as `/usage` for one subscriber, for a quota check. We enforce no per-subscriber quota: poll this and call `DELETE /subscribers/{external_id}` when yours trips. [Reference](https://proofkit.org/partner-docs#get-subscribersexternal_idusage)",
        "security": [
          {
            "partnerKey": []
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "RFC 3339 start of the window, inclusive. Defaults to 30 days before `to`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-09-01T00:00:00Z"
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "RFC 3339 end of the window, exclusive. Defaults to now.",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-10-01T00:00:00Z"
          }
        ],
        "responses": {
          "200": {
            "description": "The subscriber's usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriberUsageReport"
                },
                "example": {
                  "external_id": "user-42",
                  "from": "2026-09-01T00:00:00Z",
                  "to": "2026-10-01T00:00:00Z",
                  "bytes": 1073741824,
                  "bytes_up": 214748364,
                  "bytes_down": 858993460,
                  "cost_nano": 12000000
                }
              }
            }
          },
          "400": {
            "description": "`from` after `to`.\n\nCodes: `INVALID_RANGE`.\n\nA plain-text body (no JSON envelope) means the framework refused the request before our handler ran.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_RANGE": {
                    "summary": "from is after to",
                    "value": {
                      "error": {
                        "code": "INVALID_RANGE",
                        "message": "from must not be after to"
                      }
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Failed to deserialize query string: from: input contains invalid characters"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not yours, or does not exist.\n\nCodes: `NOT_FOUND`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "summary": "Not your subscriber, or none by that external_id",
                    "value": {
                      "error": {
                        "code": "NOT_FOUND",
                        "message": "Subscriber not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "partnerKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Partner-Key",
        "description": "Your partner key: `pk_`, 8 hex characters, `_`, 48 hex characters. Issued by ProofKit or on the Partner page; shown once. A rotation keeps the previous key working for 24 hours."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "The one error envelope. Dispatch on `error.code`; the message is for people.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable code, e.g. `INVALID_PARTNER_KEY`."
              },
              "message": {
                "type": "string",
                "description": "Human-readable message."
              },
              "details": {
                "description": "Reserved. No endpoint on this surface sets it today."
              }
            }
          }
        }
      },
      "TrialUsage": {
        "type": "object",
        "required": [
          "until",
          "max_subscribers",
          "subscribers",
          "max_bytes",
          "bytes_used",
          "active"
        ],
        "properties": {
          "until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the free traffic ends; `null` if the trial was never started."
          },
          "max_subscribers": {
            "type": "integer",
            "format": "int32",
            "description": "How many subscribers the trial covers."
          },
          "subscribers": {
            "type": "integer",
            "format": "int64",
            "description": "Subscribers you have created, disabled ones included."
          },
          "max_bytes": {
            "type": "integer",
            "format": "int64",
            "description": "The trial's traffic allowance in bytes."
          },
          "bytes_used": {
            "type": "integer",
            "format": "int64",
            "description": "Traffic counted against it so far."
          },
          "active": {
            "type": "boolean",
            "description": "`true` while every limit holds and traffic is free. `false`: you are charged like `prepaid` from the next traffic report on."
          }
        }
      },
      "PartnerAccount": {
        "type": "object",
        "required": [
          "partner_id",
          "billable",
          "billing_mode",
          "billing_plan",
          "balance_nano",
          "credit_limit_nano",
          "suspended",
          "rate_limit_per_min",
          "wallet",
          "escrow_address",
          "deposit_link"
        ],
        "properties": {
          "partner_id": {
            "type": "string",
            "format": "uuid",
            "description": "Your partner id."
          },
          "billable": {
            "type": "boolean",
            "description": "Whether your billing account exists. `false` means `POST /subscribers` answers `PARTNER_NOT_BILLABLE`."
          },
          "billing_mode": {
            "type": "string",
            "enum": [
              "free",
              "metered"
            ],
            "description": "`free` while ProofKit covers your traffic (the `exempt` plan), otherwise `metered`, including during a trial. Read `billing_plan` and `trial.active` for the plan."
          },
          "billing_plan": {
            "type": "string",
            "enum": [
              "exempt",
              "trial",
              "prepaid"
            ],
            "description": "`exempt` (ProofKit covers your traffic), `trial` (free within the trial's limits) or `prepaid` (charged from your own TON deposit)."
          },
          "balance_nano": {
            "type": "integer",
            "format": "int64",
            "description": "Balance of your billing account in nanoTON: deposits + unsettled earnings + referral credits − traffic charges − withdrawals. The connect gate, the session enforcer and the Partner page also subtract stake locked in nodes your billing account owns (none unless it runs nodes)."
          },
          "credit_limit_nano": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "How far below zero the balance may go, as a positive magnitude. Always `0` on `prepaid`."
          },
          "suspended": {
            "type": "boolean",
            "description": "Always `false` when you can read it: a disabled partner's key gets `401`."
          },
          "rate_limit_per_min": {
            "type": "integer",
            "examples": [
              300
            ],
            "description": "The general per-key budget. The subscription bucket's 1,200 is not reported here."
          },
          "wallet": {
            "type": [
              "string",
              "null"
            ],
            "description": "The TON wallet linked to your billing account, `null` before you link one on the Partner page."
          },
          "escrow_address": {
            "type": "string",
            "description": "The ProofKit escrow contract your deposits go to."
          },
          "deposit_link": {
            "type": [
              "string",
              "null"
            ],
            "description": "`ton://transfer/<escrow>?text=deposit`; `null` until a wallet is linked."
          },
          "trial": {
            "$ref": "#/components/schemas/TrialUsage",
            "description": "Present only while `billing_plan` is `trial`; omitted otherwise."
          }
        }
      },
      "CatalogueNode": {
        "type": "object",
        "required": [
          "node_id",
          "node_type",
          "country",
          "price_per_gb_nano",
          "total_price_per_gb_nano",
          "fee_bps",
          "supported_transports",
          "olcrtc_carriers",
          "is_public",
          "sessions_count",
          "max_users",
          "max_users_effective",
          "agent_online"
        ],
        "properties": {
          "node_id": {
            "type": "string",
            "format": "uuid",
            "description": "Use as `origin_id` or `edge_id` in `connect`."
          },
          "node_type": {
            "type": "string",
            "enum": [
              "origin",
              "edge"
            ],
            "description": "`origin` (the exit to the internet) or `edge` (an entry that relays to an origin)."
          },
          "country": {
            "type": "string",
            "examples": [
              "DE"
            ],
            "description": "Two-letter country code, as `exit_country` / `entry_country` expect it."
          },
          "price_per_gb_nano": {
            "type": "integer",
            "format": "int64",
            "description": "The node's price per GB for you, with any price agreed for you applied, **before** the network fee."
          },
          "total_price_per_gb_nano": {
            "type": "integer",
            "format": "int64",
            "description": "What a GB through this node costs you: `price_per_gb_nano` plus the network fee."
          },
          "fee_bps": {
            "type": "integer",
            "format": "int64",
            "description": "The network fee in basis points: `2000` = 20%."
          },
          "entry_region": {
            "type": "string",
            "enum": [
              "RU"
            ],
            "description": "`RU` on an edge in Russia. Absent on every other node, including an origin in Russia."
          },
          "supported_transports": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Any of `vless_reality`, `hysteria2`, `vless_xhttp`, `olcrtc`."
          },
          "olcrtc_carriers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The olcRTC carriers the node serves right now, Telemost first: any of `telemost`, `wbstream`, `salutejazz`. `[]` when it serves none."
          },
          "olcrtc_slots_total": {
            "type": "integer",
            "format": "int32",
            "description": "olcRTC slots: key holders counted as present at once, all the node's carrier rooms together. Absent on a node with no olcRTC carrier."
          },
          "olcrtc_slots_free": {
            "type": "integer",
            "format": "int32",
            "description": "Free olcRTC slots right now. Absent with `olcrtc_slots_total`."
          },
          "olcrtc_keys_total": {
            "type": "integer",
            "format": "int32",
            "description": "olcRTC keys the node may hold. Absent with the slots."
          },
          "olcrtc_keys_free": {
            "type": "integer",
            "format": "int32",
            "description": "At most how many olcRTC keys it will still hand out (an upper bound). Absent with the slots."
          },
          "is_public": {
            "type": "boolean",
            "description": "`false` for a private node granted to you."
          },
          "sessions_count": {
            "type": "integer",
            "format": "int64",
            "description": "Active sessions on the node, as origin or edge, idle ones included. Not what `max_users_effective` is checked against."
          },
          "max_users": {
            "type": "integer",
            "format": "int32",
            "description": "The node's own user limit, `0` when it sets none."
          },
          "max_users_effective": {
            "type": "integer",
            "format": "int32",
            "description": "The limit actually enforced, against live users (sessions that moved traffic in the last 15 minutes); origins only."
          },
          "agent_online": {
            "type": "boolean",
            "description": "Whether the node's agent is connected to our servers right now."
          }
        }
      },
      "CreateSubscriberRequest": {
        "type": "object",
        "required": [
          "external_id"
        ],
        "properties": {
          "external_id": {
            "type": "string",
            "minLength": 1,
            "examples": [
              "user-42"
            ],
            "description": "Your own identifier for the person: 1 to 128 **bytes** of UTF-8 after surrounding whitespace is trimmed, unique within your partner (JSON Schema counts characters, so the byte limit is not in the schema). The trimmed value is what we store and what you use in paths. Avoid `.` and `..`: they are accepted, but browsers (this explorer included) and many HTTP clients drop such path segments, even percent-encoded, so a path call for them reaches another endpoint."
          }
        }
      },
      "Subscriber": {
        "type": "object",
        "required": [
          "external_id",
          "account_id",
          "sub_token",
          "created",
          "disabled",
          "created_at"
        ],
        "properties": {
          "external_id": {
            "type": "string",
            "description": "Your identifier, trimmed."
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "The subscriber's ProofKit account."
          },
          "sub_token": {
            "type": [
              "string",
              "null"
            ],
            "description": "The capability behind the hosted URLs `https://proofkit.org/sub/<sub_token>`: anyone holding it can fetch the subscription without a key. A secret. Changes only on `reset`."
          },
          "created": {
            "type": "boolean",
            "description": "Whether this call made the subscriber. The only way to tell: the status is `200` either way."
          },
          "disabled": {
            "type": "boolean",
            "description": "Whether the subscriber is disabled."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the subscriber was created."
          }
        }
      },
      "SubscriberListItem": {
        "type": "object",
        "required": [
          "external_id",
          "account_id",
          "disabled",
          "created_at"
        ],
        "properties": {
          "external_id": {
            "type": "string",
            "description": "Your identifier, trimmed."
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "The subscriber's ProofKit account."
          },
          "disabled": {
            "type": "boolean",
            "description": "Whether the subscriber is disabled."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the subscriber was created."
          }
        }
      },
      "SubscriberList": {
        "type": "object",
        "required": [
          "subscribers",
          "next_cursor"
        ],
        "properties": {
          "subscribers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubscriberListItem"
            },
            "description": "This page, oldest first. Empty when nothing matches."
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass as `cursor` for the next page; `null` on the last page. Opaque: do not build or change one."
          }
        }
      },
      "ConnectRequest": {
        "type": "object",
        "description": "One of `exit_country` (or its alias `country`) or `origin_id` is required. Every field may be `null`, which counts as absent. When several name the same thing, one wins and the others are ignored without an error: `service_id` over `origin_id` over `exit_country`, `exit_country` over `country`, and `edge_id` over `entry_country`.",
        "properties": {
          "exit_country": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "DE"
            ],
            "description": "Two-letter country to exit from, matched exactly against the catalogue's `country`. We pick the least-loaded active origin there."
          },
          "origin_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "An exact origin from the catalogue, instead of a country."
          },
          "entry_country": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "RU"
            ],
            "description": "Optional entry country, e.g. `RU`: we pick an edge there linked to the chosen origin. If none is available the session is created direct, without an error."
          },
          "edge_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Optional exact edge. With `exit_country`, the origin is chosen among those linked to it. With `origin_id`, the link is not checked."
          },
          "transport": {
            "type": [
              "string",
              "null"
            ],
            "description": "Only `hysteria2` changes anything: the route must carry Hysteria2, or the call answers `TRANSPORT_NOT_SUPPORTED`. Any other value is ignored; a session carries every transport its route supports."
          },
          "service_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "deprecated": true,
            "description": "Legacy, from before the catalogue; do not use it."
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Legacy alias of `exit_country`."
          }
        }
      },
      "ConnectResponse": {
        "type": "object",
        "required": [
          "session_id",
          "sub_token",
          "origin_id",
          "edge_id",
          "total_price_per_gb_nano",
          "platform_fee_nano"
        ],
        "properties": {
          "session_id": {
            "type": "string",
            "format": "uuid",
            "description": "The session."
          },
          "sub_token": {
            "type": "string",
            "description": "As in the subscriber object."
          },
          "origin_id": {
            "type": "string",
            "format": "uuid",
            "description": "The exit the session runs on."
          },
          "edge_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The entry, `null` on a direct route. Check it after `entry_country`."
          },
          "total_price_per_gb_nano": {
            "type": "integer",
            "format": "int64",
            "description": "The session's price per GB, the network fee included, locked when it started (a later drop reaches it, a rise does not)."
          },
          "platform_fee_nano": {
            "type": "integer",
            "format": "int64",
            "description": "The fee part of that price."
          }
        }
      },
      "SubscriberSession": {
        "type": "object",
        "required": [
          "session_id",
          "status",
          "origin_id",
          "edge_id",
          "price_per_gb_nano",
          "bytes",
          "bytes_up",
          "bytes_down",
          "started_at"
        ],
        "properties": {
          "session_id": {
            "type": "string",
            "format": "uuid",
            "description": "The session."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "pending"
            ],
            "description": "`pending` until its node confirms it."
          },
          "origin_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The exit."
          },
          "edge_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The entry, `null` on a direct route."
          },
          "price_per_gb_nano": {
            "type": "integer",
            "format": "int64",
            "description": "The session's current locked price **with** the network fee (unlike the catalogue's field of the same name)."
          },
          "bytes": {
            "type": "integer",
            "format": "int64",
            "description": "Cumulative total, `bytes_up + bytes_down`."
          },
          "bytes_up": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes the subscriber SENT (client to internet)."
          },
          "bytes_down": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes the subscriber RECEIVED (internet to client)."
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the session started."
          }
        }
      },
      "OlcrtcUrl": {
        "type": "object",
        "required": [
          "carrier",
          "url"
        ],
        "properties": {
          "carrier": {
            "type": "string",
            "description": "`telemost`, `wbstream` or `salutejazz`."
          },
          "url": {
            "type": "string",
            "description": "`olcrtc://<carrier>?<transport>@<room>#<key>$<label>`."
          }
        }
      },
      "SessionTransportConfig": {
        "type": "object",
        "required": [
          "transport",
          "label",
          "eligible"
        ],
        "properties": {
          "transport": {
            "type": "string",
            "enum": [
              "vless_reality",
              "hysteria2",
              "vless_xhttp"
            ],
            "description": "The transport."
          },
          "label": {
            "type": "string",
            "description": "Display name: `VLESS Reality`, `Hysteria2` or `VLESS XHTTP`."
          },
          "eligible": {
            "type": "boolean",
            "description": "Whether the route carries it. Reality always does."
          },
          "url": {
            "type": "string",
            "description": "The share link. Present only when eligible."
          },
          "qr_svg": {
            "type": "string",
            "description": "QR of `url` as SVG. Present when eligible, unless `qr=false`."
          },
          "reason": {
            "type": "string",
            "examples": [
              "transport not offered by this route"
            ],
            "description": "Why it is not eligible. Present only when not eligible."
          }
        }
      },
      "SessionConfigs": {
        "type": "object",
        "required": [
          "session_id",
          "sub_transport",
          "sub_token",
          "subscription",
          "transports"
        ],
        "properties": {
          "session_id": {
            "type": "string",
            "format": "uuid",
            "description": "The session."
          },
          "sub_transport": {
            "type": "string",
            "description": "The subscriber account's own mode for its older hosted list; `vless_reality` unless changed. This API cannot change it."
          },
          "sub_token": {
            "type": [
              "string",
              "null"
            ],
            "description": "As in the subscriber object."
          },
          "subscription": {
            "type": "string",
            "description": "Base64 of this session's eligible links (Reality, then Hysteria2 and XHTTP where eligible): this one session's bundle, not the hosted subscription."
          },
          "subscription_qr_svg": {
            "type": "string",
            "description": "QR of `subscription`; omitted with `qr=false`."
          },
          "olcrtc_url": {
            "type": "string",
            "description": "The Telemost olcRTC line, when the origin serves Telemost and the subscriber may have a line there. Omitted otherwise."
          },
          "olcrtc_urls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OlcrtcUrl"
            },
            "description": "One line per carrier the origin serves, Telemost first, all on one key. Omitted when there are none."
          },
          "transports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SessionTransportConfig"
            },
            "minItems": 3,
            "maxItems": 3,
            "description": "Always three entries in this order: Reality, Hysteria2, XHTTP."
          }
        }
      },
      "OlcrtcRequest": {
        "type": "object",
        "properties": {
          "exit_country": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "DE"
            ],
            "description": "Restricts the pick to one exit country. Absent: anywhere with a room."
          }
        }
      },
      "OlcrtcLink": {
        "type": "object",
        "required": [
          "session_id",
          "origin_id",
          "sub_token",
          "olcrtc_url",
          "olcrtc_urls",
          "slots_total",
          "slots_free",
          "keys_total",
          "keys_free"
        ],
        "properties": {
          "session_id": {
            "type": "string",
            "format": "uuid",
            "description": "The session the lines belong to."
          },
          "origin_id": {
            "type": "string",
            "format": "uuid",
            "description": "The node the subscriber was put on."
          },
          "sub_token": {
            "type": [
              "string",
              "null"
            ],
            "description": "As in the subscriber object."
          },
          "olcrtc_url": {
            "type": "string",
            "description": "The Telemost line."
          },
          "olcrtc_urls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OlcrtcUrl"
            },
            "description": "One line per carrier the node serves, Telemost first. Same node, same key.",
            "minItems": 1
          },
          "slots_total": {
            "type": "integer",
            "format": "int32",
            "description": "olcRTC slots of this node."
          },
          "slots_free": {
            "type": "integer",
            "format": "int32",
            "description": "Free slots, read after this subscriber's key was issued. `0` is normal."
          },
          "keys_total": {
            "type": "integer",
            "format": "int32",
            "description": "Keys this node may hold."
          },
          "keys_free": {
            "type": "integer",
            "format": "int32",
            "description": "Keys it will still hand out; already counts this subscriber's key."
          }
        }
      },
      "SubscriptionEntry": {
        "type": "object",
        "required": [
          "url",
          "transport",
          "remark",
          "session_id",
          "origin_id",
          "edge_id",
          "exit_country",
          "entry_country"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "The share link or olcRTC line, as the app imports it."
          },
          "transport": {
            "type": "string",
            "description": "`vless_reality`, `hysteria2`, `vless_xhttp` or `olcrtc`."
          },
          "carrier": {
            "type": "string",
            "description": "The olcRTC carrier; present only on olcRTC lines."
          },
          "remark": {
            "type": "string",
            "description": "The line's name as the app shows it (decoded)."
          },
          "session_id": {
            "type": "string",
            "format": "uuid",
            "description": "The session behind the line."
          },
          "origin_id": {
            "type": "string",
            "format": "uuid",
            "description": "The exit."
          },
          "edge_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The entry, `null` on a direct route."
          },
          "exit_country": {
            "type": "string",
            "description": "The exit's country."
          },
          "entry_country": {
            "type": [
              "string",
              "null"
            ],
            "description": "The entry's country, `null` on a direct route."
          }
        }
      },
      "SubscriptionJson": {
        "type": "object",
        "required": [
          "entries",
          "upload",
          "download"
        ],
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubscriptionEntry"
            },
            "description": "The lines in order."
          },
          "upload": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes sent this calendar month (UTC)."
          },
          "download": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes received this calendar month (UTC)."
          }
        }
      },
      "SubscriberUsage": {
        "type": "object",
        "required": [
          "external_id",
          "bytes",
          "bytes_up",
          "bytes_down",
          "cost_nano"
        ],
        "properties": {
          "external_id": {
            "type": "string",
            "description": "Your identifier."
          },
          "bytes": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes in the window."
          },
          "bytes_up": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes the subscriber SENT (client to internet)."
          },
          "bytes_down": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes the subscriber RECEIVED (internet to client)."
          },
          "cost_nano": {
            "type": "integer",
            "format": "int64",
            "description": "What the traffic cost you, copied from the charges."
          }
        }
      },
      "UsageReport": {
        "type": "object",
        "required": [
          "from",
          "to",
          "total_bytes",
          "total_bytes_up",
          "total_bytes_down",
          "total_cost_nano",
          "subscribers"
        ],
        "properties": {
          "from": {
            "type": "string",
            "format": "date-time",
            "description": "Start of the window, inclusive."
          },
          "to": {
            "type": "string",
            "format": "date-time",
            "description": "End of the window, exclusive."
          },
          "total_bytes": {
            "type": "integer",
            "format": "int64",
            "description": "Sum of `bytes`."
          },
          "total_bytes_up": {
            "type": "integer",
            "format": "int64",
            "description": "Sum of `bytes_up`."
          },
          "total_bytes_down": {
            "type": "integer",
            "format": "int64",
            "description": "Sum of `bytes_down`."
          },
          "total_cost_nano": {
            "type": "integer",
            "format": "int64",
            "description": "Sum of `cost_nano`."
          },
          "subscribers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubscriberUsage"
            },
            "description": "Every subscriber you created, largest `bytes` first."
          }
        }
      },
      "SubscriberUsageReport": {
        "type": "object",
        "required": [
          "external_id",
          "from",
          "to",
          "bytes",
          "bytes_up",
          "bytes_down",
          "cost_nano"
        ],
        "properties": {
          "external_id": {
            "type": "string",
            "description": "Your identifier."
          },
          "from": {
            "type": "string",
            "format": "date-time",
            "description": "Start of the window, inclusive."
          },
          "to": {
            "type": "string",
            "format": "date-time",
            "description": "End of the window, exclusive."
          },
          "bytes": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes in the window."
          },
          "bytes_up": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes the subscriber SENT (client to internet)."
          },
          "bytes_down": {
            "type": "integer",
            "format": "int64",
            "description": "Bytes the subscriber RECEIVED (internet to client)."
          },
          "cost_nano": {
            "type": "integer",
            "format": "int64",
            "description": "What the traffic cost you."
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "A missing, malformed or unknown key, or the key of a disabled partner: deliberately indistinguishable. The framework's checks of the body, path and query run before the key check: a request that fails one gets that plain-text `400`, `415` or `422`, key or no key.\n\nCodes: `INVALID_PARTNER_KEY`.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "INVALID_PARTNER_KEY",
                "message": "Missing or invalid partner API key"
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limited. Budgets are counted per key prefix (`pk_` and its 8 hex characters) in a fixed 60-second window: 1,200 per minute for subscription fetches, 300 for everything else. The count is taken before the key is checked. The per-key refusal adds `Retry-After`. A `429` with **no** `X-RateLimit-*` headers comes from the per-IP flood guard (6,000 per minute per IP on `/api/v1/partner/*`), which runs first.\n\nCodes: `RATE_LIMITED`.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "Rate limit exceeded: 300 requests per 60 seconds"
              }
            }
          }
        }
      },
      "RateLimitedSubscription": {
        "description": "Rate limited. Budgets are counted per key prefix (`pk_` and its 8 hex characters) in a fixed 60-second window: 1,200 per minute for subscription fetches, 300 for everything else. The count is taken before the key is checked. The per-key refusal adds `Retry-After`. A `429` with **no** `X-RateLimit-*` headers comes from the per-IP flood guard (6,000 per minute per IP on `/api/v1/partner/*`), which runs first.\n\nCodes: `RATE_LIMITED`.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "Rate limit exceeded: 1200 requests per 60 seconds"
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Our failure. If the rate limiter's store is unreachable, the per-IP layer answers this with \"Rate limiter unavailable\", without `X-RateLimit-*` (it runs before the per-key layer).\n\nCodes: `INTERNAL_ERROR`.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "INTERNAL_ERROR",
                "message": "Database error"
              }
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "A body over 1 MB. Our proxy refuses it before it reaches the API, so the answer is its HTML page: no JSON envelope, no `X-RateLimit-*`, no `X-Request-ID`. Every body this API takes is far smaller.",
        "content": {
          "text/html": {
            "schema": {
              "type": "string"
            },
            "example": "<html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "A JSON body sent without `Content-Type: application/json` (plain text, no JSON envelope).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "text/plain": {
            "schema": {
              "type": "string"
            },
            "example": "Expected request with `Content-Type: application/json`"
          }
        }
      },
      "UnprocessableEntity": {
        "description": "The JSON is valid but a field is missing or has the wrong type (plain text, no JSON envelope).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "text/plain": {
            "schema": {
              "type": "string"
            },
            "example": "Failed to deserialize the JSON body into the target type: missing field `external_id` at line 1 column 2"
          }
        }
      },
      "InvalidPathParameter": {
        "description": "An `external_id` that is not valid UTF-8 once percent-decoded, e.g. `%FF` (plain text, no JSON envelope).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "text/plain": {
            "schema": {
              "type": "string"
            },
            "example": "Invalid URL: Invalid UTF-8 in `external_id`"
          }
        }
      },
      "UnknownEndpoint": {
        "description": "Any path under `/api/v1/partner` that is not listed here.\n\nCodes: `NOT_FOUND`.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "NOT_FOUND",
                "message": "Unknown partner API endpoint"
              }
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "A listed path called with a method it does not have.\n\nCodes: `METHOD_NOT_ALLOWED`.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Allow": {
            "description": "The methods this path answers, comma-separated (HEAD is included wherever GET is).",
            "schema": {
              "type": "string"
            },
            "example": "GET,HEAD"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "METHOD_NOT_ALLOWED",
                "message": "This partner API endpoint does not support that method"
              }
            }
          }
        }
      }
    },
    "parameters": {
      "ExternalId": {
        "name": "external_id",
        "in": "path",
        "required": true,
        "description": "Your identifier for the subscriber, percent-encoded (a space, `/`, `?`, `#`, `%` and any non-ASCII letter). Another partner's subscriber answers `404`, the same as one that does not exist. An id of `.` or `..` is reachable only from a client that sends the segment as it is: browsers, this explorer and many HTTP clients remove it.",
        "schema": {
          "type": "string",
          "minLength": 1
        },
        "example": "user-42"
      },
      "SessionId": {
        "name": "session_id",
        "in": "path",
        "required": true,
        "description": "A session id from `GET /subscribers/{external_id}/sessions`.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "3d91b7c2-5e4a-4f8b-a2c6-000000000101"
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "The budget of the bucket this request was counted in: 300 per minute, or 1,200 for subscription fetches. Absent, like the other two, on a `429` or `500` from the per-IP layer and on the proxy's `413`.",
        "schema": {
          "type": "integer"
        },
        "example": 300
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in the current 60-second window.",
        "schema": {
          "type": "integer"
        },
        "example": 297
      },
      "X-RateLimit-Reset": {
        "description": "Seconds until the window resets.",
        "schema": {
          "type": "integer"
        },
        "example": 42
      },
      "X-Request-ID": {
        "description": "Our id for this request; send your own and we echo it. Quote it when you contact us about a call.",
        "schema": {
          "type": "string"
        }
      },
      "Retry-After": {
        "description": "On a per-key `429`: seconds to wait, equal to `X-RateLimit-Reset`.",
        "schema": {
          "type": "integer"
        },
        "example": 42
      }
    }
  }
}
