Odaduu · DMC QuoteOps · v1
Odaduu Public API
A small, stable HTTP surface for B2B partners to pull booking status, browse our reference catalogs, and submit new RFQs into the Odaduu sales pipeline. Bearer-authenticated, JSON in and JSON out, cursor-paginated where it matters.
https://builder.trawish.com/api/public/v1Overview
Every request is bearer-authenticated. Keys are minted per partner and (optionally) bound to a specific agent. An agent-scoped key can only see its own bookings and can only submit RFQs on that agent’s behalf. Reference catalogs are always org-wide.
Default rate limit is 1000 requests per rolling hour per key. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining headers.
Authentication
Send your key in the Authorization header:
Authorization: Bearer odaduu_live_ab12CDEfghIJKlmnOP34
We show the full secret once at mint time. If you lose it, revoke the key and mint a fresh one; we cannot recover the plaintext. The first 20 characters (the odaduu_live_ab12CDEf prefix) stay visible in our admin so we can identify a key without seeing the secret.
Response envelope
All successful responses wrap the payload:
{
"ok": true,
"data": { "id": "cl...", "ref": "OD-2026-4132", "status": "quote_sent", ... },
"meta": { "count": 10, "nextCursor": "eyJ0Ijoi..." }
}Errors follow the same shape:
{
"ok": false,
"error": { "code": "unauthorized", "message": "invalid API key" }
}List endpoints return an opaque meta.nextCursor. Pass it back on the next request to page forward; its value is stable across concurrent writes.
Endpoints
Health
/healthVerifies your key and echoes the tenant it resolves to. Hit this first after mint.
Request
curl -H 'Authorization: Bearer odaduu_live_...' \ https://builder.trawish.com/api/public/v1/health
Response
{
"ok": true,
"data": {
"status": "ok",
"orgId": "cmq11ccnc0000f9mjwylvqaxs",
"agentId": null,
"rateLimitPerHour": 1000,
"apiVersion": "v1",
"serverTime": "2026-09-03T09:20:00.000Z"
}
}Bookings
/bookingsList bookings visible to your key, newest first. Agent-scoped keys see only their own bookings.
| Param | Type | Notes |
|---|---|---|
| status | string, optional | draft_quote, quote_sent, client_confirmed, ops_in_progress, travel_completed, cancelled |
| limit | int, optional | Page size (default 25, max 100) |
| cursor | string, optional | Opaque token from a prior response's meta.nextCursor |
Request
curl -H 'Authorization: Bearer odaduu_live_...' \ https://builder.trawish.com/api/public/v1/bookings?limit=10&status=quote_sent
Response
{
"ok": true,
"data": [
{
"id": "cmtl0q9b20004j39gdw0wk6q7",
"ref": "OD-2026-7245",
"status": "draft_quote",
"guestName": null,
"arrivalDate": "2026-11-05",
"departureDate": "2026-11-14",
"pax": { "adults": 2, "children": 1, "infants": 0 },
"destination": { "code": "JP", "name": "Japan" },
"agentId": null,
"createdAt": "2026-09-02T06:54:08.327Z",
"updatedAt": "2026-09-02T06:54:08.327Z"
}
],
"meta": {
"count": 1,
"nextCursor": "eyJ0IjoiMjAyNi0wOS0wMlQwNjo1NDowOC4zMjdaIiwiaSI6ImNtdGpxcTdyZDA5OGF3ZnVjMG96YTU5ZW4ifQ"
}
}meta.nextCursor is null when the last page has been reached.
/bookings/{id}Full detail for one booking, including the currently active quote's total in your sell currency.
Request
curl -H 'Authorization: Bearer odaduu_live_...' \ https://builder.trawish.com/api/public/v1/bookings/cmtl0q9b20004j39gdw0wk6q7
Response
{
"ok": true,
"data": {
"id": "cmtl0q9b20004j39gdw0wk6q7",
"ref": "OD-2026-7245",
"status": "draft_quote",
"guestName": "Mr. Sharma",
"arrivalDate": "2026-11-05",
"departureDate": "2026-11-14",
"pax": { "adults": 2, "children": 1, "infants": 0 },
"destination": { "code": "JP", "name": "Japan" },
"agentId": null,
"focPax": 0,
"childAges": [8],
"leadSource": "email",
"agentRequirements": "Vegetarian meals",
"activeQuote": {
"version": 1,
"variant": "standard",
"label": null,
"totalSell": 0,
"sellCurrency": "INR",
"marginPercent": 0,
"validUntil": null,
"sentAt": null,
"sentChannel": null
},
"createdAt": "2026-09-02T06:54:08.327Z",
"updatedAt": "2026-09-02T06:54:08.327Z"
}
}/quotesSubmit an RFQ. Creates a DRAFT_QUOTE booking on our side; sales picks it up in the internal dashboard and builds the itinerary.
Request
curl -X POST -H 'Authorization: Bearer odaduu_live_...' \
-H 'Content-Type: application/json' \
-d '{
"destinationCode": "JP",
"guestName": "Mr. Sharma",
"arrivalDate": "2026-11-05",
"departureDate": "2026-11-14",
"pax": { "adults": 2, "children": 1, "infants": 0 },
"childAges": [8],
"leadSource": "EMAIL",
"agentRequirements": "Vegetarian meals"
}' \
https://builder.trawish.com/api/public/v1/quotesResponse
{
"ok": true,
"data": {
"id": "cmtl9a3fk00046i2aupcodp19",
"ref": "OD-2026-4761",
"status": "draft_quote",
"guestName": "Mr. Sharma",
"arrivalDate": "2026-11-05",
"departureDate": "2026-11-14",
"pax": { "adults": 2, "children": 1, "infants": 0 },
"destination": { "code": "JP", "name": "Japan" },
"agentId": null,
"focPax": 0,
"childAges": [8],
"leadSource": "email",
"agentRequirements": "Vegetarian meals",
"activeQuote": {
"version": 1,
"variant": "standard",
"label": null,
"totalSell": 0,
"sellCurrency": "INR",
"marginPercent": 0,
"validUntil": null,
"sentAt": null,
"sentChannel": null
},
"createdAt": "2026-09-03T08:20:00.000Z",
"updatedAt": "2026-09-03T08:20:00.000Z"
}
}Returns the created BookingDetail. activeQuote is present but empty (days[] and hotels[] are []) until sales fills it in.
Reference catalogs
/destinationsDestinations available for RFQ submission.
Request
curl -H 'Authorization: Bearer odaduu_live_...' \ https://builder.trawish.com/api/public/v1/destinations
Response
{
"ok": true,
"data": [
{
"id": "cmq11cczg0002f9mjcvsmp9gf",
"code": "JP",
"name": "Japan",
"currency": "JPY"
}
],
"meta": { "count": 1 }
}/hotelsHotel catalog. Filter by city or free-text name match.
| Param | Type | Notes |
|---|---|---|
| city | string, optional | Case-insensitive equality |
| q | string, optional | Substring match on name |
| limit | int, optional | Default 100, max 200 |
Request
curl -H 'Authorization: Bearer odaduu_live_...' \ https://builder.trawish.com/api/public/v1/hotels?city=Tokyo&limit=10
Response
{
"ok": true,
"data": [
{
"id": "hot_abc123",
"name": "Park Hyatt Tokyo",
"city": "Tokyo",
"country": "Japan",
"address": "3-7-1-2 Nishi Shinjuku"
}
],
"meta": { "count": 1 }
}/sightseeingsPaid activities and free stops.
| Param | Type | Notes |
|---|---|---|
| city | string, optional | Matches against the catalog's cities[] array |
| category | string, optional | PAID or FREE |
| kind | string, optional | SIGHTSEEING, ACTIVITY, SHOPPING, WALKING |
| q | string, optional | Substring match on name |
Request
curl -H 'Authorization: Bearer odaduu_live_...' \ https://builder.trawish.com/api/public/v1/sightseeings?city=Kyoto&category=PAID
Response
{
"ok": true,
"data": [
{
"id": "sight_xyz789",
"name": "Fushimi Inari Shrine Tour",
"category": "PAID",
"kind": "SIGHTSEEING",
"cities": ["Kyoto"],
"description": "Guided walking tour of the iconic torii gates.",
"adultPrice": 4500,
"childPrice": 2500,
"childAgeFrom": 4,
"childAgeTo": 11,
"durationMinutes": 120,
"openingHours": { "from": "08:00", "to": "17:00" },
"closedDaysOfWeek": [],
"timedEntry": false,
"mustSee": true
}
],
"meta": { "count": 1 }
}/fullday-toursPrivate full-day tour catalog with per-seater JPY pricing.
| Param | Type | Notes |
|---|---|---|
| city | string, optional | Matches against the catalog's cities[] array |
| q | string, optional | Substring match on name |
Request
curl -H 'Authorization: Bearer odaduu_live_...' \ https://builder.trawish.com/api/public/v1/fullday-tours?city=Hiroshima
Response
{
"ok": true,
"data": [
{
"id": "mig_2026_08_14_ft_hiroshima_local_751cfe93b7e9",
"name": "Hiroshima day tour with local guide",
"cities": ["Hiroshima"],
"description": "Peace Memorial, Miyajima ferry, local lunch included.",
"pricePerSeater": {
"seater6": 85000,
"seater9": 110000,
"seater13": 140000,
"seater22": 210000,
"seater29": null,
"seater45": null
}
}
],
"meta": { "count": 1 }
}Error codes
Codes are stable across versions. Messages may drift; match on codes.
| Code | HTTP | When it fires |
|---|---|---|
| unauthorized | 401 | Missing, malformed, unknown, or expired key. |
| forbidden | 403 | Key is valid but not permitted for this resource. |
| not_found | 404 | Booking (or similar) does not exist or is out of scope for your key. |
| bad_request | 400 | Payload validation failed. `error.details` carries specifics. |
| rate_limited | 429 | Hourly quota exceeded. `error.details.retryAfterSec` tells you when to retry. |
| conflict | 409 | Reserved for write endpoints; not raised by v1 today. |
| internal_error | 500 | Server-side fault. Please report with `error.details.hint`. |
| not_implemented | 501 | Public API is disabled on this deployment. |
Troubleshooting 401
Almost every failed integration lands here first. Work down the list; nine times out of ten it is one of the top three.
- Prefix mismatch. Your key must start with
odaduu_live_. Anything else returns"malformed API key"before we even look it up. - Missing scheme. The header value must be
Bearer <key>with the literal wordBearerand one space. NoToken, noBasic. - Wrong host. The base URL is exactly
https://builder.trawish.com/api/public/v1. Any other host will 404 or 401 depending on how your DNS resolves. - Key revoked or expired.
"invalid API key"means the shape passed but the DB lookup failed. Ask your account manager to checkSettings, API keysfor the row. - Copy-paste artifact. A trailing newline, a zero-width space, or a curly quote around the key sneaks through Notion and Slack. Retype it, or paste through
echo -n "$KEY" | wc -cto check the length: a valid key is 39 characters.
Still stuck? Send your keyPrefix (never the full secret) plus the timestamp of a failing request to [email protected] and we can look up the specific 401 in our logs.
Getting started
- Ask your Odaduu account manager at [email protected] to mint you a key. They will send you the full secret once; store it in your secret manager immediately.
- Run the
/healthcall in the Endpoints section to confirm auth is wired up and your key resolves to the right tenant. - Poll
/bookingsto pull your live pipeline, or hit/quotesto open new RFQs directly from your system. - Watch
X-RateLimit-Remainingon every response and back off when it approaches zero. A429comes withretryAfterSecin the error details.