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
curl "$BASE/api/v1/products?hub=LOS&category=history" \
-H "Authorization: Bearer tm_test_…"curl "$BASE/api/v1/products/12/availability?from=2026-12-10&to=2026-12-20" \
-H "Authorization: Bearer tm_test_…"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}}'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
- Search products, then read the tour for its guest types, add-ons and rules.
- Availability lists each start time with
remainingseats and whether it isbookable. - Quote gives the exact total (guest types, add-ons, peak-day price changes). It is free to call.
- Book holds the seats and returns
checkoutUrl. Two people can never take the last seat: the second gets409 unavailable. - 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 | /destinations | Places we run tours, with how many tours are on sale. |
| GET | /products | Search 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}/availability | Start times with seats left (from, to; up to 93 days). |
| POST | /quotes | The exact price for a slot and group. Books nothing. |
| POST | /bookings | Hold the seats and get a checkoutUrl. Supports Idempotency-Key. |
| GET | /bookings | Your bookings (status, email, page, limit). |
| GET | /bookings/{reference} | One booking. |
| POST | /bookings/{reference}/cancel | Cancel; refund follows the tour’s policy. |
| GET | /me | Who 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.
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.