Topolo Docs Topolo Platform
Open workspace

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.

Create a draft campaign

Creates one draft campaign. Optional offerId must resolve in the selected organization/workspace. Creating it neither activates redemption nor issues codes.

Outcome and boundaries

Creates one draft campaign. Optional offerId must resolve in the selected organization/workspace. Creating it neither activates redemption nor issues codes.

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.campaigns: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

  • Use offers.list to resolve an offerId if needed; otherwise omit it. Discover the canonical Campaigns workspace separately.

Action: app_topolo_campaigns.campaigns.create · POST /api/campaigns.

Input fields

FieldRequiredMeaning and validation
typeYesCampaign category from the exact enum. It does not activate the campaign. string; enum=["promo", "influencer", "referral", "ambassador", "seasonal", "event"]
nameYesHuman-readable stored name. Whitespace can pass a schema string check yet fail a required-string domain check. string; minLength=1
offerIdNoOptional offer id discovered through offers.list in this same organization/workspace. Omit for no linked offer; an unknown or differently scoped offer is refused. string
commissionNoOptional kind/value object. pct computes a rounded percentage of recorded redemption value; fixed and tiered currently use the stored value as a fixed commission. This is recorded accounting, not a payout. object
attributionNoStored optional attribution object; storage is not proof that a provider event was attributed. 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.campaigns.create --data @payload.json --json
topolo actions plan app_topolo_campaigns.campaigns.create --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.

Create without an offer · HTTP 201

Input:

{
  "type": "event",
  "name": "Illustrative launch"
}

Illustrative response:

{
  "campaign": {
    "id": "campaign_example",
    "type": "event",
    "name": "Illustrative launch",
    "status": "draft",
    "offerId": null,
    "commission": null,
    "attribution": {},
    "createdAt": "2026-10-20T00:00:00.000Z",
    "updatedAt": "2026-10-20T00:00:00.000Z"
  }
}

Create with scoped offer and commission · HTTP 201

Input:

{
  "type": "event",
  "name": "Illustrative launch",
  "offerId": "offer_example",
  "commission": {
    "kind": "pct",
    "value": 10
  }
}

Illustrative response:

{
  "campaign": {
    "id": "campaign_example",
    "type": "event",
    "name": "Illustrative launch",
    "status": "draft",
    "offerId": "offer_example",
    "commission": {
      "kind": "pct",
      "value": 10
    },
    "attribution": {},
    "createdAt": "2026-10-20T00:00:00.000Z",
    "updatedAt": "2026-10-20T00:00:00.000Z"
  }
}

Unknown or foreign offer · HTTP 400

Input:

{
  "type": "event",
  "name": "Illustration",
  "offerId": "offer_absent"
}

Illustrative response:

{
  "error": "offer_not_found"
}

Verify the outcome

  • Match campaign.id, type, name and draft status; read campaigns.get before making a separate activation decision.

Refusals and recovery

  • HTTP 400 invalid_campaign_type: Correct the named input or retain the authoritative refusal in the same context.
  • HTTP 400 name_required: Correct the named input or retain the authoritative refusal in the same context.
  • HTTP 400 offer_not_found: 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

Source validates the offer lookup but stores commission/attribution objects; this does not establish pricing, consent or a complete attribution policy.

Exact schemas and provenance

Input schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "promo",
        "influencer",
        "referral",
        "ambassador",
        "seasonal",
        "event"
      ]
    },
    "name": {
      "type": "string",
      "minLength": 1
    },
    "offerId": {
      "type": "string"
    },
    "commission": {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "pct",
            "fixed",
            "tiered"
          ]
        },
        "value": {
          "type": "number"
        }
      },
      "required": [
        "kind",
        "value"
      ],
      "additionalProperties": false
    },
    "attribution": {
      "type": "object",
      "properties": {},
      "additionalProperties": {}
    }
  },
  "required": [
    "type",
    "name"
  ],
  "additionalProperties": false
}

Output schema:

{
  "type": "object",
  "title": "Create campaign response",
  "description": "Operation result returned by campaigns.create.",
  "properties": {
    "success": {
      "type": "boolean"
    },
    "id": {
      "type": "string"
    },
    "data": {
      "anyOf": [
        {
          "type": "object",
          "title": "campaign record",
          "properties": {
            "id": {
              "type": "string",
              "description": "Stable identifier when the campaign 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.

  • List workspace offers — Campaigns: Lists stored offers in the bound organization/workspace, newest creation first. Reading an offer does not establish that any campaign or code uses it.
  • 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.
  • 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.

{
  "type": "event",
  "name": "Illustrative launch",
  "__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.