Channels
A channel is a route to market: a webshop, a punch-out catalogue, a feed, a kiosk. It is the second scoping axis after markets, and what it decides is assortment visibility.
Base path: https://api.revenexx.com/v1/channels.
Channel types
A type is what lets a merchant name a kind of channel this app never thought of, without waiting for a release.
curl -X POST https://api.revenexx.com/v1/channels/types \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"code":"kiosk","title":"Kiosk","position":40}'
code and title are the only two required. The code is trimmed, lowercased and fixed from then on — it becomes exactly what channels.type stores, and there is no foreign key behind that column to carry a rename, so every channel holding the old string would be left pointing at nothing. title is the part a merchant renames later.
Two things worth knowing about the 409:
- The uniqueness is wider than your tenant. The code is unique on the column alone, so a code held by another tenant collides too — and the read this route does before inserting cannot see it.
- A tenant keeps at most 200 types. The 201st is a
409withtype_limit_reached.
Creating a type changes nothing about existing channels: it is a name that becomes available, not one that gets applied. It does not become the default either — pass is_default: true for that, which demotes the current holder.
A type has its own route by row id, the uuid the list returns, not the code: GET /v1/channels/types/{id} reads one type, PUT /v1/channels/types/{id} updates it, and DELETE /v1/channels/types/{id} removes it. PUT changes everything except the code, and a different code answers 400. The example below retires a type; on the default type it answers 409 default_must_stay_active. DELETE answers 409 while a channel still uses the type, for the last type left, and for the default while no other active type can take the flag (default_must_stay_active).
curl -X PUT "https://api.revenexx.com/v1/channels/types/{id}" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"active":false}'
active (boolean, default true) offers the type for new channels. Set it to false to retire a type: it stays on the channels that carry it but is no longer offered for new ones. Making a retired type the default is 409 inactive_cannot_be_default.
GET /v1/channels/types filters on active (?active=true or ?active=false), and it is the only filter that list honours. Any other value is a 400 invalid_value. A body active that is not a boolean is a 400 invalid_active. The default type cannot be retired: 409 default_must_stay_active. Make another type the default first.
Channels
curl -X POST https://api.revenexx.com/v1/channels \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"code":"punchout","name":"Punch-out catalogue","type":"punchout","unassigned_visibility":"assigned_only"}'
| Field | Meaning |
|---|---|
code | Identifier, and what a caller names to select the channel. A rename detaches its assignments, see Updating and deleting a channel. |
name, labels | Display name and localized names. |
type | One of the tenant's channel type codes. |
status | active or inactive. |
is_default | The channel a caller naming none falls back to. |
unassigned_visibility | inherit (use the tenant answer), all, or assigned_only. |
position | Sort order, and the tie-break when more than one channel claims default. |
POST /v1/channels/defaults restores the shipped defaults.
Listing channels
GET /v1/channels pages through the channels, 50 at a time by default (limit up to 200, offset, and order as column, column.asc or column.desc). Every column is an exact-match filter, and they combine. ?code= finds the channel a scope slug names, ?is_default=true finds the fallback channel without resolving a context, and ?unassigned_visibility=assigned_only finds the channels that closed their assortment. A query parameter that is not a column of channels is refused, not ignored: 400 with unknown_filter, and details.allowed lists the parameters the list takes. An order that names an unknown column is 400 too.
curl "https://api.revenexx.com/v1/channels?unassigned_visibility=assigned_only" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..."
Updating and deleting a channel
PUT /v1/channels/{id} takes the fields to change and keeps the rest, and an empty body is 400. {id} is the row's uuid, not the code. The answer is the channel as it now stands. Three of its fields need care:
codeis accepted, and nothing follows it. The assignments other apps hold against the old slug stay under that slug, so a rename silently detaches every one of them. The channel types route refuses the same edit.is_default: falseon the only default channel leaves the tenant with no default. Every request naming no channel then resolves to nothing (no_default_channel). Promote another channel first.typenaming a type the tenant has retired is409type_inactive. Atypethe tenant does not keep at all is400. Resending the retired type a channel already carries is accepted. Acodeused by another channel is409unique_violation.
curl -X PUT "https://api.revenexx.com/v1/channels/{id}" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"unassigned_visibility":"assigned_only"}'
DELETE /v1/channels/{id} removes the row and nothing else. Nothing cascades, because the app declares no foreign key. The assignments other apps hold for the channel stay where they are, keyed by the slug, and they outlive the row. A new channel created later under the same code silently adopts all of them, so choose a new code if that is not wanted. Deleting the default channel is allowed and leaves the tenant with none, as above. POST /v1/channels/defaults creates the seeded shop channel, as the default, only when no channel with the code shop exists; it never moves the flag onto an existing channel, and it does not take the flag off another one, so a tenant whose default is another channel ends up with two. To restore a default otherwise, PUT a channel with {"is_default": true}. A non-uuid {id} is 400.
curl -X DELETE "https://api.revenexx.com/v1/channels/{id}" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..."
The bootstrap
curl "https://api.revenexx.com/v1/channels/context?channel=punchout" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..."
One call tells a shop front, a punch-out front-end or a feed builder which channel it is in and what an unassigned row means there, so it applies the policy itself instead of hard-coding one.
Two things come back, not one: the channel that resolved, and the visibility policy in force for it. The policy travels with the channel because a caller holding one and not the other still cannot render anything — knowing you are in the punch-out channel says nothing about whether an unassigned product belongs in its assortment.
Resolution order
Through the public gateway it is: ?channel=, then the principal's channel claim, then the channel flagged is_default. source in the response names which of them answered.
api.revenexx.com the header step is inert because the gateway does not forward it. Use the query parameter.It never errors on an unknown channel
That is deliberate, and it is why this operation declares no 4xx of its own. An unknown or inactive channel answers 200 with resolved: false and a reason, so a caller can tell no such channel from the service is down:
channel_required no_default_channel unknown_channel channel_inactive
A tenant with no channels at all answers 200 with reason: "no_default_channel". default_ambiguous says when more than one channel claims the flag — the lowest position wins.
The visibility gate
curl -X POST https://api.revenexx.com/v1/channels/visibility \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{
"channel": "punchout",
"items": [
{ "id": "…", "channels": ["webshop"] },
{ "id": "…", "channels": [] }
]
}'
Two cases, and only one of them needs a policy:
- A row with channel assignments is visible if and only if the active channel is among them. No configuration involved.
- A row without assignments is the case
unassigned_channel_visibilityowns:allshows it,assigned_onlyhides it.
A channel may override the tenant answer for itself, through its own unassigned_visibility. That is how the webshop stays open while a punch-out channel serves only its negotiated assortment.
assigned_only on that channel — do not rely on the assignment absence.The same asymmetry applies to markets: a row created without X-Revenexx-Market is unassigned, and unassigned is visible everywhere. See Settings.
Vocabularies
These are the closed value sets this app owns, ready for a select box. GET /v1/channels/vocabularies lists the names with their title and description, without the values. GET /v1/channels/vocabularies/{name} returns one vocabulary with its values in the order a select box should offer them. Each value carries its title, description and a badge tone, and default_tone is what a value without one falls back to. The names are statuses, types and unassigned-visibility; any other name is 404.
curl "https://api.revenexx.com/v1/channels/vocabularies/unassigned-visibility" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..."
statuses and unassigned-visibility are fixed by the app. types is the tenant's own list of channel types, so it changes when a type is added, renamed or retired. A retired type is not offered, but it is still returned under inactive, so a screen can render a channel that already carries it.
Where channels show up elsewhere
| Place | Effect |
|---|---|
A product attribute marked scopable | One value per channel, in attribute_values.channel_specific — see Products. |
family_attributes.required_channels | Narrows a required attribute to named channels. |
| A channel-scoped price list | Specificity level 2 in the price resolution order. |
carts.channel_id, orders.channel_id | Which channel the buying happened in. |
Where to go next
- Markets — the other scoping axis.
- Price lists — channel-scoped pricing.
- Products — channel-scoped attribute values.
- Connect a procurement system by punchout — a punchout account shops in its channel, so this is where a buyer's assortment is decided.