Home › Docs › Promotions API

Promotions API

Endpoints for managing promotional banners and deals. Banners display in the hub's customer-facing interface. Deals apply automatic discounts at checkout.

Banners

GET /owner/banners

List promotional banners configured for a hub.

Version1
URLhttps://tina-api.venhub.com/api/v1/owners/owner/banners
AuthAPI Key or Firebase
Required Scopepromotions:read

Example Request

cURL
curl -X GET \
  "https://tina-api.venhub.com/api/v1/owners/owner/banners?hub_id=123" \
  --header "X-API-Key: vh_abc123..."

Parameters

ParameterTypeLocationRequiredDescription
hub_idIntegerQueryRequiredHub ID to retrieve banners for

Response

JSON — 200 OK
[
  {
    "id": 1,
    "name": "Summer Sale",
    "image_url": "https://storage.example.com/banners/summer.jpg",
    "order_by": 1
  },
  {
    "id": 2,
    "name": "New Products",
    "image_url": "https://storage.example.com/banners/new.jpg",
    "order_by": 2
  }
]
POST /owner/banners

Create a new promotional banner with image upload.

Version1
URLhttps://tina-api.venhub.com/api/v1/owners/owner/banners
AuthAPI Key or Firebase
Required Scopepromotions:write
ℹ️
This endpoint uses multipart/form-data for file upload. Include the hub_id as a query parameter.

Example Request

cURL
curl -X POST \
  "https://tina-api.venhub.com/api/v1/owners/owner/banners?hub_id=123" \
  --header "X-API-Key: vh_abc123..." \
  --form 'name=Summer Sale' \
  --form 'order_by=1' \
  --form '[email protected]'

Parameters

ParameterTypeLocationRequiredDescription
hub_idIntegerQueryRequiredHub ID to create the banner for
nameStringFormRequiredBanner display name
order_byIntegerFormRequiredDisplay order position
fileFileFormRequiredBanner image file

Response

JSON — 200 OK
{
  "id": 3,
  "name": "Summer Sale",
  "image_url": "https://storage.example.com/banners/abc123.jpg",
  "order_by": 1
}
DELETE /owner/banners/{banner_id}

Delete a promotional banner from the hub.

Version1
URLhttps://tina-api.venhub.com/api/v1/owners/owner/banners/{banner_id}
AuthAPI Key or Firebase
Required Scopepromotions:write

Example Request

cURL
curl -X DELETE \
  "https://tina-api.venhub.com/api/v1/owners/owner/banners/3?hub_id=123" \
  --header "X-API-Key: vh_abc123..."

Parameters

ParameterTypeLocationRequiredDescription
banner_idIntegerPathRequiredID of the banner to delete
hub_idIntegerQueryRequiredHub ID the banner belongs to

Response

ℹ️
200 OK with an empty body. This endpoint doesn't return {success: true} or any other JSON — check the status code to confirm the delete succeeded.

Deals

GET /deals/active/{hub_id}

Get currently active deals for a hub.

Version1
URLhttps://tina-api.venhub.com/api/v1/deals/active/{hub_id}
AuthAPI Key or Firebase
Required Scopepromotions:read

Example Request

cURL
curl -X GET \
  "https://tina-api.venhub.com/api/v1/deals/active/123" \
  --header "X-API-Key: vh_abc123..."

Parameters

ParameterTypeLocationRequiredDescription
hub_idIntegerPathRequiredHub ID to retrieve active deals for

Response

JSON — 200 OK
[
  {
    "id": 1,
    "name": "Buy 2 Get 1 Free",
    "description": "Buy any 2 snacks, get the cheapest one free",
    "display_text": "Buy any 2 snacks, get 1 free!",
    "display_image": "https://storage.example.com/deals/bogo.jpg",
    "priority": 1
  }
]
GET /deals/{deal_id}

Get detailed information about a specific deal.

Version1
URLhttps://tina-api.venhub.com/api/v1/deals/{deal_id}
AuthAPI Key or Firebase
Required Scopepromotions:read

Example Request

cURL
curl -X GET \
  "https://tina-api.venhub.com/api/v1/deals/1" \
  --header "X-API-Key: vh_abc123..."

Parameters

ParameterTypeLocationRequiredDescription
deal_idIntegerPathRequiredID of the deal to retrieve

Response

JSON — 200 OK
{
  "id": 1,
  "name": "Buy 2 Get 1 Free",
  "description": "Buy any 2 snacks, get the cheapest one free",
  "is_deal_active": true,
  "start": "2026-02-01",
  "end": "2026-02-28",
  "triggers": [
    { "type": "QUANTITY", "quantity": 2, "category": "Snacks", "category_id": 14, "product_id": null }
  ],
  "rewards": [
    { "type": "FREE_ITEM", "quantity": 1, "product_id": 456, "discount_pct_or_price": null }
  ],
  "constraints": [
    { "type": "SALEAMT", "value": 100 }
  ]
}
GET /deals/hub/{hub_id}

Get all deals (active and inactive) for a hub.

Version1
URLhttps://tina-api.venhub.com/api/v1/deals/hub/{hub_id}
AuthAPI Key or Firebase
Required Scopepromotions:read

Example Request

cURL
curl -X GET \
  "https://tina-api.venhub.com/api/v1/deals/hub/123" \
  --header "X-API-Key: vh_abc123..."

Parameters

ParameterTypeLocationRequiredDescription
hub_idIntegerPathRequiredHub ID to retrieve all deals for

Response

JSON — 200 OK
[
  {
    "id": 1,
    "name": "Buy 2 Get 1 Free",
    "description": "Buy any 2 snacks, get the cheapest one free",
    "is_deal_active": true,
    "start": "2026-02-01",
    "end": "2026-02-28",
    "triggers": [
      { "type": "QUANTITY", "quantity": 2, "category": "Snacks", "category_id": 14, "product_id": null }
    ],
    "rewards": [
      { "type": "FREE_ITEM", "quantity": 1, "product_id": 456, "discount_pct_or_price": null }
    ],
    "constraints": [
      { "type": "SALEAMT", "value": 100 }
    ]
  }
]
POST /deals/create-from-owner

Create a new promotional deal with banner image upload.

Version1
URLhttps://tina-api.venhub.com/api/v1/deals/create-from-owner
AuthAPI Key or Firebase
Required Scopepromotions:write
ℹ️
Deal configuration is passed as a JSON-encoded string in the dealData field.

Example Request

cURL
curl -X POST \
  "https://tina-api.venhub.com/api/v1/deals/create-from-owner" \
  --header "X-API-Key: vh_abc123..." \
  --form 'dealData={"deal":{"name":"Summer Sale","hub_id":123},"trigger":{"type":"QUANTITY"},"reward":{"type":"PERCENT_OFF"}}' \
  --form '[email protected]'

Parameters

ParameterTypeLocationRequiredDescription
dealDataJSON StringFormRequiredJSON-encoded deal configuration
fileFileFormRequired*Banner image. *Required on vencart-service, optional on vh-tina-service

dealData Fields

FieldTypeRequiredDescription
deal.nameStringRequiredDisplay name for the deal
deal.hub_idIntegerRequiredHub to associate the deal with
deal.startDateOptionalDeal start date (YYYY-MM-DD)
deal.endDateOptionalDeal end date (YYYY-MM-DD)
triggerObjectRequiredTrigger configuration
rewardObjectRequiredReward configuration

Response

JSON — 200 OK
{
  "id": 3,
  "name": "Summer Sale",
  "is_deal_active": true,
  "display_image": "https://storage.example.com/deals/abc123.jpg"
}
PUT /deals/{deal_id}

Update an existing deal's configuration.

Version1
URLhttps://tina-api.venhub.com/api/v1/deals/{deal_id}
AuthAPI Key or Firebase
Required Scopepromotions:write

Example Request

cURL
curl -X PUT \
  "https://tina-api.venhub.com/api/v1/deals/1" \
  --header "X-API-Key: vh_abc123..." \
  --header "Content-Type: application/json" \
  --data '{"name": "Updated Deal Name", "is_deal_active": false}'

Parameters

ParameterTypeLocationRequiredDescription
deal_idIntegerPathRequiredID of the deal to update
nameStringBodyOptionalUpdated display name for the deal
is_deal_activeBooleanBodyOptionalEnable or disable the deal
startDateBodyOptionalUpdated start date (YYYY-MM-DD)
endDateBodyOptionalUpdated end date (YYYY-MM-DD)

Response

JSON — 200 OK
{
  "id": 1,
  "name": "Updated Deal Name",
  "description": "Buy any 2 snacks, get the cheapest one free",
  "is_deal_active": false,
  "start": "2026-02-01",
  "end": "2026-02-28",
  "triggers": [
    { "type": "QUANTITY", "quantity": 2, "category": "Snacks", "category_id": 14, "product_id": null }
  ],
  "rewards": [
    { "type": "FREE_ITEM", "quantity": 1, "product_id": 456, "discount_pct_or_price": null }
  ],
  "constraints": [
    { "type": "SALEAMT", "value": 100 }
  ]
}
DELETE /deals/{deal_id}

Delete a deal and all associated triggers and rewards.

Version1
URLhttps://tina-api.venhub.com/api/v1/deals/{deal_id}
AuthAPI Key or Firebase
Required Scopepromotions:write

Example Request

cURL
curl -X DELETE \
  "https://tina-api.venhub.com/api/v1/deals/1" \
  --header "X-API-Key: vh_abc123..."

Parameters

ParameterTypeLocationRequiredDescription
deal_idIntegerPathRequiredID of the deal to delete

Response

JSON — 200 OK
{
  "success": true,
  "message": "Deal 1 deleted successfully"
}

Errors

Error CodeDescription
deal_not_foundThe specified deal does not exist

Additional Deal Routes

POST /deals/

Create a deal directly with a JSON body, as an alternative to /deals/create-from-owner's multipart upload — use this when there's no banner image to attach.

URLhttps://tina-api.venhub.com/api/v1/deals/
AuthAPI Key or Firebase
Required Scopepromotions:write

Example Request

cURL
curl -X POST \
  "https://tina-api.venhub.com/api/v1/deals/" \
  --header "X-API-Key: vh_abc123..." \
  --header "Content-Type: application/json" \
  --data '{"name":"Buy 2 Get 1 Free","hub_id":123,"description":"Buy any 2 snacks, get the cheapest one free","start":"2026-02-01","end":"2026-02-28","triggers":[{"type":"QUANTITY","quantity":2,"category":"Snacks","category_id":14}],"rewards":[{"type":"FREE_ITEM","quantity":1,"product_id":456}],"constraints":[{"type":"SALEAMT","value":100}]}'

Body

FieldTypeRequiredDescription
nameStringRequiredDisplay name for the deal
hub_idIntegerRequiredHub to associate the deal with
descriptionStringOptionalInternal description
startDateOptionalDeal start date (YYYY-MM-DD)
endDateOptionalDeal end date (YYYY-MM-DD)
triggersArrayRequiredTrigger definitions — same fields as GET /deals/{deal_id}'s triggers, minus id (not yet assigned)
rewardsArrayRequiredReward definitions — same shape as above, minus id
constraintsArrayOptionalConstraint definitions — same shape as above, minus id

Response

JSON — 200 OK
{
  "id": 4,
  "name": "Buy 2 Get 1 Free",
  "description": "Buy any 2 snacks, get the cheapest one free",
  "is_deal_active": true,
  "start": "2026-02-01",
  "end": "2026-02-28",
  "triggers": [
    { "id": 10, "type": "QUANTITY", "quantity": 2, "category": "Snacks", "category_id": 14 }
  ],
  "rewards": [
    { "id": 11, "type": "FREE_ITEM", "quantity": 1, "product_id": 456 }
  ],
  "constraints": [
    { "id": 12, "type": "SALEAMT", "value": 100 }
  ]
}
POST /deals/check-eligibility

Check which active deals a cart qualifies for, before applying any reward.

URLhttps://tina-api.venhub.com/api/v1/deals/check-eligibility
AuthAPI Key or Firebase
Required Scopepromotions:read

Example Request

cURL
curl -X POST \
  "https://tina-api.venhub.com/api/v1/deals/check-eligibility" \
  --header "X-API-Key: vh_abc123..." \
  --header "Content-Type: application/json" \
  --data '{"hub_id":123,"cart_items":[{"product_id":456,"category_id":14,"quantity":2,"unit_price":299}],"cart_total":598}'

Body

FieldTypeRequiredDescription
hub_idIntegerRequiredHub the cart belongs to
user_idIntegerOptionalCustomer placing the order
cart_itemsArrayRequiredEach item: product_id, category_id (optional), subcategory_id (optional), quantity, unit_price
cart_totalIntegerRequiredCart total in cents
user_order_countIntegerOptionalCustomer's prior order count, for first-time-buyer style deals
device_uuidStringOptionalDevice identifier

Response

JSON — 200 OK
{
  "eligible_deals": [
    {
      "deal_id": 1,
      "deal_name": "Buy 2 Get 1 Free",
      "description": "Buy any 2 snacks, get the cheapest one free",
      "display_text": "Buy any 2 snacks, get 1 free!",
      "reward_description": "1 free item",
      "potential_savings": 299,
      "start": "2026-02-01",
      "end": "2026-02-28",
      "stackable": false
    }
  ]
}
ℹ️
description and display_text are optional and may be absent. This is a preview only — nothing is applied to the cart until you call apply-reward.
POST /deals/apply-reward

Apply one or more deals' rewards to a cart, after check-eligibility has confirmed they qualify.

URLhttps://tina-api.venhub.com/api/v1/deals/apply-reward
AuthAPI Key or Firebase
Required Scopepromotions:write

Example Request

cURL
curl -X POST \
  "https://tina-api.venhub.com/api/v1/deals/apply-reward" \
  --header "X-API-Key: vh_abc123..." \
  --header "Content-Type: application/json" \
  --data '{"deal_ids":[1],"hub_id":123,"cart_items":[{"product_id":456,"category_id":14,"quantity":2,"unit_price":299}]}'

Body

FieldTypeRequiredDescription
deal_idsArray<Integer>RequiredDeals to apply
hub_idIntegerRequiredHub the cart belongs to
user_idIntegerOptionalCustomer placing the order
cart_itemsArrayRequiredSame shape as check-eligibility's cart_items
device_uuidStringOptionalDevice identifier

Response

JSON — 200 OK
{
  "success": true,
  "deal_applied": true,
  "modified_cart_items": [
    {
      "product_id": 456,
      "category_id": 14,
      "quantity": 2,
      "original_unit_price": 299,
      "discounted_unit_price": 0,
      "discount_applied": 299,
      "quantity_discounted": 1
    }
  ],
  "original_cart_total": 598,
  "discounted_cart_total": 299,
  "total_savings": 299,
  "message": "Deal applied successfully"
}
GET /deals/{deal_id}/redemption-status

Check redemption status/history for a specific deal.

URLhttps://tina-api.venhub.com/api/v1/deals/{deal_id}/redemption-status
AuthAPI Key or Firebase
Required Scopepromotions:read
GET /deals/promo-products/{hub_id}

List product IDs currently attached to any active deal for a hub (union of every reward and trigger product ID across the hub's active deals).

URLhttps://tina-api.venhub.com/api/v1/deals/promo-products/{hub_id}
AuthAPI Key or Firebase
Required Scopepromotions:read

Example Request

cURL
curl -X GET \
  "https://tina-api.venhub.com/api/v1/deals/promo-products/123" \
  --header "X-API-Key: vh_abc123..."

Response

JSON — 200 OK
[456, 789]