> For the complete documentation index, see [llms.txt](https://docs.resifactory.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.resifactory.net/reseller-api/reseller-api.md).

# Reseller API

The ResiFactory **Reseller** (white-label) API. Authenticate with a `Bearer rf_rsl_...` key; each endpoint is gated by a scope (`subaccounts:read/write`, `billing:read/write`, `webhooks:manage`). Base URL `https://resifactory.net/rsl/v1`.

Errors return `{ "error": "<code>", "message": "..." }`. Send an `Idempotency-Key` header on the mutating endpoints (sub-account create, top-up, webhook create/delete) to make retries safe.

## Identity

## Account summary

> Returns your reseller account slug, the pool aliases enabled for your account, and the scopes on the key used for this request. Works with any valid key regardless of scopes — useful as a credential health check.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/me":{"get":{"operationId":"me","summary":"Account summary","description":"Returns your reseller account slug, the pool aliases enabled for your account, and the scopes on the key used for this request. Works with any valid key regardless of scopes — useful as a credential health check.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"account":{"type":"string","description":"Your reseller account slug."},"pools":{"type":"array","items":{"type":"string"},"description":"Pool aliases enabled for your account (e.g. `residential`)."},"scopes":{"type":"array","items":{"type":"string"},"description":"The scopes granted to the key used for this request."}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The reseller account is suspended — no API access at all, including reads.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Enabled pool aliases

> Lists the pool aliases enabled for your account and the proxy host each resolves to. Pool ids differ per account — call this rather than hardcoding an alias.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/pools":{"get":{"operationId":"pools","summary":"Enabled pool aliases","description":"Lists the pool aliases enabled for your account and the proxy host each resolves to. Pool ids differ per account — call this rather than hardcoding an alias.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"pools":{"type":"array","items":{"type":"object","properties":{"pool":{"type":"string","description":"Pool alias — use this as a `pools`/`pool` value on other endpoints."},"host":{"type":["string","null"],"description":"Proxy host sub-accounts on this pool connect to, or null if not resolvable."}}}}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The reseller account is suspended — no API access at all, including reads.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Sub-accounts

## Create a sub-account

> Provisions a headless proxy sub-account under your reseller account and returns its one-time credentials. Requires an Idempotency-Key header — a retry with the same key and body replays the original response verbatim, including the credentials (the only way to recover them after a lost response; they are never returned again). A \`ref\` that already exists on your account is also refused with \`idempotency\_conflict\` (409), independently of the Idempotency-Key you send — \`ref\` uniqueness and idempotency replay are two different checks that share one error code.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/subaccounts":{"post":{"operationId":"subaccounts_create","summary":"Create a sub-account","description":"Provisions a headless proxy sub-account under your reseller account and returns its one-time credentials. Requires an Idempotency-Key header — a retry with the same key and body replays the original response verbatim, including the credentials (the only way to recover them after a lost response; they are never returned again). A `ref` that already exists on your account is also refused with `idempotency_conflict` (409), independently of the Idempotency-Key you send — `ref` uniqueness and idempotency replay are two different checks that share one error code.","responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"The created sub-account. `credentials` is shown ONLY in this response (or an Idempotency-Key replay of it) — there is no endpoint to retrieve the password again.","properties":{"ref":{"type":"string","description":"The `ref` you sent."},"pools":{"type":"array","items":{"type":"string"},"description":"Pool aliases enabled on the created sub-account."},"status":{"type":"string","enum":["active"],"description":"Always `active` on creation."},"credentials":{"type":"object","description":"Proxy credentials for this sub-account. Shown ONLY here (or on an Idempotency-Key replay of this exact request) — store them now.","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"hosts":{"type":"object","description":"Proxy host per enabled pool alias, keyed by alias.","additionalProperties":{"type":["string","null"]}}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_frozen`: The reseller account is not in an active billing state (e.g. frozen, or in a suspended drain-down) — mutations are blocked, though reads still work. `account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: subaccounts:write","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The pool alias does not exist, or is not enabled for your account. Call GET /pools to see the aliases available to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict`: This Idempotency-Key was already used with a different request body, or a request using it is still in progress. `subaccount_limit_reached`: Your account has reached its configured maximum number of active sub-accounts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request body or a required header failed validation — see `message` for the specific field or reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many write requests. See the `RateLimit-*` response headers and slow down.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The request could not be completed due to a server-side error. Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ref":{"type":"string","description":"Your own unique identifier for this sub-account. Used to look it up later via GET /subaccounts/{ref}. Must be unique among your account's non-archived sub-accounts."},"pools":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Pool aliases to enable for this sub-account (e.g. `residential`). Call GET /pools or GET /me to discover which aliases are available to your account."},"label":{"type":"string","description":"Optional display label for this sub-account."},"daily_cap":{"type":"integer","minimum":0,"description":"Hard-stop daily bandwidth cap for this sub-account, in bytes. 0 stops funding immediately for the day. Omit for no cap."},"expiry":{"type":"string","format":"date-time","description":"Timestamp after which this sub-account stops carrying traffic. Any value `Date.parse` can parse is accepted. Omit for no expiry."}},"required":["ref","pools"]}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"Retry-safe request key. A repeat with the same body replays the original response verbatim (including one-time secrets); a repeat with a different body, or one still in progress, returns 409 idempotency_conflict."}]}}}}
```

## List sub-accounts

> Your sub-accounts, newest first, server-paged.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/subaccounts":{"get":{"operationId":"subaccounts_list","summary":"List sub-accounts","description":"Your sub-accounts, newest first, server-paged.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","description":"Sub-accounts for this page.","items":{"type":"object","properties":{"ref":{"type":"string"},"label":{"type":["string","null"]},"status":{"type":"string","enum":["active","archived","disabled_by_partner","disabled_by_operator"],"description":"The sub-account's intent status (what you or an operator set it to) — not its effective traffic status. See GET /subaccounts/{ref} for that."}}}},"has_more":{"type":"boolean","description":"True if another page follows. Advance `offset` by `limit` to fetch it."}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: subaccounts:read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Maximum number of results to return on this page. Values above 100 are clamped to 100."}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"description":"Number of results to skip."}}]}}}}
```

## One sub-account

> Returns a single sub-account's status detail, including its EFFECTIVE traffic-eligibility status (which can differ from the intent status you set — e.g. an active sub-account under a frozen reseller account, or one past its \`expiry\`).

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/subaccounts/{ref}":{"get":{"operationId":"subaccounts_detail","summary":"One sub-account","description":"Returns a single sub-account's status detail, including its EFFECTIVE traffic-eligibility status (which can differ from the intent status you set — e.g. an active sub-account under a frozen reseller account, or one past its `expiry`).","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ref":{"type":"string"},"label":{"type":["string","null"]},"intent_status":{"type":"string","enum":["active","archived","disabled_by_partner","disabled_by_operator"],"description":"What you (or an operator) set this sub-account's status to."},"effective_status":{"type":"string","enum":["active","disabled","expired","archived"],"description":"The sub-account's actual traffic-eligibility right now, accounting for `expiry` and account-level state. See `reasons`."},"reasons":{"type":"array","items":{"type":"string","enum":["reseller_frozen","reseller_suspended_drain","disabled_by_partner","disabled_by_operator","expired"]},"description":"Why `effective_status` is what it is. `reseller_frozen` is informational only — a frozen reseller account still carries traffic."},"pools":{"type":"array","items":{"type":"string"},"description":"Pool aliases enabled on this sub-account."},"proxy_username":{"type":["string","null"],"description":"Proxy username for this sub-account. The password is never returned again after creation."}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: subaccounts:read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No sub-account exists with this `ref` on your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":"Sub-account ref, as sent to POST /subaccounts."}]}}}}
```

## Billing & invoices

## Current billing period accrual

> Your usage-based accrual for the current anchored-monthly billing period, per pool, at current rates. Read-only — does not create or affect an invoice.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/billing/current":{"get":{"operationId":"billing_current","summary":"Current billing period accrual","description":"Your usage-based accrual for the current anchored-monthly billing period, per pool, at current rates. Read-only — does not create or affect an invoice.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"object","description":"The current billing period this accrual covers.","properties":{"from":{"type":"string","description":"Period start (YYYY-MM-DD), inclusive."},"to":{"type":"string","description":"Period end (YYYY-MM-DD), inclusive."}}},"pools":{"type":"array","description":"Accrual broken down by pool alias.","items":{"type":"object","properties":{"pool":{"type":"string"},"gb":{"type":"string","description":"Bandwidth accrued this period for this pool, in GB, as a fixed-point decimal string (6 places)."},"amount_cents":{"type":"integer"}}}},"total_cents":{"type":"integer","description":"Total accrued charges for the current period so far, in cents."},"unbilled_cents":{"type":"integer","description":"Total accrued charges not yet invoiced, in cents — may span more than just the current period."}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: billing:read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## List invoices

> Your invoices, newest first, server-paged.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/invoices":{"get":{"operationId":"invoices_list","summary":"List invoices","description":"Your invoices, newest first, server-paged.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"number":{"type":"integer","description":"Invoice number, unique per account. Use as `{number}` on GET /invoices/{number} and GET /invoices/{number}/lines."},"display":{"type":"string","description":"Human-readable invoice identifier: `RSL-<account>-<number>`."},"status":{"type":"string","enum":["draft","pending_approval","issued","paid","payment_failed","void"],"description":"Invoice lifecycle status."},"period_start":{"type":["string","null"],"description":"Start of the billed period (YYYY-MM-DD), or null if not yet set."},"period_end":{"type":["string","null"],"description":"End of the billed period (YYYY-MM-DD), or null if not yet set."},"subtotal_cents":{"type":["integer","null"],"description":"Subtotal before any credit notes, in cents, or null before it has been computed."},"total_cents":{"type":["integer","null"],"description":"Total due, in cents, or null before it has been computed."},"due_date":{"type":["string","null"],"description":"Payment due date (YYYY-MM-DD), or null while still `draft`/`pending_approval`."},"issued_at":{"type":["string","null"],"description":"When this invoice was issued, or null while still `draft`/`pending_approval`."},"hosted_invoice_url":{"type":["string","null"],"description":"Hosted payment page URL, present once issued."},"created_at":{"type":"string","description":"When this invoice row was created."}}}},"has_more":{"type":"boolean","description":"True if another page follows. Advance `offset` by `limit` to fetch it."}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: billing:read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Maximum number of results to return on this page. Values above 100 are clamped to 100."}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"description":"Number of results to skip."}}]}}}}
```

## One invoice

> Returns a single invoice.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/invoices/{number}":{"get":{"operationId":"invoice_detail","summary":"One invoice","description":"Returns a single invoice.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"number":{"type":"integer","description":"Invoice number, unique per account. Use as `{number}` on GET /invoices/{number} and GET /invoices/{number}/lines."},"display":{"type":"string","description":"Human-readable invoice identifier: `RSL-<account>-<number>`."},"status":{"type":"string","enum":["draft","pending_approval","issued","paid","payment_failed","void"],"description":"Invoice lifecycle status."},"period_start":{"type":["string","null"],"description":"Start of the billed period (YYYY-MM-DD), or null if not yet set."},"period_end":{"type":["string","null"],"description":"End of the billed period (YYYY-MM-DD), or null if not yet set."},"subtotal_cents":{"type":["integer","null"],"description":"Subtotal before any credit notes, in cents, or null before it has been computed."},"total_cents":{"type":["integer","null"],"description":"Total due, in cents, or null before it has been computed."},"due_date":{"type":["string","null"],"description":"Payment due date (YYYY-MM-DD), or null while still `draft`/`pending_approval`."},"issued_at":{"type":["string","null"],"description":"When this invoice was issued, or null while still `draft`/`pending_approval`."},"hosted_invoice_url":{"type":["string","null"],"description":"Hosted payment page URL, present once issued."},"created_at":{"type":"string","description":"When this invoice row was created."}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: billing:read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No invoice exists with this `number` on your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"number","in":"path","required":true,"schema":{"type":"string"},"description":"Invoice number, as returned in the `number` field of an invoice."}]}}}}
```

## Invoice line items

> Returns the per-pool line items making up an invoice, at the alias and rate that applied when it was issued.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/invoices/{number}/lines":{"get":{"operationId":"invoice_lines","summary":"Invoice line items","description":"Returns the per-pool line items making up an invoice, at the alias and rate that applied when it was issued.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"pool":{"type":"string","description":"Pool alias this line covers, as it existed when the invoice was issued."},"gb":{"type":"string","description":"Bandwidth billed on this line, in GB, as a decimal string."},"bytes":{"type":"integer","description":"Bandwidth billed on this line, in bytes."},"price_cents_per_gb":{"type":"integer","description":"Rate applied to this line, in cents per GB."},"amount_cents":{"type":"integer","description":"Amount charged for this line, in cents."}}}}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: billing:read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No invoice exists with this `number` on your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"number","in":"path","required":true,"schema":{"type":"string"},"description":"Invoice number, as returned in the `number` field of an invoice."}]}}}}
```

## Top up a prepaid pool balance

> Charges your saved card off-session and credits the resulting bandwidth to a pool's prepaid balance, at the current top-up rate. Prepaid accounts only, and only when a valid saved card is on file. Requires an Idempotency-Key header — a retry with the same key and body replays the original response verbatim without charging again.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/topup":{"post":{"operationId":"topup_create","summary":"Top up a prepaid pool balance","description":"Charges your saved card off-session and credits the resulting bandwidth to a pool's prepaid balance, at the current top-up rate. Prepaid accounts only, and only when a valid saved card is on file. Requires an Idempotency-Key header — a retry with the same key and body replays the original response verbatim without charging again.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"gb":{"type":"number","description":"Bandwidth credited by this top-up, in GB — computed server-side from live pricing."},"amount_cents":{"type":"integer","description":"Amount charged, in cents — exactly the `amount_cents` you sent."},"balance":{"type":"number","description":"This pool's prepaid balance after the top-up, in GB."}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The saved card on file was declined.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: billing:write","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This Idempotency-Key was already used with a different request body, or a request using it is still in progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request body or a required header failed validation — see `message` for the specific field or reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many write requests. See the `RateLimit-*` response headers and slow down.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The payment processor could not complete the charge. Nothing was charged or credited — safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pool":{"type":"string","description":"Pool alias to top up, as returned by GET /pools (e.g. `residential`). Must be a pool alias enabled for your account."},"amount_cents":{"type":"integer","minimum":1,"maximum":5000000,"description":"Amount to charge your saved card, in cents. Up to 5000000 ($50,000) per request. The bandwidth credited is computed server-side from the pool's live top-up price per GB — there is no `gb` parameter; do not compute or send one."}},"required":["pool","amount_cents"]}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"Retry-safe request key. A repeat with the same body replays the original response verbatim (including one-time secrets); a repeat with a different body, or one still in progress, returns 409 idempotency_conflict."}]}}}}
```

## Webhooks

## List webhook endpoints

> Your registered (non-revoked) webhook endpoints.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/webhooks":{"get":{"operationId":"webhooks_list","summary":"List webhook endpoints","description":"Your registered (non-revoked) webhook endpoints.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"},"description":"Subscribed event names — see POST /webhooks' `events` for the full closed set."},"status":{"type":"string","enum":["active"]},"created_at":{"type":"string"},"last_delivery_status":{"type":["string","null"],"enum":["delivered","delivery_failed",null],"description":"Collapsed outcome of the most recent delivery attempt, or null if nothing has been attempted yet. No other detail (retry count, last error) is exposed here."}}}}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: webhooks:manage","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Register a webhook endpoint

> Registers a new webhook endpoint and returns its signing secret. The URL is vetted at save time (must be a public https\:// endpoint — no localhost/.local/.internal hostnames or private/reserved IP literals) and re-vetted at every send. Requires an Idempotency-Key header — a retry with the same key and body replays the original response verbatim, including the secret (the only way to recover it after a lost response; it is never returned again). Only available while your account is active.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/webhooks":{"post":{"operationId":"webhooks_create","summary":"Register a webhook endpoint","description":"Registers a new webhook endpoint and returns its signing secret. The URL is vetted at save time (must be a public https:// endpoint — no localhost/.local/.internal hostnames or private/reserved IP literals) and re-vetted at every send. Requires an Idempotency-Key header — a retry with the same key and body replays the original response verbatim, including the secret (the only way to recover it after a lost response; it is never returned again). Only available while your account is active.","responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"`secret` is shown ONLY here (or on an Idempotency-Key replay of this exact request) — there is no endpoint to retrieve it again.","properties":{"id":{"type":"integer"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active"]},"secret":{"type":"string","description":"Webhook signing secret (`rf_whsec_...`). Used to verify the `RF-Signature` header on deliveries."}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_frozen`: The reseller account is not in an active billing state (e.g. frozen, or in a suspended drain-down) — mutations are blocked, though reads still work. `account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: webhooks:manage","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict`: This Idempotency-Key was already used with a different request body, or a request using it is still in progress. `webhook_limit_reached`: Your account has reached its configured maximum number of registered webhook endpoints.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request body or a required header failed validation — see `message` for the specific field or reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many write requests. See the `RateLimit-*` response headers and slow down.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"HTTPS endpoint to receive webhook deliveries. Must be a public `https://` URL."},"events":{"type":"array","items":{"type":"string","enum":["subaccount.created","subaccount.updated","subaccount.status_changed","reseller.threshold.50","reseller.threshold.80","reseller.threshold.100","reseller.status_changed","invoice.draft","invoice.issued","invoice.paid","invoice.payment_failed","invoice.voided","credit_note.issued","key.revoked"]},"minItems":1,"description":"Event names to subscribe to."}},"required":["url","events"]}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"Retry-safe request key. A repeat with the same body replays the original response verbatim (including one-time secrets); a repeat with a different body, or one still in progress, returns 409 idempotency_conflict."}]}}}}
```

## Remove a webhook endpoint

> Revokes a webhook endpoint — no further deliveries are sent to it. Requires an Idempotency-Key header (same replay contract as every other mutation on this API). Only available while your account is active.

```json
{"openapi":"3.1.0","info":{"title":"ResiFactory Reseller API","version":"1.0.0"},"servers":[{"url":"https://resifactory.net/rsl/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rf_rsl_*"}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","forbidden","account_suspended","account_frozen","invalid_params","pool_not_found","subaccount_not_found","subaccount_limit_reached","idempotency_conflict","rate_limited","invoice_not_found","webhook_not_found","webhook_limit_reached","card_declined","payment_processor_error","internal_error"],"description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable detail. Not part of the stable contract — match on `error`, never on this string."}}}}},"paths":{"/webhooks/{id}":{"delete":{"operationId":"webhooks_delete","summary":"Remove a webhook endpoint","description":"Revokes a webhook endpoint — no further deliveries are sent to it. Requires an Idempotency-Key header (same replay contract as every other mutation on this API). Only available while your account is active.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"401":{"description":"Missing, invalid, or IP-restricted API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`account_frozen`: The reseller account is not in an active billing state (e.g. frozen, or in a suspended drain-down) — mutations are blocked, though reads still work. `account_suspended`: The reseller account is suspended — no API access at all, including reads. `forbidden`: Key lacks the required scope: webhooks:manage","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No webhook endpoint exists with this `id` on your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This Idempotency-Key was already used with a different request body, or a request using it is still in progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request body or a required header failed validation — see `message` for the specific field or reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many write requests. See the `RateLimit-*` response headers and slow down.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook id, as returned by POST /webhooks or GET /webhooks."},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"Retry-safe request key. A repeat with the same body replays the original response verbatim (including one-time secrets); a repeat with a different body, or one still in progress, returns 409 idempotency_conflict."}]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.resifactory.net/reseller-api/reseller-api.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
