List workspace offers
Lists stored offers in the bound organization/workspace, newest update first. Reading an offer does not establish that any campaign or code uses it.
Outcome and boundaries
Lists stored offers in the bound organization/workspace, newest update first. Reading an offer does not establish that any campaign or code uses it.
Before you start
- Confirm the intended environment and current credential organization using whoami; discover the exact application/action before any call.
- Required application permission: app_topolo_campaigns.offers:read. Catalog discoverability is not permission to execute.
- Discover an authorized canonical workspace and bind resourceType workspace/resourceId separately from action input.
- All worked cases in this guide are hypothetical source-reviewed illustrations; no business action, live fixture, customer data or provider request was executed.
- Canonical workspace minimum access is read in addition to the application permission. After grant verification, the first request may create an app-local identity/recipient mirror even before the later app-permission check; this is not campaign-domain completion.
Discover the intended identifiers
- Discover a granted workspace; this action takes no campaign id.
Action: app_topolo_campaigns.offers.list · GET /api/offers.
Input fields
No input fields. Send {}. Any published resource context remains separate from this object.
Safe call sequence
Write the reviewed input to payload.json; fixture identifiers below are unusable examples. Validate before planning, bind the discovered resource separately, and execute only within actual authority.
topolo actions validate app_topolo_campaigns.offers.list --data @payload.json --json
topolo actions plan app_topolo_campaigns.offers.list --resource-type workspace --resource-id "$CAMPAIGNS_WORKSPACE_ID" --data @payload.json --json
A published read can use topolo actions call with the same discovered resource and input. This guide does not invoke it.
Worked cases
Every response below is synthetic and source-reviewed, not captured. HTTP status and body are explained independently; a structurally valid body alone never proves successful execution. Middleware may add a different error envelope.
One stored offer · HTTP 200
Input:
{}
Illustrative response:
{
"offers": [
{
"id": "offer_example",
"kind": "discount_pct",
"value": 15,
"label": "Illustrative 15 percent",
"rules": {},
"createdAt": "2026-10-20T00:00:00.000Z",
"updatedAt": "2026-10-20T00:00:00.000Z"
}
]
}
Empty workspace · HTTP 200
Input:
{}
Illustrative response:
{
"offers": []
}
Permission refusal · HTTP 403
Input:
{}
Illustrative response:
{
"error": "forbidden"
}
Verify the outcome
- Inspect offers[].id, kind, value, label and rules. Use a returned id when creating or updating a campaign.
Refusals and recovery
- HTTP 403
forbidden: Raw handler permission refusal. SDKs can normalize it to permission_denied. Keep the same intended resource; do not widen access. - HTTP 403
workspace_forbidden: The canonical workspace grant refused the selected resource. Resolve it with the access owner, not a guessed workspace.
No campaign-domain rollback is needed for this read. Preserve authentication, permission and resource refusals; do not switch organization or widen a grant to bypass them.
Exact schemas and provenance
Input schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {},
"additionalProperties": false
}
Output schema:
{
"title": "List offers response",
"description": "Collection response returned by offers.list. The service may return the collection directly or in an envelope.",
"oneOf": [
{
"type": "array",
"items": {
"type": "object",
"title": "offer record",
"properties": {
"id": {
"type": "string",
"description": "Stable identifier when the offer exposes one."
}
},
"additionalProperties": true
}
},
{
"type": "object",
"properties": {
"data": {
"oneOf": [
{
"type": "array",
"items": {
"type": "object",
"title": "offer record",
"properties": {
"id": {
"type": "string",
"description": "Stable identifier when the offer exposes one."
}
},
"additionalProperties": true
}
},
{
"type": "object",
"additionalProperties": true
}
]
},
"items": {
"type": "array",
"items": {
"type": "object",
"title": "offer record",
"properties": {
"id": {
"type": "string",
"description": "Stable identifier when the offer exposes one."
}
},
"additionalProperties": true
}
},
"offers": {
"type": "array",
"items": {
"type": "object",
"title": "offer record",
"properties": {
"id": {
"type": "string",
"description": "Stable identifier when the offer exposes one."
}
},
"additionalProperties": true
}
},
"count": {
"type": "integer",
"minimum": 0
},
"nextCursor": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
}
],
"x-topolo-contract-source": "derived-action-metadata",
"x-topolo-contract-precision": "structural"
}
Reviewed immutable source: TopoloCampaigns@cacbb8b12b4e19a662cf82d03a927aef3c3a35e8, apps/api/src/campaigns-store.ts. Fresh production catalog was read on 2026-10-20. Its publication metadata is not proof that this source is loaded. Output examples are not widened or rewritten to force schema success; the accompanying validation record classifies exact success, refusal, no-body and structural-schema limits.
Related actions
- Create a draft campaign — Campaigns: Creates one draft campaign. Optional offerId must resolve in the selected organization/workspace. Creating it neither activates redemption nor issues codes.
- Update or activate a campaign — Campaigns: Changes campaign name, offer or draft/active/paused/archived status. Activation allows existing active codes to validate/redeem; it does not issue or distribute codes.
Local input rejection example
This deliberately invalid input belongs to a pure schema check; it is never a planned or executed request. The closed action input schema must reject the extra field. It has no HTTP status or server response.
{
"__unsupported_field": true
}
The hidden companion labels this as ILLUSTRATIVE_LOCAL_SCHEMA_CHECK; its validation representation is not an action output.
Published output validation limits
Current published output qualification: the output schema accepts an empty object, a partial success marker, a partial data object and a refusal body. It therefore establishes only broad structure. Require the documented HTTP/domain result and source-shaped fields; missing fields or a schema-valid error are not success. The current oneOf array/object branches have disjoint types, including the nested data branches; no oneOf ambiguity was observed. Their missing required collection fields still permit empty or partial objects.
These observations come from pure checks of the exact published schema; they are not server responses or production captures.