Quick start
Approved partners receive one production API key with explicit scopes. Fund the partner wallet, then create credit-paid bookings that confirm synchronously and emit 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 with payment_method set explicitly to CREDITS.
- 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 online bookable legacy and published 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 available for the full trip starting 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" },
"package_kind": "PILGRIMAGE",
"pricing_strategy": "FIXED_PER_PERSON",
"availability": { "type": "ALL_TIME", "ranges": [] },
"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 itinerary, pricing type, terms, legacy options, and pilgrimage departures.
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) | Required for legacy and flexible-date packages; optional for scheduled departures. |
| participants_count* | body | integer (1–50) | Number of travellers. |
| selected_option_ids | body | string[] | Legacy packages only: IDs from selectable_options. |
| pricing_selection | body | object | Pilgrimages: adult/child/infant counts, group tier when needed, and departure ID for scheduled trips. |
Example response
{
"quote_id": "pq_8cf1a2",
"package": { "slug": "char-dham-yatra", "title": "Char Dham Yatra" },
"participants_count": 2,
"pricing_selection": { "adults": 2, "child_with_bed": 0, "child_without_bed": 0, "infants": 0, "departure_id": "68a123456789abcdef012345" },
"travel_dates": { "from": "2030-10-12", "to": "2030-10-22" },
"amount": 4200,
"currency": "INR",
"price_breakdown": { "package_price": 4000, "discount_amount": 0, "gst_rate": 5, "gst_amount": 200, "total_amount": 4200 },
"expires_at": "2030-10-01T12:15:00.000Z"
}- ·Quotes expire 15 minutes after creation.
- ·The quoted price is fixed, but seats are checked again when booking.
- ·Each quote is scoped to the API key that created it and can be consumed exactly once.
- ·Returns 201 Created.
Prepaid credits
Credit-funded bookings confirm immediately without redirecting a customer to a payment page. One credit equals ₹1, credits do not expire, and a booking requires the full balance—split payments are not supported.
payment_method: CREDITS is required. Omission or any other value returns 422 CreditsRequired. If the balance is insufficient, the API returns 402 and does not consume credits or the package quote./creditsRead the current prepaid balance and lifetime totals.
Example response
{
"balance_credits": 71550,
"currency": "INR",
"conversion": { "credits": 1, "inr": 1 },
"lifetime_credited": 200000,
"lifetime_debited": 128450
}- ·Requires credits:read. One credit equals ₹1 and credits do not expire.
/credits/transactionsList cursor-paginated wallet ledger entries.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| limit | query | integer (1–100) | Page size. Defaults to 25. |
| cursor | query | string | next_cursor from the previous response. |
| type | query | credit transaction type | Optional entry-type filter. |
Example response
{
"count": 1,
"has_next": false,
"next_cursor": null,
"results": [{
"transaction_id": "pct_8cf1a2",
"type": "BOOKING_DEBIT",
"amount_credits": -128450,
"balance_after": 71550,
"currency": "INR",
"partner_booking_id": "pb_c91a"
}]
}- ·Ledger entries are immutable; corrections appear as compensating entries.
Booking 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",
"payment_method": "CREDITS",
"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',
payment_method: 'CREDITS',
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",
"payment_method": "CREDITS",
"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 credit-paid 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 |
|---|---|---|---|
| payment_method* | body | "CREDITS" | Required. Partner API bookings confirm synchronously from the prepaid balance and do not return a payment URL. |
| 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 details. Pilgrimages require name, age, and traveller_type matching the quote. |
Example response
{
"partner_booking_id": "pb_c91a",
"tirth_booking_id": "bk_71029",
"amount": 128450,
"currency": "INR",
"payment_method": "CREDITS",
"payment_status": "paid",
"credits_debited": 128450,
"credit_balance": 71550,
"credit_transaction_id": "pct_8cf1a2",
"payment_url": null
}- ·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.
- ·Omitting CREDITS or sending another payment method returns 422 CreditsRequired.
- ·The full credit balance is required; insufficient balance returns 402 without consuming the booking or package quote.
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_pendingconfirmedcancellation_requestedcancelledrefund_pendingrefundedfailedexpired/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, cancellation_requested, cancelled, refund_pending, refunded, failed, expired.
/bookings/{partner_booking_id}/cancellationsRaise one operations-reviewed cancellation request for a paid ritual or package booking.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| partner_booking_id* | path | string | Partner booking ID returned from POST /bookings. |
| reason* | body | string (3–1000 chars) | Why the customer is requesting cancellation. |
| partner_cancellation_reference | body | string | Your optional reconciliation reference. |
Example response
{
"partner_booking_id": "pb_c91a...",
"status": "cancellation_requested",
"cancellation": {
"status": "requested",
"reason": "Customer travel plans changed",
"partner_cancellation_reference": "cancel_78492",
"requested_at": "2026-09-27T12:00:00.000Z"
},
"refund": {
"status": "not_requested",
"reference": null,
"amount": null,
"currency": "INR",
"completed_at": null
}
}- ·The first request returns 202; repeats return the original request and current state with 200.
- ·This requests review only. It does not guarantee cancellation or automatically issue a refund.
- ·Requires the bookings:cancel scope.
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