waca tour

Waca Tour API

Search and book tours across Nigeria. Everything is JSON over HTTPS under /api/v1. Create a partner account to get a sandbox key in a minute.

Quickstart

1. Find a tour
curl "$BASE/api/v1/products?hub=LOS&category=history" \
  -H "Authorization: Bearer tm_test_…"
2. See when it runs
curl "$BASE/api/v1/products/12/availability?from=2026-12-10&to=2026-12-20" \
  -H "Authorization: Bearer tm_test_…"
3. Price it
curl -X POST $BASE/api/v1/quotes -H "Authorization: Bearer tm_test_…" \
  -H "Content-Type: application/json" \
  -d '{"productId":12,"date":"2026-12-12","time":"10:00","pax":{"adult":2,"child":1}}'
4. Book it
curl -X POST $BASE/api/v1/bookings -H "Authorization: Bearer tm_test_…" \
  -H "Content-Type: application/json" -H "Idempotency-Key: cart-8841" \
  -d '{"productId":12,"date":"2026-12-12","time":"10:00","pax":{"adult":2},
       "customer":{"name":"Ada Obi","email":"ada@example.com","phone":"+2348030000000"},
       "returnUrl":"https://yoursite.example/thanks"}'
# → 201 { "data": { "reference": "TB-7KQ2M9XA", "status": "pending_payment", "checkoutUrl": "https://…" } }

Send the guest to checkoutUrl. When they have paid, the booking becomes confirmed and you get a webhook. Seats are held for about 20 minutes while they pay.

Authentication

Send your key in every request: Authorization: Bearer tm_live_…. Keys starting tm_test_ are the sandbox. Keep keys on your server, never in a browser or app. A key is shown once; revoke and replace it from your dashboard. Each key may make 120 requests a minute; every response says how many you have left (RateLimit-Remaining).

The booking flow

  1. Search products, then read the tour for its guest types, add-ons and rules.
  2. Availability lists each start time with remaining seats and whether it is bookable.
  3. Quote gives the exact total (guest types, add-ons, peak-day price changes). It is free to call.
  4. Book holds the seats and returns checkoutUrl. Two people can never take the last seat: the second gets 409 unavailable.
  5. Pay: the guest pays on the checkout page. Confirmation arrives as a webhook (or poll GET /bookings/{reference}).

Statuses: pending_payment → confirmed → completed (after the tour), or cancelled / expired / no_show.

Endpoints

GET/destinationsPlaces we run tours, with how many tours are on sale.
GET/productsSearch tours: q, destination, hub (ABV|LOS), category, date, guests, minPrice, maxPrice, sort, page, limit.
GET/products/{id|slug}One tour: prices, extras, rules, photos, your commission.
GET/products/{id}/availabilityStart times with seats left (from, to; up to 93 days).
POST/quotesThe exact price for a slot and group. Books nothing.
POST/bookingsHold the seats and get a checkoutUrl. Supports Idempotency-Key.
GET/bookingsYour bookings (status, email, page, limit).
GET/bookings/{reference}One booking.
POST/bookings/{reference}/cancelCancel; refund follows the tour’s policy.
GET/meWho this key is and its mode.

Money and dates

All money is whole kobo (1 naira = 100 kobo) as integers: 2500000 is ₦25,000. Dates are YYYY-MM-DD and times HH:MM in 24-hour Nigerian time (WAT). Your commission is on every booking you read.

Errors

HTTP 409
{ "error": { "code": "unavailable", "message": "Only 2 seats left at that time.", "requestId": "req_9f2c…" } }

Codes: invalid (400), unauthorized (401), payment (402), forbidden (403), not_found (404), unavailable / conflict (409), rate_limited (429). Quote the requestId when you contact support.

Webhooks

Set an address in your dashboard. We POST booking.created, booking.confirmed, booking.cancelled, booking.completed and booking.no_show. Verify the X-Tamuly-Signature: t=…,v1=… header: v1 is the hex HMAC-SHA256 of `${t}.${body}`. Addresses must be public https.

Node.js
import { createHmac, timingSafeEqual } from 'node:crypto'
function valid(header, rawBody, secret) {
  const [t, v1] = header.split(',').map((p) => p.split('=')[1])
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  return timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}

Sandbox

A tm_test_ key uses pretend payments (open the checkoutUrl to “pay”), never takes a real seat and never touches real money. Switch to a tm_live_ key when we have approved your account.