Read a distributor's Odoo product and subscription catalogue (user)
THE server-to-server endpoint of this surface. A distributor calls it from their own system to read their Odoo products and, if their token allows, their subscriptions. Every other Odoo endpoint is private to the cockpit partner interface.
Auth
"Public" here means "not the cockpit UI session". It is NOT unauthenticated. Send a
Partner-owned Sanctum bearer token carrying the odoo-catalogue ability.
THE PARTNER IN THE PATH IS FOR READABILITY ONLY. The token decides which Odoo is read, and the request 404s unless the token's partner matches the path. A partner id is an identifier, not a secret, so a mismatch cannot be allowed to succeed. It answers 404 rather than 403 on purpose: confirming that some other partner id exists is itself a disclosure.
Unlike the session endpoints, this route is NOT behind the partner interface kill switch, so it never answers 503.
Two abilities, not one
odoo-catalogue gets you products. Subscriptions need odoo-subscriptions ON TOP,
because that block carries customer names and recurring_monthly /
recurring_total / non_recurring_total for every sale order including drafts and
churned deals. That is a commercial ledger rather than a price list, so it has to be
asked for separately.
Reading the subscription block
The three subscription keys are ALWAYS present, and subscriptions: [] alone never
tells you why. Read subscriptions_available first, then
subscriptions_unavailable_reason, which distinguishes three different situations a
caller must act on differently: the token lacks the ability, the partner's Odoo lacks
the Enterprise sale_subscription app, or the read failed. Products are still
returned in all three cases, deliberately, rather than losing what the caller came
for.
User bearer token. Default authentication for customer-facing endpoints.
Obtain a token by calling POST /login with your credentials, then send it
on every subsequent request as Authorization: Bearer <token>. The token
inherits the permissions and PBX scope of the authenticated user.
- HTTP Authorization Scheme
bearer- Bearer format
Bearer <token>
application/jsonResponses
Catalogue retrieveddataobject
products*array<object>
812Desk LampLAMP-00189.5EURAll / Office FurnitureUnits1subscriptions*array<object>
7731S00042customerobject | null
315Northwind TradingMonthly1_draft 2_renewal 3_progress 4_paused 5_renewed 6_churn 7_upsell null3_progress49.949.902026-09-01EUR2026-03-01 08:00:001errors*object
https://cockpit.voxbi.com/api/v1/odoo/public/{partner}/catalogue
curl -X GET 'https://cockpit.voxbi.com/api/v1/odoo/public/{partner}/catalogue' \
-H 'Authorization: Bearer YOUR_TOKEN'
const response = await fetch('https://cockpit.voxbi.com/api/v1/odoo/public/{partner}/catalogue', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${YOUR_TOKEN}`,
},
});
const data = await response.json();
console.log(data);
import requests
response = requests.get('https://cockpit.voxbi.com/api/v1/odoo/public/{partner}/catalogue',
headers={'Authorization': f'Bearer {YOUR_TOKEN}'}
)
response.raise_for_status()
data = response.json()
print(data)
<?php
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "Content-Type: application/json\r\nAuthorization: Bearer YOUR_TOKEN",
],
]);
$response = file_get_contents('https://cockpit.voxbi.com/api/v1/odoo/public/{partner}/catalogue', false, $context);
$data = json_decode($response, true);
print_r($data);
{ … }
"data": { … }
"products": [ … ],
{ … }
"subscriptions": [ … ],
{ … }
"customer": { … },
{ … }
{ … }
{ … }
{ … }
"errors": { … }
"limit": [ … ]
{ … }
22