Topolo Docs Topolo Platform
Open workspace

Issue campaign codes — Campaigns

Issues an explicit code once, or generates count codes (default one, declared maximum 1000). It stores active code records for an existing campaign; this path does not itself require the campaign to be active.

Issue campaign codes

Issues an explicit code once, or generates count codes (default one, declared maximum 1000). It stores active code records for an existing campaign; this path does not itself require the campaign to be active.

Outcome and boundaries

Issues an explicit code once, or generates count codes (default one, declared maximum 1000). It stores active code records for an existing campaign; this path does not itself require the campaign to be active.

Actual validation/redemption still requires an active campaign. Issuance does not create a batch association, Quro distribution, physical asset or completed journey.

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.codes:write. 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_write 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

  • Read campaigns.get; use its id. Retain codes[].id as the code-record id and codes[].code as the redeemable string; they are different identifiers.

Action: app_topolo_campaigns.codes.issue · POST /api/campaigns/{id}/codes.

Input fields

FieldRequiredMeaning and validation
idYesCampaign identifier from campaigns[].id; scoped to the selected organization/workspace. It is not a batch/code/placement id. string; minLength=1
codeNoHuman code string from codes[].code; preserve exact spelling and keep it distinct from a record id. string
countNoInteger 1–1000 under the published schema; omitted is one. Used only when an explicit nonempty code is absent. integer; minimum=1; maximum=1000
ownerRefNoOpaque stored owner reference, not a user lookup or commission grant. string

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.codes.issue --data @payload.json --json
topolo actions plan app_topolo_campaigns.codes.issue --resource-type workspace --resource-id "$CAMPAIGNS_WORKSPACE_ID" --data @payload.json --json

The published action requires confirmation. Only after the exact mutation is authorized, use that same payload/context with --confirm on plan/call; no operation in this guide has been executed.

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.

Issue one explicit code · HTTP 201

Input:

{
  "id": "campaign_example",
  "code": "EXAMPLE-20",
  "ownerRef": "partner_example"
}

Illustrative response:

{
  "codes": [
    {
      "id": "code_example",
      "campaignId": "campaign_example",
      "batchId": null,
      "code": "EXAMPLE-20",
      "ownerRef": "partner_example",
      "quroSlug": null,
      "status": "active",
      "createdAt": "2026-10-20T00:00:00.000Z"
    }
  ]
}

Generate two codes · HTTP 201

Input:

{
  "id": "campaign_example",
  "count": 2
}

Illustrative response:

{
  "codes": [
    {
      "id": "code_example",
      "campaignId": "campaign_example",
      "batchId": null,
      "code": "EXAMPLE-20",
      "ownerRef": null,
      "quroSlug": null,
      "status": "active",
      "createdAt": "2026-10-20T00:00:00.000Z"
    },
    {
      "id": "code_example_2",
      "campaignId": "campaign_example",
      "batchId": null,
      "code": "EXAMPLE-21",
      "ownerRef": null,
      "quroSlug": null,
      "status": "active",
      "createdAt": "2026-10-20T00:00:00.000Z"
    }
  ]
}

Duplicate explicit code · HTTP 400

Input:

{
  "id": "campaign_example",
  "code": "EXAMPLE-20"
}

Illustrative response:

{
  "error": "code_already_exists"
}

Verify the outcome

  • List codes filtered by campaignId; match returned code strings, record ids and count before another issue request.

Refusals and recovery

  • HTTP 400 campaign_not_found: Correct the named input or retain the authoritative refusal in the same context.
  • HTTP 400 code_already_exists: Correct the named input or retain the authoritative refusal in the same context.
  • 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 deletion or exact undo is published for this record. Preserve the returned identifier and inspect the corresponding list before considering another separately authorized operation. A timeout does not prove that nothing was written.

Source and catalog limits

Published active-only issuance and mandatory code/label wording is not the reviewed implementation. The native action has no label or batchId input. Explicit code takes precedence over count. Generation inserts rows in a loop; a failure may follow partial inserts, so blind retry can duplicate issuance.

Exact schemas and provenance

Input schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "minLength": 1
    },
    "code": {
      "type": "string"
    },
    "count": {
      "type": "integer",
      "minimum": 1,
      "maximum": 1000
    },
    "ownerRef": {
      "type": "string"
    }
  },
  "required": [
    "id"
  ],
  "additionalProperties": false
}

Output schema:

{
  "type": "object",
  "title": "Issue codes response",
  "description": "Operation result returned by codes.issue.",
  "properties": {
    "success": {
      "type": "boolean"
    },
    "id": {
      "type": "string"
    },
    "data": {
      "anyOf": [
        {
          "type": "object",
          "title": "code record",
          "properties": {
            "id": {
              "type": "string",
              "description": "Stable identifier when the code exposes one."
            }
          },
          "additionalProperties": true
        },
        {
          "type": "object",
          "additionalProperties": true
        }
      ]
    },
    "result": {
      "type": "object",
      "additionalProperties": true
    },
    "message": {
      "type": "string"
    }
  },
  "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.

  • Get campaign — Campaigns: Reads one non-deleted campaign in the bound organization and workspace. An absent or differently scoped campaign returns HTTP 404 campaign_not_found.
  • List issued codes — Campaigns: Lists code records in the bound organization/workspace, optionally filtered by campaignId; results are ordered newest creation first.
  • Enable, disable or associate a code — Campaigns: Updates active/disabled status, optional batchId and stored quroSlug for one code string in this workspace. It does not change a label, call Quro, or prove a physical placement.
  • Check code eligibility — Campaigns: Evaluates a code and returns HTTP 200 with valid:true or valid:false and a reason. This read does not consume redemption capacity.

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.

{
  "id": "campaign_example",
  "code": "EXAMPLE-20",
  "ownerRef": "partner_example",
  "__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.

These observations come from pure checks of the exact published schema; they are not server responses or production captures.