# Remove campaign token unit

Remove a token unit from campaign. Admin or owning business.

Endpoint: DELETE /campaigns/{campaignId}/tokens/{tokenUnitId}
Version: 2.0.53
Security: authJWT

## Path parameters:

  - `campaignId` (string, required)

  - `tokenUnitId` (string, required)

## Response 200 fields (application/json):

  - `id` (string, required)
    Campaign id

  - `ownerBusinessId` (string, required)
    Optional owner business ID. If set, this campaign belongs to a specific business. If null, the campaign is tenant-owned (system-level).

  - `name` (string, required)
    Campaign name

  - `description` (string, required)
    Campaign description

  - `beneficiaryAccountAddress` (string, required)
    Campaign beneficiary account address

  - `startDate` (string, required)
    Campaign start date, default is the current date

  - `endDate` (string, required)
    Campaign end date

  - `imageUrl` (string, required)
    img url

  - `logoUrl` (string, required)
    Logo URL for the campaign

  - `externalUrl` (string, required)
    Campaign url

  - `isActive` (boolean, required)
    Campaign isActive

  - `approval` (object, required)
    Approval metadata for this campaign.

  - `approval.status` (string, required)
    Approval state: pending_approval = awaiting admin review, approved = active, rejected = denied.
    Enum: "pending_approval", "approved", "rejected"

  - `approval.approvedAt` (string)
    Timestamp when the entity was approved or rejected by an admin.

  - `approval.approvedBy` (string)
    Admin user ID who approved or rejected this entity.

  - `approval.rejectionReason` (string)
    Reason provided when the entity was rejected.

  - `isTestnet` (boolean)
    Campaign isTestnet, this means that the campaign is running on testnet, not mainnet

  - `createdAt` (string, required)
    create date

  - `updatedAt` (string, required)
    update date

  - `order` (number, required)
    Campaign order

  - `tags` (array, required)
    Campaign tags

  - `countryCodeRestrictions` (object)
    Country code restrictions as an array of strings (e.g., ["NOT_ES", "FR"])
    Example: ["NOT_ES","FR"]

  - `trigger` (object, required)
    Campaign trigger: what triggers the campaign, and what are the conditions for the trigger to be activated

  - `trigger.name` (number)
    Campaign trigger name

  - `trigger.description` (string)
    Campaign trigger description

  - `trigger.terms` (string)
    Terms and conditions for participation. Plain text or HTML. Multi-language handling at app level.

  - `trigger.maxPerDay` (number)
    DEPRECATED - use maxPerDayPerUser: Campaign trigger max per day

  - `trigger.maxPerDayPerUser` (number)
    Campaign trigger max per day per user

  - `trigger.maxPerUser` (number)
    Campaign trigger max per user

  - `trigger.minCooldownSeconds` (number)
    Campaign trigger min cooldown seconds

  - `trigger.maxGeoDistanceInMeters` (number)
    Campaign trigger max geo distance to Business in meters

  - `trigger.requiredUserInfo` (string)
    Campaign trigger required user info

  - `trigger.triggerType` (string)
    WHO can initiate a claim for this campaign.
**Values:**
- `CLAIM_BY_USER`: End users initiate claims (scan QR, tap NFC, enter geofence)
- `CLAIM_BY_BUSINESS`: Business claims on behalf of users (POS, kiosk, staff-assisted)
- `CLAIM_BY_SYSTEM`: Server/webhook automated claims (webhooks, scheduled jobs)

**Important:** This defines WHO can claim.
NOT to be confused with TriggerSource.type which defines HOW to claim (QR_CODE, NFC_TAG, GPS_GEOFENCE, etc.).
**App Logic:**
- If CLAIM_BY_USER: App shows claim UI based on triggerSources[].type
- If CLAIM_BY_BUSINESS: Requires business auth + userIdentifier in request
- If CLAIM_BY_SYSTEM: Server-to-server only, requires tenant API key
    Enum: "CLAIM_BY_USER", "CLAIM_BY_SYSTEM", "CLAIM_BY_BUSINESS"

  - `trigger.maxMultiplier` (number)
    Campaign trigger max multiplier

  - `trigger.completionThreshold` (number)
    Campaign trigger completion threshold. This indicates the number of completions required before the reward is granted

  - `trigger.maxTotal` (number)
    Campaign trigger max total completions across all users

  - `trigger.maxPerDayTotal` (number)
    Campaign trigger max total completions per day across all users

  - `trigger.maxPerSource` (number)
    Maximum claims per trigger source for this campaign. Limits how many times each individual trigger source can be used.

  - `trigger.conditions` (array)
    Campaign trigger conditions

  - `trigger.conditions.conditionType` (string, required)
    Trigger condition type
    Enum: "EQUALS", "NOT_EQUALS", "GREATER_THAN", "LESS_THAN", "CONTAINS", "IS_PART_OF"

  - `trigger.conditions.value` (object, required)
    Trigger condition value

  - `trigger.conditions.key` (string, required)
    Trigger condition key

  - `trigger.sourceLogic` (string)
    Source logic type defining how multiple trigger sources combine to activate the flow
    Enum: "any"

  - `trigger.id` (string, required)
    Campaign trigger id

  - `tokenUnits` (array, required)

  - `tokenUnits.id` (string, required)
    Database UUID for this token unit

  - `tokenUnits.token` (object, required)
    The token contract this unit references

  - `tokenUnits.token.id` (string, required)
    Database UUID for this token contract entity

  - `tokenUnits.token.contractAddress` (string, required)
    Smart contract address deployed on the blockchain

  - `tokenUnits.token.metadata` (array)
    Token metadata templates - blueprints used when minting NFTs. Each template defines properties (name, image, expiry) and has a unique tokenMetadataIncrementalId. Null for ERC20 (Points) tokens.

  - `tokenUnits.token.metadata.ownerBusinessId` (string)
    Optional owner business ID. If set, this token metadata belongs to a specific business. If null, the token metadata is tenant-owned (system-level).

  - `tokenUnits.token.metadata.isActive` (boolean)
    Whether this template is active and can be used for minting

  - `tokenUnits.token.metadata.imageUrl` (string)
    This is the URL to the image of the item. Can be just about any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.

  - `tokenUnits.token.metadata.externalUrl` (string)
    This is the URL that will appear below the asset's image

  - `tokenUnits.token.metadata.description` (string)
    A human-readable description of the item. Markdown is supported.
**ERC721 Template Interpolation:** Supports `{{placeholder}}` syntax for dynamic personalization at mint time (without AI costs).
**Available placeholders:**
- `{{user.firstName}}`, `{{user.lastName}}` - User data
- `{{campaign.name}}`, `{{business.displayName}}` - Campaign/business
- `{{context.xxx}}` - Dynamic context values (TriggerSource.context or claim request)
- `{{defaults.xxx}}` - Default values from defaultPromptContext

**Example:** `"Thank you {{user.firstName}} for visiting {{business.displayName}}! Your exclusive reward for {{context.eventName}}."`
**Processing order:** Template interpolation runs first, then AI processing (if configured) can further enhance the result.

  - `tokenUnits.token.metadata.name` (string)
    Name of the item.
**ERC721 Template Interpolation:** Supports `{{placeholder}}` syntax for dynamic personalization at mint time (without AI costs).
**Available placeholders:**
- `{{user.firstName}}`, `{{user.lastName}}` - User data
- `{{campaign.name}}`, `{{business.displayName}}` - Campaign/business
- `{{context.xxx}}` - Dynamic context values (TriggerSource.context or claim request)
- `{{defaults.xxx}}` - Default values from defaultPromptContext

**Example:** `"Welcome {{context.guestName}} - VIP Pass"` or `"{{user.firstName}}'s {{campaign.name}} Reward"`
**Processing order:** Template interpolation runs first, then AI processing (if configured) can further enhance the result.

  - `tokenUnits.token.metadata.validityType` (string)
    **ERC721 only** - Validity type defines how token expiry is calculated. Use with validityDuration for relative types. Ignored for ERC1155 tokens.
    Enum: "fixed_date", "days_from_issuance", "hours_from_issuance", "months_from_issuance", "end_of_month", "end_of_year", "trigger_date", "trigger_date_range", "days_from_trigger", "hours_from_trigger"

  - `tokenUnits.token.metadata.validityDuration` (number)
    **ERC721 only** - Duration in days or hours (used with days_from_issuance, hours_from_issuance validity types). Ignored for ERC1155 tokens.
    Example: 30

  - `tokenUnits.token.metadata.expiryDate` (string)
    Fixed expiry date (used with fixed_date validity type). For other validity types, this is computed at issuance time.

  - `tokenUnits.token.metadata.animationUrl` (string)
    A URL to a multi-media attachment for the item. The file extensions GLTF, GLB, WEBM, MP4, M4V, OGV, and OGG are supported, along with the audio-only extensions MP3, WAV, and OGA. Animation_url also supports HTML pages, allowing you to build rich experiences and interactive NFTs using JavaScript canvas, WebGL, and more. Scripts and relative paths within the HTML page are now supported. However, access to browser extensions is not supported.

  - `tokenUnits.token.metadata.youtubeUrl` (string)
    A URL to a YouTube video (only used if animation_url is not provide

  - `tokenUnits.token.metadata.creatorAccountAddress` (string)
    Creator Address

  - `tokenUnits.token.metadata.previewUrl` (string)
    Preview Url

  - `tokenUnits.token.metadata.tags` (array)
    Tags for categorization and filtering
    Example: ["summer","vip","limited-edition"]

  - `tokenUnits.token.metadata.aiPromptConfigs` (array)
    **ERC721 only** - AI prompt configurations for dynamic content generation at mint time.
AI processing runs AFTER template interpolation, so prompts can reference already-interpolated values.
Results are mapped by key to override static fields (name, description, imageUrl). Ignored for ERC1155 tokens.
**Note:** For simple personalization without AI costs, use `{{placeholder}}` syntax directly in name/description fields instead.
    Example: [{"type":"TEXT_GENERATION","key":"name","prompt":"Generate a unique reward name for {{user.firstName}} at {{business.displayName}}"},{"type":"IMAGE_GENERATION","key":"imageUrl","prompt":"A {{defaults.…

  - `tokenUnits.token.metadata.aiPromptConfigs.type` (string, required)
    Type of AI operation to perform
    Enum: "TEXT_GENERATION", "IMAGE_GENERATION"

  - `tokenUnits.token.metadata.aiPromptConfigs.key` (string, required)
    Key to store the generated result under
    Example: name

  - `tokenUnits.token.metadata.aiPromptConfigs.prompt` (string, required)
    The prompt to send to the AI model. Supports dynamic placeholders that are replaced with context values.
**Available Placeholders:**
- `{{user.firstName}}`, `{{user.lastName}}`, `{{user.email}}` - User profile data
- `{{campaign.name}}`, `{{campaign.description}}` - Campaign data
- `{{tenant.projectName}}`, `{{tenant.projectDescription}}` - Tenant/project data
- `{{business.displayName}}`, `{{business.name}}` - Business data
- `{{triggerSource.name}}`, `{{triggerSource.type}}` - Trigger source data
- `{{redemption.name}}`, `{{redemption.description}}` - Redemption data
- `{{context.xxx}}` - Dynamic context from TriggerSource.context or CampaignClaimRequest.context
- `{{defaults.xxx}}` - Default values from TokenMetadata.defaultPromptContext

**Context Sources:**
1. TriggerSource.context (admin QR/NFC) - Always applied
2. CampaignClaimRequest.context (user API) - Only if allowExternalContextOverwrite=true
3. TokenMetadata.defaultPromptContext - Always applied as fallback

**Examples:**
- `"Generate a welcome message for {{user.firstName}} at {{business.displayName}}"`
- `"Create a VIP badge for guest {{context.guestName}} in room {{context.roomNumber}}"`
- `"Design a {{defaults.style}} reward image for {{campaign.name}}"`
    Example: Generate a unique reward name for {{user.firstName}} visiting {{business.displayName}}

  - `tokenUnits.token.metadata.allowExternalContextOverwrite` (boolean)
    **ERC721 only** - Allow USER-PROVIDED external context from claim/redeem requests to be used in template interpolation and AI prompts.
**Security model:**
- `false` (default): Only admin-controlled context (TriggerSource.context, Redemption.context, defaultPromptContext) is available
- `true`: User-provided context from API requests is also merged and available as `{{context.xxx}}`

**Note:** Admin-controlled context (QR/NFC data) is ALWAYS applied regardless of this flag.
    Example: true

  - `tokenUnits.token.metadata.defaultPromptContext` (object)
    **ERC721 only** - Default context values for template interpolation and AI prompts. Available as `{{defaults.xxx}}` placeholders.
Always applied regardless of allowExternalContextOverwrite. Use for brand defaults, styling preferences, or fallback values.
**Example usage in name:** `"{{defaults.brand}} - {{user.firstName}}'s Reward"`
    Example: {"brand":"PERS Rewards","style":"minimal","defaultLocation":"Online"}

  - `tokenUnits.token.metadata.businessIds` (array)
    Business IDs where this token can be redeemed/spent. Empty array means any business in the tenant.
    Example: ["business-uuid-1","business-uuid-2"]

  - `tokenUnits.token.metadata.webhookId` (string)
    **ERC721 only** - Webhook ID for dynamic data fetch at mint time.
If set, the webhook is executed before metadata generation and response data is merged into the NFT.
**Use cases:**
- Fetch guest data from PMS/CRM systems
- Get real-time pricing or availability
- Retrieve user-specific content from external APIs

**Processing order:** Webhook fetch runs AFTER template interpolation but BEFORE AI processing.
    Example: webhook-uuid-for-pms-lookup

  - `tokenUnits.token.metadata.webhookPayloadTemplate` (object)
    **ERC721 only** - Payload template for webhook request.
Supports `{{placeholder}}` interpolation with same context as AI prompts.
**Example:**

```json
{
  "userId": "{{user.id}}",
  "campaignId": "{{campaign.id}}",
  "bookingId": "{{context.bookingId}}"
}
```
    Example: {"userId":"{{user.id}}","bookingId":"{{context.bookingId}}"}

  - `tokenUnits.token.metadata.webhookFieldMapping` (object)
    **ERC721 only** - Field mapping from webhook response to metadata fields.
Keys are webhook response paths (dot notation for nested), values are target field names.
**Mapping rules:**
- Explicit mapping: `"response.path": "targetField"`
- Known fields (name, description, imageUrl): auto-mapped if present
- Unknown fields or `"attributes"` target: become NFT attributes

**Example:**

```json
{
  "guestName": "name",
  "data.guest.image": "imageUrl",
  "roomNumber": "attributes",
  "checkInDate": "attributes"
}
```
If null/undefined, uses auto-mapping.
    Example: {"guestName":"name","roomNumber":"attributes","data.image":"imageUrl"}

  - `tokenUnits.token.metadata.consumable` (boolean)
    Whether this token template is consumable (burned on redemption) or collectible (transferred). Applies to ERC1155 and ERC721 only — ERC20 is always transferred. When true, the token is destroyed (burn) when used as payment in a redemption. Defaults to true — most token templates are consumable. Set to false for collectible/non-burning use cases. Mirrored as a consumable attribute in the on-chain IPFS metadata.

  - `tokenUnits.token.metadata.id` (string, required)
    Database UUID for this token metadata template

  - `tokenUnits.token.metadata.animationWeb3StorageUrl` (string)
    IPFS/Arweave URL for animation file - immutable web3 storage

  - `tokenUnits.token.metadata.imageWeb3StorageUrl` (string)
    IPFS/Arweave URL for image - immutable web3 storage

  - `tokenUnits.token.metadata.web3StorageUrl` (string)
    IPFS/Arweave URL for complete metadata JSON - this URL is stored on-chain and links to off-chain metadata

  - `tokenUnits.token.metadata.tokenMetadataIncrementalId` (number, required)
    Incremental ID within the token contract. For ERC1155: becomes the on-chain tokenId. For ERC721: lookup key for template to generate unique metadata.

  - `tokenUnits.token.metadata.approval` (object, required)
    Approval metadata for this token metadata.

  - `tokenUnits.token.metadata.mintCount` (number)
    Total number of mints for this token metadata (via ?include=mintCount). Counts SUCCEEDED MINT transactions using tokenMetadataIncrementalId. Accurate for both ERC1155 and ERC721 (post v2.3.49). Historical ERC721 transactions before v2.3.49 are not counted.

  - `tokenUnits.token.metadata.burnCount` (number)
    Total number of burns for this token metadata (via ?include=burnCount). Counts SUCCEEDED BURN transactions using tokenMetadataIncrementalId. Accurate for both ERC1155 and ERC721 (post v2.3.49). Historical ERC721 transactions before v2.3.49 are not counted.

  - `tokenUnits.token.metadata.included` (object)
    Included related entities. Only populated when include parameter is specified.

  - `tokenUnits.token.metadata.included.ownerBusiness` (object)

  - `tokenUnits.token.metadata.included.ownerBusiness.id` (string, required)
    The id of the business, this is unique and will be used to identify the business.

  - `tokenUnits.token.metadata.included.ownerBusiness.email` (string, required)
    The email of the business, this is unique and will be used to identify the business.

  - `tokenUnits.token.metadata.included.ownerBusiness.accountAddress` (string, required)
    The address of the business, this is the address that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.currentSigningAccountId` (string, required)
    Current active signing account ID for external wallet operations

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets` (array, required)
    Business-owned counterfactual smart contract wallets that can receive tokens

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.id` (string, required)
    Unique identifier for the internal wallet

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.ownerType` (string, required)
    Owner type for polymorphic ownership
    Enum: "user", "business", "tenant", "system", "external"

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.ownerId` (string, required)
    Owner ID for polymorphic ownership
    Example: user_123

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.walletManagementType` (string, required)
    Type of internal wallet
    Enum: "custodial", "non-custodial"

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.address` (string, required)
    CREATE2 generated address that can receive tokens

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.chainId` (number, required)
    Blockchain network chain identifier

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.status` (string, required)
    Current status of the wallet
    Enum: "pending", "active", "suspended", "archived"

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.ownerSigningAccountId` (string)
    ID of signing account that owns this internal wallet

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.tags` (array, required)
    Tags associated with the wallet for categorization

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.createdAt` (string, required)
    Timestamp when the wallet was created

  - `tokenUnits.token.metadata.included.ownerBusiness.wallets.updatedAt` (string, required)
    Timestamp when the wallet was last updated

  - `tokenUnits.token.metadata.included.ownerBusiness.businessLegalName` (string, required)
    The legal name of the business, this is the name that will be used for legal purposes.

  - `tokenUnits.token.metadata.included.ownerBusiness.registrationNumber` (string, required)
    The business registration number (e.g., company registration, VAT number, EIN)

  - `tokenUnits.token.metadata.included.ownerBusiness.displayName` (string, required)
    The display name of the business, this is the name that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.description` (string, required)
    The description of the business, this is the description that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.shortDescription` (string, required)
    The short description of the business, this is the description that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.websiteUrl` (string, required)
    The website of the business, this is the website that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.imageUrl` (string, required)
    The image of the business, this is the image that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.logoUrl` (string, required)
    Logo URL for the business

  - `tokenUnits.token.metadata.included.ownerBusiness.streetAddress` (string, required)
    The address of the business, this is the address that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.phoneNumber` (string, required)
    The phone number of the business.

  - `tokenUnits.token.metadata.included.ownerBusiness.neighborhood` (string, required)
    Neighborhood/area name (e.g., "West Bay", "Pearl Qatar", "Lusail") - auto-populated from geocoding

  - `tokenUnits.token.metadata.included.ownerBusiness.district` (string, required)
    District/administrative area - auto-populated from geocoding

  - `tokenUnits.token.metadata.included.ownerBusiness.city` (string, required)
    The city of the business, this is the city that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.postalCode` (string, required)
    The postal code of the business, this is the postal code that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.country` (string, required)
    The country of the business (auto-populated from geocoding if coordinates provided)

  - `tokenUnits.token.metadata.included.ownerBusiness.countryCode` (string, required)
    ISO 3166-1 alpha-2 country code (e.g., QA, US, AE) - auto-populated from geocoding
    Example: QA

  - `tokenUnits.token.metadata.included.ownerBusiness.coordsLatitude` (number, required)
    The latitude of the business, this is the latitude that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.coordsLongitude` (number, required)
    The longitude of the business, this is the longitude that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.businessType` (object, required)
    The business type of the business, this is the business type that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.businessType.id` (string)
    Business type id

  - `tokenUnits.token.metadata.included.ownerBusiness.businessType.name` (string, required)
    Business type name

  - `tokenUnits.token.metadata.included.ownerBusiness.businessType.description` (string, required)
    Business type description

  - `tokenUnits.token.metadata.included.ownerBusiness.businessType.code` (string, required)
    Business type code

  - `tokenUnits.token.metadata.included.ownerBusiness.businessType.iconUrl` (string, required)
    Business type icon url

  - `tokenUnits.token.metadata.included.ownerBusiness.isActive` (boolean, required)
    The status of the business, this is the status that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.approval` (object, required)
    Approval metadata for this business.

  - `tokenUnits.token.metadata.included.ownerBusiness.canMintToken` (boolean, required)
    The ability to mint token for the business, this is the ability that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.canChargeToken` (boolean, required)
    The ability to charge token for the business, this is the ability that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.canManageUsers` (boolean, required)
    The ability to manage users for the business, this is the ability that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.canReceiveDonation` (boolean, required)
    The ability to receive donation for the business, this is the ability that will be shown to the public.

  - `tokenUnits.token.metadata.included.ownerBusiness.tags` (array, required)
    Tags for categorization and filtering

  - `tokenUnits.token.metadata.included.ownerBusiness.customData` (object, required)
    Custom business data including Google Places information (placeID, currentOpeningHours)
    Example: {"placeID":"ChIJN1t_tDeuEmsRUsoyG83frY4","currentOpeningHours":{"open_now":true,"weekday_text":["Monday: 9:00 AM – 5:00 PM","Tuesday: 9:00 AM – 5:00 PM"]}}

  - `tokenUnits.token.metadata.included.token` (object)
    Parent token contract info (via ?include=token)

  - `tokenUnits.token.metadata.included.token.chainId` (number)
    Blockchain chain ID (mainnet or testnet)

  - `tokenUnits.token.metadata.included.token.contractAddress` (string)
    Smart contract address

  - `tokenUnits.token.metadata.included.token.type` (string)
    Token type (ERC721, ERC1155)

  - `tokenUnits.token.abi` (object, required)
    this is the abi of the contract, this is the interface of the contract to interact with it

  - `tokenUnits.token.chainId` (number, required)
    this is the chain id of the chain where the token is deployed

  - `tokenUnits.token.abiUrl` (string, required)
    this is the url of the abi of the contract, to be used to fetch the abi of the contract

  - `tokenUnits.token.name` (string)
    this is the name of the token contract

  - `tokenUnits.token.symbol` (string)
    this is the symbol of the token contract, this is optional and can be null

  - `tokenUnits.token.decimals` (number)
    this is the decimals of the token. This is optional and only used for ERC20 tokens (Points)

  - `tokenUnits.token.isActive` (boolean, required)
    This can be used to enable or disable the token

  - `tokenUnits.token.isGallery` (boolean, required)
    This can be used to enable or disable the token for gallery

  - `tokenUnits.token.type` (string, required)
    This is the underlying web3 native type of the token contract
    Enum: "ERC20", "ERC1155", "ERC721"

  - `tokenUnits.token.stampToken` (boolean, required)
    When true, this ERC1155 contract is used as a shared stamp contract. TokenMetadata entries are auto-provisioned per business and can be referenced in redemption priceTokenUnits with resolveByBusiness=true.

  - `tokenUnits.tokenMetadataIncrementalId` (number)
    Token metadata template ID. For ERC1155: becomes on-chain tokenId. For ERC721: lookup key for template to generate unique metadata.

  - `tokenUnits.amount` (number, required)
    Amount of tokens to mint/transfer. For ERC721 this is typically 1, for ERC1155/ERC20 can be any quantity.

  - `tokenUnits.maxAmount` (number)
    Token unit max amount. Applies to MINT/EARN contexts only (e.g. campaign reward capping where user earns tokens per revenue spent). Ignored for spend/transfer contexts such as redemption priceTokenUnits.

  - `businessEngagements` (array, required)

  - `businessEngagements.id` (string, required)
    id

  - `businessEngagements.shortDescription` (number, required)
    A short description of the business engagement with indications what to do etc

  - `businessEngagements.businessIds` (array, required)
    Business IDs associated with this engagement. Use CampaignDTO.included.businesses for full entities.

  - `businessEngagements.businesses` (array, required)
    Businesses (DEPRECATED: use businessIds + CampaignDTO.included.businesses instead. Will be removed in Q2 2026)
    Example: [{"id":"business-uuid-1"},{"id":"business-uuid-2"}]

  - `businessEngagements.campaignId` (string, required)
    Campaign id

  - `businessEngagements.maxPerBusiness` (number, required)
    max per business, the maximum number of times a user can engage with the buisness in the campaign

  - `businessEngagements.maxPerDay` (number, required)
    max per day, the maximum number of times a user can engage with the buisness in the campaign per day

  - `businessEngagements.externalUrl` (string, required)
    The external URL for the business engagement, e.g. a link to a website or app

  - `triggerSourceIds` (array, required)
    Trigger source IDs. Use to batch fetch or request via ?include=triggerSources
    Example: ["trigger-uuid-1","trigger-uuid-2"]

  - `included` (object)
    Included related data. Only populated when include parameter is specified.

  - `included.claimCount` (number)
    Total number of claims for this campaign (via ?include=claimCount)

  - `included.triggerSources` (array)
    Full trigger source entities (via ?include=triggerSources)

  - `included.triggerSources.type` (string, required)
    Type of trigger source - HOW to claim rewards.
**Available Types:**
- `QR_CODE`: Physical QR code scan. App opens camera, decodes QR, extracts triggerSourceId
- `NFC_TAG`: NFC tag tap. App activates NFC reader, reads tag data
- `GPS_GEOFENCE`: GPS-based geofence. App sends user coordinates for proximity validation
- `API_WEBHOOK`: Server-to-server webhook. External system triggers claim via API
- `TRANSACTION`: Purchase/transaction triggered. Claim activated by payment events

**Important:** This defines HOW claims are triggered (the touchpoint mechanism).
NOT to be confused with CampaignTriggerType which defines WHO can claim (CLAIM_BY_USER, CLAIM_BY_BUSINESS, CLAIM_BY_SYSTEM).
**App Integration:**

```typescript
const triggerTypes = campaign.included?.triggerSources?.map(ts => ts.type);
// Based on types, show appropriate UI (camera for QR, NFC prompt, location request, etc.)
```
    Enum: "QR_CODE", "NFC_TAG", "API_WEBHOOK", "GPS_GEOFENCE", "TRANSACTION"

  - `included.triggerSources.name` (string, required)
    Human-readable name for the trigger source
    Example: Main Entrance QR Code

  - `included.triggerSources.description` (number)
    Optional description explaining this trigger source
    Example: QR code located at the main entrance for visitor check-in

  - `included.triggerSources.context` (object)
    **ERC721 only** - Admin-controlled dynamic context for template interpolation and AI prompts. This data is ALWAYS applied (not subject to allowExternalContextOverwrite).
**Template Interpolation:** Values become available as `{{context.keyName}}` placeholders in TokenMetadata name/description fields and AI prompts.
**Special validity keys:**
- `validityDate` - Base date for trigger-based validity
- `validityEndDate` - End date for date ranges (e.g., hotel checkout)
- `validityDuration` - Override duration in days/hours

**Custom keys:** Any arbitrary key becomes `{{context.keyName}}` placeholder.
**Use cases:**
- QR at hotel room: `{ location: "Room 305", roomType: "Suite" }`
- NFC at event entrance: `{ eventName: "Summer Festival", zone: "VIP" }`
- Kiosk-specific: `{ deviceId: "kiosk-001", branch: "Downtown" }`
    Example: {"location":"Main Lobby","deviceId":"kiosk-001","validityDate":"2026-04-20T11:00:00Z"}

  - `included.triggerSources.maxUsage` (number)
    Maximum usage limit. null=unlimited, 1=single-use (receipt), 100=limited edition. Usage count calculated from claims via CQRS.
    Example: null

  - `included.triggerSources.businessId` (number)
    Reference to the business that owns this trigger source. Optional - can be tenant-wide trigger sources
    Example: business-uuid-123

  - `included.triggerSources.coordsLatitude` (number)
    Latitude. Geographic coordinates for location-based trigger validation.
**Universal Location Support:** ANY trigger type can use proximity validation (GPS_GEOFENCE, QR_CODE, NFC_TAG, API_WEBHOOK, TRANSACTION).
**Location Resolution Priority:**
1. TriggerSource coordinates (if set)
2. Business coordinates (if businessId exists)
3. Neither - No location validation

Distance constraints defined in CampaignTrigger.maxGeoDistanceInMeters. Both latitude and longitude must be provided together.
**Geocoding behavior:** Changing coords will auto-update address fields. To adjust pin position without changing address (e.g., parking entrance), set BOTH coordinates AND address fields in the same request.
    Example: 47.6062

  - `included.triggerSources.coordsLongitude` (number)
    Longitude. Geographic coordinates for location-based trigger validation.
**Universal Location Support:** ANY trigger type can use proximity validation (GPS_GEOFENCE, QR_CODE, NFC_TAG, API_WEBHOOK, TRANSACTION).
**Location Resolution Priority:**
1. TriggerSource coordinates (if set)
2. Business coordinates (if businessId exists)
3. Neither - No location validation

Distance constraints defined in CampaignTrigger.maxGeoDistanceInMeters. Both latitude and longitude must be provided together.
    Example: -122.3321

  - `included.triggerSources.streetAddress` (string)
    Street address (auto-populated from geocoding if coordinates provided)
    Example: 123 Main Street

  - `included.triggerSources.neighborhood` (string)
    Neighborhood/area name (e.g., "West Bay", "Pearl Qatar", "Lusail") - auto-populated from geocoding
    Example: West Bay

  - `included.triggerSources.district` (string)
    District/administrative area - auto-populated from geocoding
    Example: Doha Municipality

  - `included.triggerSources.city` (string)
    City. Auto-populated from geocoding if only coordinates provided. **Geocoding behavior:** Changing address fields will auto-update coordinates. To adjust pin without changing address, set BOTH coords AND address fields.
    Example: Doha

  - `included.triggerSources.postalCode` (string)
    Postal code (auto-populated from geocoding if coordinates provided)
    Example: 12345

  - `included.triggerSources.country` (string)
    Country (auto-populated from geocoding if coordinates provided)
    Example: Qatar

  - `included.triggerSources.countryCode` (string)
    ISO 3166-1 alpha-2 country code (auto-populated from geocoding)
    Example: QA

  - `included.triggerSources.id` (string, required)
    Unique identifier for the trigger source
    Example: source-12345

  - `included.triggerSources.isActive` (boolean, required)
    Whether this trigger source is currently active. Inactive sources won't trigger any flows
    Example: true

  - `included.triggerSources.isExhausted` (boolean, required)
    Whether this trigger source has been exhausted (agotado). Set via CQRS when claim count reaches maxUsage.

  - `included.triggerSources.createdAt` (object, required)
    Timestamp when the trigger source was created
    Example: 2024-01-01T12:00:00.000Z

  - `included.triggerSources.updatedAt` (object, required)
    Timestamp when the trigger source was last updated
    Example: 2024-01-10T12:00:00.000Z

  - `included.businesses` (array)
    Full business entities for all businessEngagements (via ?include=businesses)

