---
title: "Configure the client's own AI models (BYOK)"
method: PUT
path: "/clients/{id_client}/ai-settings"
tags: ["clients"]
---

# Configure the client's own AI models (BYOK)

`PUT /clients/{id_client}/ai-settings`

## Request

- Base URL: `https://api.planvortex.com/v1.0.0`
- URL: `https://api.planvortex.com/v1.0.0/clients/{id_client}/ai-settings`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (client credentials, token URL `https://api.planvortex.com/v1.0.0/oauth/token`)

Set the client's Bring-Your-Own-Key AI provider configuration per scope. The request body is an object keyed by scope (orchestrator, text, image); a scope set to null clears it. Providers and capabilities are validated against a fixed catalogue. When a scope uses BYOK, its calls are billed to the client's own provider and do NOT consume PlanVortex AI credits, but the plan still has to include AI credits to use the feature. API keys are stored encrypted and never returned; the response client only exposes provider, model and has_api_key per scope. Requires the client:update permission.

## Path parameters

- `id_client` string, required

## Request body

- ClientsAiSettings: Per-scope AI provider configuration (BYOK). Only the scopes you send are touched; a scope set to `null` clears its configuration and returns that scope to PlanVortex credits. `orchestrator` and `text` need a text-capable provider, `image` needs an image-capable one. `video` is reserved for a later phase.
  - `orchestrator` ClientsAiScopeSetting: AI provider configuration for a single scope (BYOK). On write, api_key is required the first time and is stored encrypted; on subsequent writes it can be omitted to keep the existing key (send only provider/model). On read, the api_key is NEVER returned — only has_api_key is exposed.
    - `provider` 'openrouter' | 'openai' | 'google' | 'anthropic', required: Fixed catalogue of allowed providers. The provider must support the capability of the scope (text for orchestrator/text, image for image).
    - `model` string, required: Model identifier at the chosen provider (e.g. google/gemini-3-flash).
    - `api_key` string: Provider API key. Write-only: accepted on PUT, stored encrypted (AES-256-GCM), never returned.
    - `has_api_key` boolean: Read-only. True when an encrypted API key is stored for this scope.
  - `text` ClientsAiScopeSetting: AI provider configuration for a single scope (BYOK). On write, api_key is required the first time and is stored encrypted; on subsequent writes it can be omitted to keep the existing key (send only provider/model). On read, the api_key is NEVER returned — only has_api_key is exposed.
    - `provider` 'openrouter' | 'openai' | 'google' | 'anthropic', required: Fixed catalogue of allowed providers. The provider must support the capability of the scope (text for orchestrator/text, image for image).
    - `model` string, required: Model identifier at the chosen provider (e.g. google/gemini-3-flash).
    - `api_key` string: Provider API key. Write-only: accepted on PUT, stored encrypted (AES-256-GCM), never returned.
    - `has_api_key` boolean: Read-only. True when an encrypted API key is stored for this scope.
  - `image` ClientsAiScopeSetting: AI provider configuration for a single scope (BYOK). On write, api_key is required the first time and is stored encrypted; on subsequent writes it can be omitted to keep the existing key (send only provider/model). On read, the api_key is NEVER returned — only has_api_key is exposed.
    - `provider` 'openrouter' | 'openai' | 'google' | 'anthropic', required: Fixed catalogue of allowed providers. The provider must support the capability of the scope (text for orchestrator/text, image for image).
    - `model` string, required: Model identifier at the chosen provider (e.g. google/gemini-3-flash).
    - `api_key` string: Provider API key. Write-only: accepted on PUT, stored encrypted (AES-256-GCM), never returned.
    - `has_api_key` boolean: Read-only. True when an encrypted API key is stored for this scope.

## Response `200`

Successful operation. Returns the client with the sanitized ai_settings (no API keys).

- ClientsClientOne
  - `client` ClientsClient, required: Who contracts the plan and whom the organizations hang from.
    - `_id` string, required
    - `name` string, required
    - `client_type` 'personal' | 'company'
    - `creation_date` string, date-time, required
    - `trial_tested` boolean: Whether this client has already used its trial.
    - `actual_plan` ClientsPlan, required: The client's SUBSCRIPTION, which is not the same thing as the resources it grants: the numbers live in `plan_data`.
      - `enabled` boolean, required: Whether the plan is usable. A disabled client cannot connect accounts or publish.
      - `status` string, required: The subscription's state at the payment provider (`active`, `past_due`, `canceled`, `trialing`, `unpaid`...). A cancelled subscription still works until `current_period_end`.
      - `isEnabled` boolean, required: The same thing as `enabled`, recomputed on read rather than stored. Read either; they only disagree if the plan was never saved after changing.
      - `require_action` boolean: The payment needs the customer to do something (3-D Secure, a new card).
      - `plan_identifier` 'free' | 'basic' | 'pro' | 'custom', required: Which plan was contracted. `custom` carries its own numbers in `plan_data`.
      - `plan_data` PlanData, required: The resources a plan grants. On a client it is what was contracted; on an organization, the slice of it that was assigned. The sum across all the organizations of a client can never exceed what the client has contracted. Users are NOT here: every plan has unlimited users, so they are neither charged nor split between organizations. They are still counted, in `PlanUseData.users`. There are no `artificial_inteligence`, `whatsapp` or `stats` flags either: statistics and WhatsApp are on every plan, and what gates AI is `ai_credits` — a plan with credits has AI, and Free has zero.
        - `accounts` integer, required: Social accounts that may be connected.
        - `space` number, required: Storage, in GB.
        - `integrations` integer, required: Connections to a third-party tool material is pulled from (Google Drive, an RSS feed). Not the same thing as an app: an app is API access, and it has its own allowance in `apps`.
        - `apps` integer: Apps this plan allows: 1 on Free, 2 on Basic, 5 on Pro, 10 on Custom. Each one is a `client_id` with a secret, so each one is a key to the whole public API — which every plan has, Free included. Unlike accounts or storage this is NOT split between organizations: an app belongs to the client. On a use payload it is how many exist right now.
        - `twitter_credits` integer: Monthly X (Twitter) credits. X bills per use: 15 per post, 200 if the text contains a link, 15 per deletion, 1 per stat read, 1 per timeline item. The pool resets on the 1st of each calendar month and does not roll over.
        - `ai_credits` integer: Monthly AI credits (1 credit = $0.001 of provider cost): 15 per plan orchestration pass, 2 per generated text, 70 per generated image. Resets monthly and does not roll over. A client using its own provider key (BYOK) does not consume them in that scope.
      - `current_period_start` string, date-time
      - `current_period_end` string, date-time: When the current billing period ends. A cancelled plan keeps working until then.
      - `billing_interval` 'month' | 'year': How often the plan is billed. A yearly plan costs ten monthly fees, paid up front, and every resource in it is still counted per calendar month (AI and X credits included). Clients from before yearly billing existed read `month`. It means nothing on `free`.
      - `stripe_customer_id` string
    - `ai_settings` ClientsAiSettings: Per-scope AI provider configuration (BYOK). Only the scopes you send are touched; a scope set to `null` clears its configuration and returns that scope to PlanVortex credits. `orchestrator` and `text` need a text-capable provider, `image` needs an image-capable one. `video` is reserved for a later phase.
      - `orchestrator` ClientsAiScopeSetting: AI provider configuration for a single scope (BYOK). On write, api_key is required the first time and is stored encrypted; on subsequent writes it can be omitted to keep the existing key (send only provider/model). On read, the api_key is NEVER returned — only has_api_key is exposed.
        - `provider` 'openrouter' | 'openai' | 'google' | 'anthropic', required: Fixed catalogue of allowed providers. The provider must support the capability of the scope (text for orchestrator/text, image for image).
        - `model` string, required: Model identifier at the chosen provider (e.g. google/gemini-3-flash).
        - `api_key` string: Provider API key. Write-only: accepted on PUT, stored encrypted (AES-256-GCM), never returned.
        - `has_api_key` boolean: Read-only. True when an encrypted API key is stored for this scope.
      - `text` ClientsAiScopeSetting: AI provider configuration for a single scope (BYOK). On write, api_key is required the first time and is stored encrypted; on subsequent writes it can be omitted to keep the existing key (send only provider/model). On read, the api_key is NEVER returned — only has_api_key is exposed.
        - `provider` 'openrouter' | 'openai' | 'google' | 'anthropic', required: Fixed catalogue of allowed providers. The provider must support the capability of the scope (text for orchestrator/text, image for image).
        - `model` string, required: Model identifier at the chosen provider (e.g. google/gemini-3-flash).
        - `api_key` string: Provider API key. Write-only: accepted on PUT, stored encrypted (AES-256-GCM), never returned.
        - `has_api_key` boolean: Read-only. True when an encrypted API key is stored for this scope.
      - `image` ClientsAiScopeSetting: AI provider configuration for a single scope (BYOK). On write, api_key is required the first time and is stored encrypted; on subsequent writes it can be omitted to keep the existing key (send only provider/model). On read, the api_key is NEVER returned — only has_api_key is exposed.
        - `provider` 'openrouter' | 'openai' | 'google' | 'anthropic', required: Fixed catalogue of allowed providers. The provider must support the capability of the scope (text for orchestrator/text, image for image).
        - `model` string, required: Model identifier at the chosen provider (e.g. google/gemini-3-flash).
        - `api_key` string: Provider API key. Write-only: accepted on PUT, stored encrypted (AES-256-GCM), never returned.
        - `has_api_key` boolean: Read-only. True when an encrypted API key is stored for this scope.
    - `actual_use` PlanUseData: The resources a plan grants. On a client it is what was contracted; on an organization, the slice of it that was assigned. The sum across all the organizations of a client can never exceed what the client has contracted. Users are NOT here: every plan has unlimited users, so they are neither charged nor split between organizations. They are still counted, in `PlanUseData.users`. There are no `artificial_inteligence`, `whatsapp` or `stats` flags either: statistics and WhatsApp are on every plan, and what gates AI is `ai_credits` — a plan with credits has AI, and Free has zero.
      - `accounts` integer, required: Social accounts that may be connected.
      - `space` number, required: Storage, in GB.
      - `integrations` integer, required: Connections to a third-party tool material is pulled from (Google Drive, an RSS feed). Not the same thing as an app: an app is API access, and it has its own allowance in `apps`.
      - `apps` integer: Apps this plan allows: 1 on Free, 2 on Basic, 5 on Pro, 10 on Custom. Each one is a `client_id` with a secret, so each one is a key to the whole public API — which every plan has, Free included. Unlike accounts or storage this is NOT split between organizations: an app belongs to the client. On a use payload it is how many exist right now.
      - `twitter_credits` integer: Monthly X (Twitter) credits. X bills per use: 15 per post, 200 if the text contains a link, 15 per deletion, 1 per stat read, 1 per timeline item. The pool resets on the 1st of each calendar month and does not roll over.
      - `ai_credits` integer: Monthly AI credits (1 credit = $0.001 of provider cost): 15 per plan orchestration pass, 2 per generated text, 70 per generated image. Resets monthly and does not roll over. A client using its own provider key (BYOK) does not consume them in that scope.
    - `actual_asigned` PlanData: The resources a plan grants. On a client it is what was contracted; on an organization, the slice of it that was assigned. The sum across all the organizations of a client can never exceed what the client has contracted. Users are NOT here: every plan has unlimited users, so they are neither charged nor split between organizations. They are still counted, in `PlanUseData.users`. There are no `artificial_inteligence`, `whatsapp` or `stats` flags either: statistics and WhatsApp are on every plan, and what gates AI is `ai_credits` — a plan with credits has AI, and Free has zero.
      - `accounts` integer, required: Social accounts that may be connected.
      - `space` number, required: Storage, in GB.
      - `integrations` integer, required: Connections to a third-party tool material is pulled from (Google Drive, an RSS feed). Not the same thing as an app: an app is API access, and it has its own allowance in `apps`.
      - `apps` integer: Apps this plan allows: 1 on Free, 2 on Basic, 5 on Pro, 10 on Custom. Each one is a `client_id` with a secret, so each one is a key to the whole public API — which every plan has, Free included. Unlike accounts or storage this is NOT split between organizations: an app belongs to the client. On a use payload it is how many exist right now.
      - `twitter_credits` integer: Monthly X (Twitter) credits. X bills per use: 15 per post, 200 if the text contains a link, 15 per deletion, 1 per stat read, 1 per timeline item. The pool resets on the 1st of each calendar month and does not roll over.
      - `ai_credits` integer: Monthly AI credits (1 credit = $0.001 of provider cost): 15 per plan orchestration pass, 2 per generated text, 70 per generated image. Resets monthly and does not roll over. A client using its own provider key (BYOK) does not consume them in that scope.

## Other responses

- `400`: The request failed. The body carries the PlanVortex error code in `code`: | Code | Meaning | | --- | --- | | `2104` | Invalid AI provider, scope or model. Returned when the scope is unknown, the provider is not in the catalogue, the provider does not support the scope's capability, or model/api_key are missing. | | `521` | Invalid client. The identifier is invalid or doesn't exist |
- `500`: Unhandled error by the server

---

[API](https://skmtc.dev/planvortex/apis/planvortex-api.md) · [All operations](https://skmtc.dev/planvortex/apis/planvortex-api/llms.txt) · [OpenAPI document](https://skmtc.dev/planvortex/apis/planvortex-api/revisions/abedbbc1cd52?raw)
