> ## Documentation Index
> Fetch the complete documentation index at: https://docs.autocalls.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Update user plan

> Give one of your users a subscription plan they pay you for outside your platform

This endpoint gives one of your users one of your plans, for customers who pay you outside your platform, for example by bank transfer, with your own payment links or on invoice. The user gets the plan's limits and minute rate, and its included minutes if you choose so, and sees the plan on their plans page as managed by you, without the buttons to change or cancel it. It is the **Plan** action on the Users page of your administration panel.

<Note>
  This endpoint is available to the **account owner** (with an active plan) and to team members with **Billing Access**. Other users receive a 403 Forbidden error. Only users of your platform can be changed: any other ID, including the owner account, returns 404.
</Note>

### Minutes

The plan's minutes come from the owner's balance, with its chat credits at 9 credits per minute (the `cost_in_minutes` of [List plans](/api-reference/white-label/list-plans)). With `minutes` you choose how:

* **`none`**: only the plan, its limits and minute rate. Nothing is taken from the owner's balance and nothing renews, for example when you move minutes yourself with [Transfer balance](/api-reference/white-label/transfer-balance).
* **`monthly`**: giving the plan takes one month of minutes and chat credits. Every month the user is topped back up to the plan's minutes and gets its chat credits again, and the owner's balance pays only for the minutes they used.
* **`yearly`**: giving the plan takes 12 months at once, and the user gets them month by month. Nothing more is taken for a year. After 12 months, what is left goes back to the owner's balance and the next 12 months are taken.

If the owner's balance is too low to give the plan, nothing changes and the request returns 422. Top up your balance first.

* **Moving the user to another plan or `minutes` option** first returns the unused minutes of the current plan, and what is left of a yearly prepayment, to the owner's balance.
* **Sending the user's current plan with the same `minutes`** changes only `billing`, `ends_at` and `when_short`. No minutes move.
* Minutes the user had before, outside a plan you gave, stay with them. Chat credits already given stay with the user.

A user who subscribed on your platform, with a trial or a paid subscription, cannot be given a plan until that subscription ends.

### Renewals

Renewals run every hour for the plans whose renewal day has come. When the owner's balance cannot cover one:

* The renewal waits, and it is tried again every hour. The user keeps the plan, its limits and the minutes they have left. [Get user plan](/api-reference/white-label/get-user-plan) shows `renewal.status` as `pending`, with the minutes missing.
* The owner gets a notification in the administration panel and an email, at most once a day.
* With `when_short` set to `auto_top_up`, the owner's auto top-up starts, with the amount and card set on the Credits page, even when the balance is above its threshold. It starts at most once a day. With `wait`, the renewal waits for you to top up.
* After a top-up, [Renew user plan](/api-reference/white-label/renew-user-plan) renews right away instead of within the hour.
* A plan whose last day comes on or before its renewal day is not renewed again: it ends on that day.

With auto top-up on, giving and renewing plans also check the owner's balance the same way calls do, and top it up under the threshold.

### Path Parameters

<ParamField path="id" type="integer" required>
  The ID of the user. Administrators can list user IDs with [Get platform users](/api-reference/white-label/get-users).
</ParamField>

### Request Body

<ParamField body="plan_id" type="integer" required>
  One of your plans, from [List plans](/api-reference/white-label/list-plans). Inactive plans can be given too.
</ParamField>

<ParamField body="minutes" type="string" default="monthly">
  `none`, `monthly` or `yearly`, as described above. Left out, a plan you gave before keeps its option, and a new plan is `monthly`.
</ParamField>

<ParamField body="when_short" type="string" default="wait">
  `auto_top_up` or `wait`: what happens when the owner's balance cannot cover a renewal, as described above. Left out, a plan you gave before keeps its choice, and a new plan waits. Plans with `minutes` set to `none` never renew, so it does not apply to them.
</ParamField>

<ParamField body="billing" type="string" default="monthly">
  With `minutes` set to `none` only: `monthly` or `yearly`, which of the plan's prices the user sees as theirs. With `monthly` or `yearly` minutes, the user sees the matching price. If the plan has no price for it, its other price is used. Left out, a plan you gave before keeps its billing, and a new plan is `monthly`.
</ParamField>

<ParamField body="ends_at" type="string">
  The last day of the plan, as `YYYY-MM-DD`, after today. The plan ends at the end of that day, and its unused minutes go back to the owner's balance. Left out, a plan you gave before keeps its end date, and a new plan has none. Send `null` to remove the end date, so the plan stays until you remove it.
</ParamField>

Unknown fields are refused, and then nothing is saved.

### Response

<ResponseField name="message" type="string">
  `Plan saved.`
</ResponseField>

<ResponseField name="data" type="object">
  The user's plan, as in [Get user plan](/api-reference/white-label/get-user-plan)
</ResponseField>

<ResponseExample>
  ```json 200 Plan Saved theme={null}
  {
    "message": "Plan saved.",
    "data": {
      "plan_id": 5,
      "name": "Starter",
      "source": "manual",
      "billing": "monthly",
      "included_minutes": 100,
      "included_credits": 0,
      "minutes_renew_at": "2026-11-02T14:25:38+00:00",
      "ends_at": null,
      "minutes": "monthly",
      "when_short": "auto_top_up",
      "renewal": {
        "status": "ok",
        "next_at": "2026-11-02T14:25:38+00:00",
        "missing_minutes": 0,
        "last_renewed_at": null,
        "prepaid_minutes": null,
        "prepaid_until": null
      }
    }
  }
  ```

  ```json 401 Unauthenticated theme={null}
  {
    "message": "Unauthenticated."
  }
  ```

  ```json 403 Not Administrator theme={null}
  {
    "message": "You are not an administrator."
  }
  ```

  ```json 403 No Billing Access theme={null}
  {
    "message": "You do not have access to these settings."
  }
  ```

  ```json 404 User Not Found theme={null}
  {
    "message": "User not found."
  }
  ```

  ```json 422 Insufficient Balance theme={null}
  {
    "message": "Insufficient balance: this plan needs 100 minutes and you have 88.83."
  }
  ```

  ```json 422 Subscribed on the Platform theme={null}
  {
    "message": "This user already has a plan paid in the platform. It must end before you can give one."
  }
  ```

  ```json 422 Not Your Plan theme={null}
  {
    "message": "The selected plan does not belong to your platform.",
    "errors": {
      "plan_id": ["The selected plan does not belong to your platform."]
    }
  }
  ```

  ```json 422 Invalid Minutes Option theme={null}
  {
    "message": "The selected minutes is invalid.",
    "errors": {
      "minutes": ["The selected minutes is invalid."]
    }
  }
  ```

  ```json 422 Invalid End Date theme={null}
  {
    "message": "The ends at field must be a date after today.",
    "errors": {
      "ends_at": ["The ends at field must be a date after today."]
    }
  }
  ```

  ```json 422 Unsupported Field theme={null}
  {
    "message": "The price field is not supported.",
    "errors": {
      "price": ["The price field is not supported."]
    }
  }
  ```

  ```json 429 Too Many Requests theme={null}
  {
    "message": "Too Many Attempts."
  }
  ```
</ResponseExample>

### Rate limit

The platform settings, default limits, user settings and plan endpoints share a limit of 60 requests per minute for each account. Above it they return `429`, and the `Retry-After` response header tells you how many seconds to wait.

### Example: Give a plan with monthly minutes

```bash theme={null}
curl -X PUT https://app.autocalls.ai/api/white-label/users/123/plan \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "plan_id": 5,
    "minutes": "monthly",
    "when_short": "auto_top_up"
  }'
```

### Example: A year paid upfront

```bash theme={null}
curl -X PUT https://app.autocalls.ai/api/white-label/users/123/plan \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "plan_id": 5,
    "minutes": "yearly"
  }'
```

### Example: Only the plan's limits, until the end of the year

```bash theme={null}
curl -X PUT https://app.autocalls.ai/api/white-label/users/123/plan \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "plan_id": 5,
    "minutes": "none",
    "billing": "yearly",
    "ends_at": "2026-12-31"
  }'
```

### Managed accounts

With **Allow New Sign-ups** off in your appearance settings, you can run every account yourself: create the user with [Register](/api-reference/white-label/register), give them a plan with this endpoint, and remove it with [Delete user plan](/api-reference/white-label/delete-user-plan) when they cancel with you.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.