Quick start
Approved partners receive one production API key with explicit scopes. Experience integrations create bookings and redirect customers to a hosted checkout URL, using signed lifecycle webhooks.
- 1Store your API key only on your server.
- 2Fetch rituals or packages with the appropriate read scope.
- 3Create a package quote and use it before it expires.
- 4Create a booking once and redirect to the returned hosted checkout.
- 5Verify webhook signatures and reconcile with the relevant status endpoint.
Authentication
Send your production credential in the X-API-Key header. Never expose it in browser or mobile code. Keys are revocable and limited by scope and requests per minute.
curl 'https://api.tirth.com/v1/partner/packages?limit=20' \
-H 'X-API-Key: tirth_pk_<your-key>'const response = await fetch('https://api.tirth.com/v1/partner/packages?limit=20', {
headers: { 'X-API-Key': process.env.TIRTH_API_KEY }
});
const packages = await response.json();import requests
response = requests.get(
"https://api.tirth.com/v1/partner/packages",
params={"limit": 20},
headers={"X-API-Key": TIRTH_API_KEY},
)
packages = response.json()Responses include RateLimit-Limit and RateLimit-Remaining. A 429 response also includes Retry-After.
Ritual and package catalogs
/ritualsList active personal and upcoming Samuhik rituals.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| type | query | string (personal | samuhik) | Filter to one ritual type. |
| search | query | string | Free-text search across title and location. |
| city | query | string | Filter by city. |
| state | query | string | Filter by state. |
| destination | query | string | Filter by destination. |
| updated_since | query | ISO date | Return only rituals updated at or after this timestamp. |
| limit | query | integer (1–100) | Page size. Defaults to 20. |
| offset | query | integer | Pagination offset. |
Example response
{
"count": 20,
"total": 143,
"has_next": true,
"results": [
{
"id": "rit_9f2c",
"type": "personal",
"slug": "rudrabhishek",
"title": "Rudrabhishek Puja",
"overview": "...",
"importance": "...",
"location": { "name": "Kashi Vishwanath", "city": "Varanasi", "state": "Uttar Pradesh", "district": "Varanasi" },
"images": ["https://..."],
"benefits": ["..."],
"process": ["..."],
"faqs": [{ "question": "...", "answer": "..." }],
"inclusions": ["..."],
"add_ons": [{ "id": "addon_1", "title": "Extra Sankalp", "description": "...", "price": 501, "currency": "INR" }],
"solutions": ["..."],
"livestream_available": true,
"updated_at": "2026-08-30T10:00:00.000Z",
"pricing": { "currency": "INR", "retail_price": 2100, "max_people": 4 }
}
]
}- ·Catalog responses support ETag/If-None-Match — send it during sync and treat a 304 as unchanged.
- ·pricing shape differs by type: personal → {currency, retail_price, max_people}; samuhik → {currency, scheduled_for, plans[], pay_as_you_wish}.
/rituals/{type}/{slug}Read plans, pricing, benefits, inclusions, add-ons, and livestream availability for one ritual.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| type* | path | string (personal | samuhik) | Ritual type. |
| slug* | path | string | Ritual slug. |
Example response
{
"id": "rit_9f2c",
"type": "personal",
"slug": "rudrabhishek",
"title": "Rudrabhishek Puja",
"location": { "name": "Kashi Vishwanath", "city": "Varanasi", "state": "Uttar Pradesh", "district": "Varanasi" },
"add_ons": [{ "id": "addon_1", "title": "Extra Sankalp", "price": 501, "currency": "INR" }],
"livestream_available": true,
"updated_at": "2026-08-30T10:00:00.000Z",
"pricing": { "currency": "INR", "retail_price": 2100, "max_people": 4 }
}- ·Same object shape as a single item from GET /rituals.
/packagesList active, positively priced pilgrimage packages with starting prices.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| search | query | string | Free-text search. |
| destination | query | string | Filter by destination. |
| package_type | query | string | Filter by package type. |
| updated_since | query | ISO date | Return only packages updated at or after this timestamp. |
| travel_start_date | query | date (YYYY-MM-DD) | Filter to packages with availability on this date. |
| limit | query | integer (1–100) | Page size. Defaults to 20. |
| offset | query | integer | Pagination offset. |
Example response
{
"count": 20,
"total": 37,
"has_next": true,
"results": [
{
"id": "pkg_4a11",
"slug": "char-dham-yatra",
"title": "Char Dham Yatra",
"subtitle": "The complete Himalayan circuit",
"overview": "...",
"duration": { "days": 11, "nights": 10 },
"package_type": "group",
"destinations": ["Yamunotri", "Gangotri", "Kedarnath", "Badrinath"],
"images": { "cover": "https://...", "gallery": ["https://..."] },
"pricing": { "currency": "INR", "starting_from": 24999, "unit": "per_person", "basis": "double_occupancy" },
"availability": { "type": "fixed_departure", "ranges": [{ "from": "2026-10-12", "to": "2026-10-22" }] },
"updated_at": "2026-08-30T10:00:00.000Z"
}
]
}- ·Catalog responses support ETag/If-None-Match — send it during sync and treat a 304 as unchanged.
/packages/{slug}Read the sanitized itinerary, availability, terms, and selectable ritual/offering options.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| slug* | path | string | Package slug. |
Example response
{
"id": "pkg_4a11",
"slug": "char-dham-yatra",
"title": "Char Dham Yatra",
"duration": { "days": 11, "nights": 10 },
"pricing": { "currency": "INR", "starting_from": 24999, "unit": "per_person", "basis": "double_occupancy" },
"pickup_point": "Haridwar Railway Station",
"starting_place": "Haridwar",
"ending_place": "Haridwar",
"inclusions": ["..."],
"exclusions": ["..."],
"best_visit_time": "May – June, September – October",
"terms_and_conditions": ["..."],
"faqs": [{ "question": "...", "answer": "..." }],
"itinerary": [{ "day_number": 1, "title": "Arrival in Haridwar", "description": "..." }],
"selectable_options": [
{ "id": "opt_1", "type": "add_on", "title": "VIP Darshan", "description": "...", "day_number": 4, "currency": "INR", "price": 1500 }
]
}Catalog responses support ETags. Send If-None-Match during synchronization and treat a 304 response as unchanged.
Package quotes
Quotes are calculated from the trusted catalog itinerary, traveller count, hotel room rules, transport capacity, selected options, markup, and GST. Client-supplied itineraries and prices are not accepted.
curl -X POST 'https://api.tirth.com/v1/partner/package-quotes' \
-H 'Content-Type: application/json' \
-H 'X-API-Key: tirth_pk_<your-key>' \
-d '{
"package_slug": "char-dham-yatra",
"travel_start_date": "2026-10-12",
"participants_count": 1,
"selected_option_ids": []
}'const quote = await fetch('https://api.tirth.com/v1/partner/package-quotes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.TIRTH_API_KEY
},
body: JSON.stringify({
package_slug: 'char-dham-yatra',
travel_start_date: '2026-10-12',
participants_count: 1,
selected_option_ids: []
})
}).then(response => response.json());import requests
response = requests.post(
"https://api.tirth.com/v1/partner/package-quotes",
headers={"X-API-Key": TIRTH_API_KEY},
json={
"package_slug": "char-dham-yatra",
"travel_start_date": "2026-10-12",
"participants_count": 1,
"selected_option_ids": [],
},
)
quote = response.json()/package-quotesCreate a server-calculated, expiring quote for a package before booking it.
Body parameters
| Name | In | Type | Description |
|---|---|---|---|
| package_slug* | body | string | The package to quote. |
| travel_start_date* | body | date (YYYY-MM-DD) | Must be a future date. |
| participants_count* | body | integer (1–50) | Number of travellers. |
| selected_option_ids | body | string[] | IDs from the package's selectable_options. |
Example response
{
"quote_id": "pq_8cf1a2",
"package": { "slug": "char-dham-yatra", "title": "Char Dham Yatra" },
"participants_count": 4,
"selected_options": [],
"travel_dates": { "from": "2026-10-12", "to": "2026-10-22" },
"amount": 128450,
"currency": "INR",
"price_breakdown": { "base": 99996, "options": 0, "markup": 18454, "gst": 10000 },
"expires_at": "2026-09-04T12:15:00.000Z"
}- ·Quotes expire 15 minutes after creation.
- ·Each quote is scoped to the API key that created it and can be consumed exactly once.
- ·Returns 201 Created.
Hosted checkout and status
Package bookings use product_type: package. Existing ritual booking requests remain valid without a product discriminator.
curl -X POST 'https://api.tirth.com/v1/partner/bookings' \
-H 'Content-Type: application/json' \
-H 'X-API-Key: tirth_pk_<your-key>' \
-H 'Idempotency-Key: order_78492_attempt_1' \
-d '{
"product_type": "package",
"quote_id": "pq_8cf1...",
"partner_reference": "order_78492",
"customer": { "full_name": "Sample Customer", "phone": "+919999999999" },
"participants": [{ "name": "Sample Customer", "age": 34 }]
}'const booking = await fetch('https://api.tirth.com/v1/partner/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.TIRTH_API_KEY,
'Idempotency-Key': 'order_78492_attempt_1'
},
body: JSON.stringify({
product_type: 'package',
quote_id: quote.data.quote_id,
partner_reference: 'order_78492',
customer: { full_name: 'Sample Customer', phone: '+919999999999' },
participants: [{ name: 'Sample Customer', age: 34 }]
})
}).then(response => response.json());import requests
response = requests.post(
"https://api.tirth.com/v1/partner/bookings",
headers={
"X-API-Key": TIRTH_API_KEY,
"Idempotency-Key": "order_78492_attempt_1",
},
json={
"product_type": "package",
"quote_id": quote["quote_id"],
"partner_reference": "order_78492",
"customer": {"full_name": "Sample Customer", "phone": "+919999999999"},
"participants": [{"name": "Sample Customer", "age": 34}],
},
)
booking = response.json()/bookingsCreate a hosted-checkout booking for a ritual or a quoted package.
Headers
| Name | Description |
|---|---|
| Idempotency-Key* | One stable key per logical checkout attempt (pattern ^[A-Za-z0-9_-]{1,255}$). |
Body parameters
| Name | In | Type | Description |
|---|---|---|---|
| product_type | body | "package" | Set to "package" for package bookings. Omit for ritual bookings. |
| quote_id* | body | string | Required for package bookings — from POST /package-quotes. |
| ritual_type | body | string (personal | samuhik) | Required for ritual bookings. |
| ritual_slug | body | string | Required for ritual bookings. |
| selected_date | body | date | Required for ritual bookings. |
| selected_plan_id | body | string | Samuhik plan selection. |
| selected_add_on_ids | body | string[] | Ritual add-ons. |
| partner_reference* | body | string | Your own order/reference ID, echoed back in webhooks and status lookups. |
| customer* | body | object | { full_name, phone, address, email? } |
| puja_mode | body | string | Ritual bookings only. |
| prasad_delivery | body | boolean | object | Ritual bookings only. |
| participants* | body | array | Traveller/participant details. |
Example response
{
"partner_booking_id": "pb_c91a",
"tirth_booking_id": "bk_71029",
"amount": 128450,
"currency": "INR",
"payment_url": "https://pay.tirth.com/c/pb_c91a",
"expires_at": "2026-09-04T13:00:00.000Z"
}- ·Retrying the same completed request with the same Idempotency-Key returns the original booking (200).
- ·Reusing an Idempotency-Key with a different payload returns 409.
Idempotency
Use one stable Idempotency-Key for every logical checkout attempt. Retrying the same completed request returns the original booking; reusing it with different data returns 409.
Status values
reservedpayment_pendingconfirmedfailedexpired/bookings/{partner_booking_id}Reconcile payment and product-specific fulfilment status.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| partner_booking_id* | path | string | ID returned from POST /bookings. |
Example response
{
"partner_booking_id": "pb_c91a",
"partner_reference": "order_78492",
"product": { "type": "package", "slug": "char-dham-yatra" },
"package": { "title": "Char Dham Yatra" },
"amount": 128450,
"currency": "INR",
"status": "confirmed",
"tirth_booking_id": "bk_71029",
"fulfilment": {
"status": "guide_assigned",
"guide": { "name": "...", "phone": "..." },
"livestream_url": null,
"recording_url": null,
"invoice_url": "https://...",
"completed": false
},
"created_at": "2026-09-04T12:00:00.000Z",
"updated_at": "2026-09-04T12:05:00.000Z"
}- ·status is one of: reserved, payment_pending, confirmed, failed, expired.
Signed webhooks
Webhook bodies are signed with HMAC-SHA256 using your one-time-issued webhook secret. Compute the signature over the exact raw body and compare it safely with X-Tirth-Signature. Deduplicate using X-Tirth-Event-Id.
Return any 2xx status quickly. Failed deliveries are retried with bounded exponential backoff.
Errors
Errors use the same JSON envelope across the API. Handle 401 by checking the key, 403 by checking scopes, 409 as an idempotency or consumed-quote conflict, 422 as invalid/unavailable input, 429 with exponential backoff, and 503 as a temporary upstream outage.
{
"status": "error",
"message": "Package is unavailable for the full trip",
"error": "InvalidRequestInput"
}Access & pricing
Partner access is free during the partnership review, with custom terms agreed as you scale. There is no public self-service signup — every production integration is reviewed before we issue credentials.
How limits work
Each partner gets a rate limit set for their integration based on expected volume — not a fixed public tier. It defaults to 120 requests/minute and is negotiated up as you grow. See Authentication for the RateLimit-* response headers.
See the partner FAQ for how approval and onboarding work, or apply below.
Apply for accessVersioning and support
All supported endpoints are under /v1/partner. Additive fields may appear without a version change; breaking changes require a new version and migration window.
Need production access or integration help?
Apply with your use case or contact the partnerships team at hi@tirth.com.
Apply for access