Skip to content

Update a plan (partial) (partner-api)

Updates one of your plans. Both PUT and PATCH update partially: a field you leave out keeps its stored value, so you can send just what changes. Same validation as the Plans form in the partner portal, including the per-country name rule (a name already used in one of the plan's resulting countries answers 422) and the Odoo rule (for a plan linked to an Odoo product with price_overridden false, the stored price stands). The Odoo link and the owner cannot be changed.

Optional trial-end hand-over: transfer_partner_id (a reseller that accepts customers from you) and transfer_plan_id (a plan it can sell). When a customer's trial ends on this plan, Cockpit opens a pending transfer request to that reseller instead of applying the normal trial-end rules; if the reseller rejects it, or the request is canceled, the normal rules apply. A target that was deactivated, deleted or stopped accepting customers from you is ignored at trial end. Re-pointing transfer_partner_id without sending transfer_plan_id drops the stored target plan; send null to clear a field.

A plan of another partner answers 404; a global Voxbi plan answers 403 because it is read only.

Required scopes: partner-api and partner-plans-write.

HTTP: PartnerApiKey

Partner API key. A bearer token that a Reseller Admin generates in the partner portal under Settings, API keys. It belongs to the reseller partner, not to a person, and carries the scopes ticked at creation time. Send it on every request as Authorization: Bearer <partner_api_key>.

Every response is confined to the partner the key belongs to. Keep the key secret and call the API from your server only, never from a browser app.

HTTP Authorization Scheme
bearer
Bearer format
Bearer <partner_api_key>
plan path · string · uuid *
Plan id (UUID). It must be one of your plans or a global plan, otherwise the answer is `404`.
accept header · string
example: application/json

Request body · required · application/json

Request schema
namestring
Plan name. A name can be used only once per country within your organization, case-insensitive.
length: 0–255
example: Business
countriesarray<string>
ISO 3166-1 alpha-2 codes of the countries the plan is offered in (case-insensitive, duplicates ignored). Required on create. On update, a plan that already has countries cannot be emptied; an older plan with none may stay without (offered in every country).
items: 1–∞
[]string
length: 2–2
descriptionstring | null
length: 0–2000
example: For growing teams
featuresstring | null
One feature per line.
length: 0–2000
example: Unlimited extensions Call recording
price_per_usernumber
Monthly price per user. For a plan linked to Odoo with `price_overridden` false the stored price stands whatever you send.
≥ 0
example: 9.9
price_overriddenboolean
example:
currencystring
pattern: ^[A-Z]{3}$
example: EUR
yearly_discountnumber | null
≥ 0≤ 100
example: 10
max_usersinteger | null
Null means unlimited.
≥ 1
example: 25
max_numbersinteger | null
Null means unlimited. A plan with numbers (null or above zero) must have `kyc_required` true.
≥ 0
example: 10
numbers_are_fixedboolean
example:
ai_enabledboolean
example: 1
stats_enabledboolean
example: 1
tempus_enabledboolean
example:
is_free_trial_allowedboolean
example: 1
free_trial_daysinteger
Required when `is_free_trial_allowed` is true.
≥ 0
example: 14
downgrade_ceremony_daysinteger | null
≥ 0
example: 3
card_verification_requiredboolean
example:
card_deadline_daysinteger
Required on create.
≥ 0
example: 0
card_reminder_days_beforeinteger
Required on create. Cannot exceed the card deadline (or the trial) window.
≥ 0
example: 0
kyc_requiredboolean
example: 1
kyc_deadline_daysinteger
Required on create.
≥ 0
example: 7
kyc_reminder_days_beforeinteger
Required on create. Cannot exceed the KYC deadline (or the trial) window.
≥ 0
example: 2
after_deadline_actionstring
Required on create. `downgrade` needs `downgrade_to_plan_id`.
enum: block_services downgrade stay
example: stay
downgrade_to_plan_idstring | null · uuid
An active plan you can see, other than this one.
transfer_partner_idstring | null · uuid
The reseller a customer is handed to, as a pending transfer request it must accept, when its trial ends on this plan. An active reseller that lists you as a source of customers in its settings; null clears it.
transfer_plan_idstring | null · uuid
The plan the customer gets at that reseller: one of its active plans or an active global plan. Needs `transfer_partner_id`; null means the customer moves without a plan.
is_activeboolean
A plan a signup link or your default signup plan still starts cannot be deactivated.
example: 1
sort_orderinteger | null
≥ 0
example: 0

Responses

Response schema
dataobject
A plan the partner that owns the API key may see: its own plans and the global Voxbi plans. Plans of other partners are never returned. A plan is offered in the countries listed in `countries`; an empty list means it is offered in every country (every plan created before countries existed behaves this way). Money values are decimal strings, never floats.
idstring · uuid
Unique identifier of the plan.
example: 550e8400-e29b-41d4-a716-446655440201
namestring
Plan name. Unique per country within your organization, case-insensitive.
example: Business
descriptionstring | null
Short description shown to customers.
example: For growing teams
price_per_userstring
Monthly price per user, as a decimal string.
example: 9.9000
currencystring
ISO 4217 currency code.
example: EUR
yearly_discountstring
Discount in percent when billed yearly, as a decimal string.
example: 10.00
price_overriddenboolean
For a plan linked to an Odoo product, whether your own price is kept when Odoo syncs. When false the price follows Odoo and cannot be changed through the API.
example:
max_usersinteger | null
Users included. Null means unlimited.
example: 25
max_numbersinteger | null
Phone numbers included. Null means unlimited, zero means none.
example: 10
numbers_are_fixedboolean
True when `max_numbers` is an exact count rather than a ceiling.
example:
ai_enabledboolean
Recording and AI features included.
example: 1
stats_enabledboolean
Statistics included.
example: 1
tempus_enabledboolean
Tempus time tracking included.
example:
featuresstring | null
Feature lines as stored, one per line.
example: Unlimited extensions Call recording
features_listarray<string>
The same feature lines, split and trimmed, empty lines dropped. Read only.
[]string
is_free_trial_allowedboolean
Whether a signup can start a free trial on this plan.
example: 1
free_trial_daysinteger
Length of the free trial in days.
example: 14
card_verification_requiredboolean
Customers must verify a payment card.
example:
card_deadline_daysinteger
Days to verify the card.
example: 0
card_reminder_days_beforeinteger
Days before the card deadline a reminder is sent.
example: 0
kyc_requiredboolean
Customers must complete KYC verification. Cannot be false on a plan that includes phone numbers.
example: 1
kyc_deadline_daysinteger
Days to complete KYC.
example: 7
kyc_reminder_days_beforeinteger
Days before the KYC deadline a reminder is sent.
example: 2
after_deadline_actionstring
What happens when a compliance deadline passes.
enum: block_services downgrade stay
example: stay
downgrade_to_plan_idstring | null · uuid
The plan customers are moved to when the action is `downgrade`.
downgrade_ceremony_daysinteger | null
Notice period in days before a downgrade.
example: 3
transfer_partner_idstring | null · uuid
The reseller a customer is handed to when its trial ends on this plan. Null when unset.
transfer_partner_namestring | null
Name of `transfer_partner_id`.
transfer_plan_idstring | null · uuid
The plan the customer gets at the target reseller. Null when unset (the customer moves without a plan).
transfer_plan_namestring | null
Name of `transfer_plan_id`.
is_activeboolean
Whether the plan is on sale. Customers already on an inactive plan keep it.
example: 1
sort_orderinteger
Display order, ascending.
example: 0
countriesarray<string>
ISO 3166-1 alpha-2 codes of the countries the plan is offered in, sorted. Empty means every country.
[]string
length: 2–2
signup_urlstring · uri
The public signup link that starts a trial on this plan, to publish on your website.
example: https://cockpit.voxbi.com/acme/signup/550e8400-e29b-41d4-a716-446655440201
customers_countinteger | null
How many of YOUR customers are on the plan now. Never counts another partner's customers on a global plan.
example: 4
sourcestring
Where the plan comes from, `manual` or `odoo`.
example: manual
is_globalboolean
True for a Voxbi plan offered to every partner. Global plans are read only.
example:
editableboolean
Whether your key's partner may change or delete the plan.
example: 1
created_atstring · date-time
example: 2026-10-01T08:00:00+00:00
updated_atstring · date-time
example: 2026-10-29T14:05:00+00:00
Unauthenticated. The `Authorization: Bearer <token>` header was missing, malformed, or names a token that has been revoked or deleted.
Response schema
messagestring
Forbidden. Either the key lacks `partner-plans-write` (the body lists `required_scopes`), or the plan is visible to you but not yours to change: a global Voxbi plan can be read but not edited or deleted.
Response schema
messagestring
Human-readable reason.
example: This API key is missing a required scope.
required_scopesarray<string>
Present on the missing-scope 403 only. Scopes the endpoint needs that the key lacks.
[]string
Not Found. No plan with this id is visible to your partner (a plan of another partner, a nonexistent id and an id that is not a UUID answer the same way).
Response schema
messagestring
Human-readable reason.
example: This API key is missing a required scope.
required_scopesarray<string>
Present on the missing-scope 403 only. Scopes the endpoint needs that the key lacks.
[]string
Unprocessable Content. A query parameter or the body failed validation (`errors` keyed by field). Plans are unique per country: reusing a name in a country where you already sell a plan of that name fails on `name` and names the clashing countries. Nothing was changed.
Response schema
messagestring
The first validation error.
errorsobject
Validation errors keyed by field.
Free-form object
Too Many Requests. Two budgets apply: 120 requests per minute per API key, and 600 per minute across all of a partner's keys together. Wait for the number of seconds in the `Retry-After` header before retrying.
Response schema
messagestring
patch https://cockpit.voxbi.com/api/v1/api/v1/plans/{plan}
Base URL
Request sample
curl -X PATCH 'https://cockpit.voxbi.com/api/v1/api/v1/plans/{plan}' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  --data '{"price_per_user":11.5,"transfer_partner_id":"550e8400-e29b-41d4-a716-446655440301","transfer_plan_id":"550e8400-e29b-41d4-a716-446655440302","countries":["AT","LU","DE"]}'
const response = await fetch('https://cockpit.voxbi.com/api/v1/api/v1/plans/{plan}', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${YOUR_TOKEN}`,
  },
  body: JSON.stringify({
    "price_per_user": 11.5,
    "transfer_partner_id": "550e8400-e29b-41d4-a716-446655440301",
    "transfer_plan_id": "550e8400-e29b-41d4-a716-446655440302",
    "countries": [
        "AT",
        "LU",
        "DE"
    ]
}),
});

const data = await response.json();
console.log(data);
import requests

response = requests.patch('https://cockpit.voxbi.com/api/v1/api/v1/plans/{plan}',
    headers={'Authorization': f'Bearer {YOUR_TOKEN}'},
    json={
    "price_per_user": 11.5,
    "transfer_partner_id": "550e8400-e29b-41d4-a716-446655440301",
    "transfer_plan_id": "550e8400-e29b-41d4-a716-446655440302",
    "countries": [
        "AT",
        "LU",
        "DE"
    ]
}
)
response.raise_for_status()
data = response.json()
print(data)
<?php
$context = stream_context_create([
    'http' => [
        'method'  => 'PATCH',
        'header'  => "Content-Type: application/json\r\nAuthorization: Bearer YOUR_TOKEN",
        'content' => '{
    \"price_per_user\": 11.5,
    \"transfer_partner_id\": \"550e8400-e29b-41d4-a716-446655440301\",
    \"transfer_plan_id\": \"550e8400-e29b-41d4-a716-446655440302\",
    \"countries\": [
        \"AT\",
        \"LU\",
        \"DE\"
    ]
}',
    ],
]);

$response = file_get_contents('https://cockpit.voxbi.com/api/v1/api/v1/plans/{plan}', false, $context);
$data = json_decode($response, true);
print_r($data);
Sample request
{ … }
"price_per_user": 11.5,
"transfer_partner_id": "550e8400-e29b-41d4-a716-446655440301",
"transfer_plan_id": "550e8400-e29b-41d4-a716-446655440302",
"countries": [ … ]
"AT",
"LU",
"DE"
]
}
{ … }
"data": { … }
"id": "550e8400-e29b-41d4-a716-446655440201",
"name": "Business",
"description": "For growing teams",
"price_per_user": "11.5000",
"currency": "EUR",
"yearly_discount": "10.00",
"price_overridden": false,
"max_users": 25,
"max_numbers": 10,
"numbers_are_fixed": false,
"ai_enabled": true,
"stats_enabled": true,
"tempus_enabled": false,
"features": "Unlimited extensions Call recording",
"features_list": [ … ],
"Unlimited extensions",
"Call recording"
],
"is_free_trial_allowed": true,
"free_trial_days": 14,
"card_verification_required": false,
"card_deadline_days": 0,
"card_reminder_days_before": 0,
"kyc_required": true,
"kyc_deadline_days": 7,
"kyc_reminder_days_before": 2,
"after_deadline_action": "stay",
"downgrade_to_plan_id": null,
"downgrade_ceremony_days": 3,
"transfer_partner_id": null,
"transfer_partner_name": null,
"transfer_plan_id": null,
"transfer_plan_name": null,
"is_active": true,
"sort_order": 0,
"countries": [ … ],
"AT",
"DE",
"LU"
],
"signup_url": "https://cockpit.voxbi.com/acme/signup/550e8400-e29b-41d4-a716-446655440201",
"customers_count": 4,
"source": "manual",
"is_global": false,
"editable": true,
"created_at": "2026-10-01T08:00:00+00:00",
"updated_at": "2026-10-29T14:05:00+00:00"
}
}
{ … }
"message": "Unauthenticated."
}
Content-Type string
example: application/json
{ … }
"message": "This API key is missing a required scope.",
"required_scopes": [ … ]
"partner-plans-write"
]
}
Content-Type string
example: application/json
{ … }
"message": "Plan not found."
}
Content-Type string
example: application/json
{ … }
"message": "You already have a plan with this name in Luxembourg. Plans are unique per country: choose a different name or different countries.",
"errors": { … }
"name": [ … ]
"You already have a plan with this name in Luxembourg. Plans are unique per country: choose a different name or different countries."
]
}
}
Content-Type string
example: application/json
{ … }
"message": "Too Many Attempts."
}
Retry-After integer
Seconds to wait before retrying.
example: 30
Content-Type string
example: application/json