1

Obtain your sandbox credentials

Contact your Vendora integration manager to receive your sandbox API key, Store Code and POS Code. These are pre-configured in this portal.

ℹ️ Never share API keys in code, URLs, screenshots or support tickets. If you suspect a key has been compromised, rotate it immediately and notify your integration manager.
2

Validate your credentials

Call the auth/validate endpoint to confirm your key is active and inspect the assigned vendor, store, POS and scopes.

POST /api/v1/pos/auth/validate
Headers:
  X-Vendora-Api-Key:    <your key>
  X-Vendora-Store-Code: <your store code>
  X-Vendora-POS-Code:   <your POS code>
  X-Request-ID:         <uuid per request>

# Response envelope:
{
  "status": 200,
  "code":   "SUCCESS",
  "response": {
    "api_key": { "name": "...", "abilities": [...] },
    "vendor":  { "name": "..." },
    "store":   { "code": "...", "name": "..." },
    "pos":     { "code": "...", "name": "..." }
  }
}
3

Retrieve available products

Fetch the list of active draw products. Each product includes price, quantity, draw date and optional promotion (coupon) details.

GET /api/v1/pos/products
Headers: (same as above)

# Key response fields (in envelope.response.products[]):
{
  "product_id": 251,
  "product_name": "Lulu Grand Draw",
  "price": 35.0,
  "minimal_quantity": 1,
  "maximal_quantity": 10,
  "available_quantity": 1250,
  "draw_date": "2025-01-15T20:00:00Z",
  "coupon": { "id": 1, "buy_x": 2, "get_y": 1 }  // nullable
}
ℹ️ An empty products array is a valid response — it means no draws are currently active.
4

Check available tickets (optional)

For ticket-selection mode, fetch available ticket IDs for a specific product. For quantity mode, skip this step.

GET /api/v1/pos/products/{product_id}/available-tickets
# Returns: envelope.response.available_ticket_ids (array of ticket ID strings)
5

Create a ticket hold

Reserve tickets before completing the sale. The hold locks tickets for a limited time window. Use the Idempotency-Key header to safely retry failed requests.

POST /api/v1/pos/ticket-holds
Headers:
  Idempotency-Key: <stable-uuid-per-cart-item>

Body (quantity mode):
{
  "product_id": 251,
  "quantity": 2
}

Body (ticket-selection mode):
{
  "product_id": 251,
  "ticket_ids": ["T-001", "T-002"]
}

# Promotional hold also requires:
{
  "vendor_customer_id": "CUST-abc123",
  "coupon_id": 1
}

# Key response fields:
{
  "hold_reference": "HOLD-abc",
  "expires_at": "2025-01-15T14:35:00Z",
  "total_payable": 70.00,
  "tickets": [
    { "ticket_id": "T-001", "ticket_type": "PAID",   "amount": 35.00 },
    { "ticket_id": "T-002", "ticket_type": "FREE",   "amount": 0.00  }
  ]
}
⚠️ A hold is NOT a sale. Tickets are not guaranteed until a successful sale. If a conflict occurs (409), refresh availability and re-select.
6

Upsert the customer

Create or update the customer record in Vendora. The vendor_customer_id returned must be included in the sale request.

POST /api/v1/pos/customers/upsert
Body:
{
  "vendor_customer_id": "CUST-<your-stable-id>",
  "first_name": "Ahmed",
  "last_name": "Al-Rashid",
  "email": "...",
  "mobile": "501234567",
  "mobile_country_id": 217,
  "nationality": "AE",
  "date_of_birth": "1990-01-15",
  "document_type": "PASSPORT",
  "document_number": "A12345678",
  "document_expiry_date": "2028-06-30",
  "document_country_id": 217,
  "address_line_1": "...",
  "country_id": 217
}
⚠️ Document numbers (passport, Emirates ID) are KYC data. Never store them in logs, analytics, localStorage or any persistent store beyond the in-flight request.
7

Submit the sale

Submit the completed sale with all hold references, KYC data and payment details. Use the same Idempotency-Key as the hold to safely retry.

POST /api/v1/pos/sales
Headers:
  Idempotency-Key: <same key as hold>

Body:
{
  "vendor_customer_id": "CUST-abc123",
  "holds": [{ "hold_reference": "HOLD-abc" }],
  "customer": { /* same as upsert body */ },
  "payment": {
    "method": "CASH",
    "amount": 70.00,
    "currency": "AED",
    "result": "PAID",
    "collected_by": "VENDOR"
  },
  "recipient_email": "..."
}
8

Retrieve the issued order

After a successful sale, retrieve the transaction to confirm ticket issuance and e-ticket delivery status.

GET /api/v1/pos/transactions/{order_reference}
# Key response fields:
{
  "order_reference": "ORD-abc",
  "payment_status": "PAID",
  "ticket_status": "ISSUED",
  "e_ticket": { "status": "QUEUED" | "SENT" | "FAILED" }
}
9

Download the e-ticket PDF (when available)

The e-ticket PDF is proxied through the Nitro server. It returns application/pdf binary data directly.

GET /api/storefront/orders/{order_reference}/e-ticket
# Returns: application/pdf binary
# Headers: Content-Disposition: attachment; filename="Vendora-e-ticket-{ref}.pdf"
# Cache-Control: no-store
ℹ️ The PDF is not cached and requires a fresh authenticated request each time. The Vendora API key never appears in this URL.

Vendora Response Envelope

Every Vendora API response wraps business data in a consistent envelope. Business data is always in response, never in data.

{
  "status":        200,
  "code":          "SUCCESS",       // endpoint-specific, may differ per operation
  "message":       "...",
  "tracing_id":    "trace-abc123",  // preserve for support requests
  "version":       "1.0",
  "errors":        null,            // array of error objects when status != 2xx
  "response":      { ... },         // ← business data is always here, not "data"
  "encrypted_data": null
}

Required Headers

HeaderSourceRequired
X-Vendora-Api-KeyPrivate runtime config (NUXT_VENDORA_API_KEY)Required
X-Vendora-Store-CodePrivate runtime config (NUXT_VENDORA_STORE_CODE)Required
X-Vendora-POS-CodePrivate runtime config (NUXT_VENDORA_POS_CODE)Required
X-Request-IDGenerated per request (UUID v4)Required
Idempotency-KeyGenerated per logical action, stored in sessionOptional
Content-Typeapplication/json for POST bodiesOptional