---
title: "Adjust reseller billing"
description: "Apply a signed delta to a reseller's credits (credits mode) or slot cap (users mode). Atomic, audited, idempotent."
---

# Adjust reseller billing

Apply a signed delta to a reseller's balance. The endpoint targets the right column automatically: in `credits` mode it moves `credits`, in `users` mode it moves `max_users`. You never pass the mode explicitly.

The update runs as a single atomic relative SQL statement (`credits = credits + delta`, or the `max_users` equivalent with a cast to `SIGNED`). Two concurrent adjustments sum instead of racing. The read path never goes through the 60-second cache used by other reseller endpoints, so a burst of top-ups from a monthly job cannot silently overwrite each other. Every successful adjustment is written to `reg_userlog` with the type `panel_api_billing_adjust`, so it appears in the same audit trail as CMS-driven changes.

## Endpoint

`POST https://<your-panel-domain>/panel-api/v1/resellers/{id}/billing/adjust`

## Authentication

Send the API key in the `Authorization: Bearer <your-api-key>` header. Only admin keys can call this endpoint.

## Required scope

`resellers:write`

## Idempotency

Every POST must include an `Idempotency-Key` header. Reusing the same key with the same body replays the cached response; reusing it with a different body returns `409 idempotency_conflict`. For a monthly billing job, use the invoice ID as the key so that a retry after a timeout never stacks a second charge on top.

## Path parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `id` | int | The reg-user ID of the reseller. |

## Request body

| Field | Type | Required | Default | Description |
| ----- | ---- | -------- | ------- | ----------- |
| `delta` | float | yes | | Signed change. Positive to top up, negative to claw back. In `users` mode the value is cast to int on the server. |
| `reason` | string | no | `""` | Free-form label written to the audit log. Truncated to 100 characters. |

```json
{
  "delta": 1,
  "reason": "docs sample adjust +1"
}
```

## Response

The response is the billing snapshot after the adjustment, in the same shape as `GET /resellers/{id}/billing`.

```json
{
  "mode": "credits",
  "credits": 11,
  "max_users": null,
  "active_users": null,
  "billing_expires": null
}
```

Fractional credits are preserved. Adding `0.10` to a balance of `0.25` produces `0.35`, not `0`. Do not floor or round in your own code before sending the delta.

## Examples

### cURL

```bash
curl -X POST https://<your-panel-domain>/panel-api/v1/resellers/262260/billing/adjust \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: invoice-INV-2026-00814" \
  -d '{"delta":1,"reason":"docs sample adjust +1"}'
```

### PHP SDK

```php
require __DIR__ . '/api-panel-php-sdk-1.0.0/autoload.php';
use XtreamAI\PanelApi\PanelApiClient;

$client = new PanelApiClient(baseUrl: 'https://<your-panel-domain>', token: '<your-api-key>');
$billing = $client->resellers->adjustBilling(
    id:             262260,
    delta:          1,
    reason:         'docs sample adjust +1',
    idempotencyKey: 'invoice-INV-2026-00814',
);
echo $billing->credits, PHP_EOL;
```

### Python SDK

```python
from xtream_ai_panel_api import PanelApiClient

client = PanelApiClient(base_url="https://<your-panel-domain>", token="<your-api-key>")
billing = client.resellers.adjust_billing(
    262260,
    delta=1,
    reason="docs sample adjust +1",
    idempotency_key="invoice-INV-2026-00814",
)
print(billing.credits)
```

## Errors

| HTTP | Error slug | When it happens | How to fix |
| ---- | ---------- | --------------- | ---------- |
| 400 | `missing_idempotency_key` | The `Idempotency-Key` header was not sent. | Add the header on every POST. |
| 400 | `invalid_body` | The `delta` field is missing from the body. | Include `delta` as a signed number. |
| 401 | `invalid_key` | Missing, malformed, or unknown API key. | Send a live key in `Authorization: Bearer <token>`. |
| 403 | `admin_only_endpoint` | A reseller key called this endpoint. | Use an admin key. |
| 403 | `insufficient_scope` | The key does not carry `resellers:write`. | Rotate the key with the correct scopes. |
| 404 | `not_found` | No reseller exists with that ID. | Verify the ID with `GET /resellers/{id}` first. |
| 409 | `idempotency_conflict` | The `Idempotency-Key` was reused with a different body. | Generate a new key or resend the original body. |
| 422 | `negative_balance_not_allowed` | A negative delta in `credits` mode is larger than the current balance. The balance is untouched; no partial deduction happens. | Lower the delta or top up first. |
| 422 | `cap_below_active_users` | A negative delta in `users` mode would drop `max_users` below the currently used slot count. No lines are auto-disabled to make room. | Free the slots first (disable or delete lines), then retry. |
| 422 | `negative_cap_not_allowed` | A negative delta in `users` mode would take `max_users` below zero. | Lower the delta. |
| 422 | `mismatched_mode` | The reseller is stored in a billing mode this endpoint does not recognize (should not happen on a healthy panel). | Contact support. |
| 429 | `rate_limited` | The per-key request budget for this minute is spent. | Back off and retry after the minute rolls over. |

## See also

- [Get reseller billing](/docs/?page=xai-ref-resellers-billing)
- [Panel API Resellers overview](/docs/?page=xai-ref-resellers-list)
- [Rate limits and Idempotency](/docs/?page=panel-api-rate-limits-idempotency)
