Partner API

Production reference · v1

Partner API documentation

One integration for ritual inventory, pilgrimage packages, hosted payment, and fulfilment updates.

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.

  1. 1Store your API key only on your server.
  2. 2Fetch rituals or packages with the appropriate read scope.
  3. 3Create a package quote and use it before it expires.
  4. 4Create a booking once and redirect to the returned hosted checkout.
  5. 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

GET/rituals

List active personal and upcoming Samuhik rituals.

Parameters

NameInTypeDescription
typequerystring (personal | samuhik)Filter to one ritual type.
searchquerystringFree-text search across title and location.
cityquerystringFilter by city.
statequerystringFilter by state.
destinationquerystringFilter by destination.
updated_sincequeryISO dateReturn only rituals updated at or after this timestamp.
limitqueryinteger (1–100)Page size. Defaults to 20.
offsetqueryintegerPagination 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}.
GET/rituals/{type}/{slug}

Read plans, pricing, benefits, inclusions, add-ons, and livestream availability for one ritual.

Parameters

NameInTypeDescription
type*pathstring (personal | samuhik)Ritual type.
slug*pathstringRitual 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.
GET/packages

List active, positively priced pilgrimage packages with starting prices.

Parameters

NameInTypeDescription
searchquerystringFree-text search.
destinationquerystringFilter by destination.
package_typequerystringFilter by package type.
updated_sincequeryISO dateReturn only packages updated at or after this timestamp.
travel_start_datequerydate (YYYY-MM-DD)Filter to packages with availability on this date.
limitqueryinteger (1–100)Page size. Defaults to 20.
offsetqueryintegerPagination 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.
GET/packages/{slug}

Read the sanitized itinerary, availability, terms, and selectable ritual/offering options.

Parameters

NameInTypeDescription
slug*pathstringPackage 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()
Quotes expire after 15 minutes, belong to the API key that created them, and can be consumed only once.
POST/package-quotes

Create a server-calculated, expiring quote for a package before booking it.

Body parameters

NameInTypeDescription
package_slug*bodystringThe package to quote.
travel_start_date*bodydate (YYYY-MM-DD)Must be a future date.
participants_count*bodyinteger (1–50)Number of travellers.
selected_option_idsbodystring[]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()
POST/bookings

Create a hosted-checkout booking for a ritual or a quoted package.

Headers

NameDescription
Idempotency-Key*One stable key per logical checkout attempt (pattern ^[A-Za-z0-9_-]{1,255}$).

Body parameters

NameInTypeDescription
product_typebody"package"Set to "package" for package bookings. Omit for ritual bookings.
quote_id*bodystringRequired for package bookings — from POST /package-quotes.
ritual_typebodystring (personal | samuhik)Required for ritual bookings.
ritual_slugbodystringRequired for ritual bookings.
selected_datebodydateRequired for ritual bookings.
selected_plan_idbodystringSamuhik plan selection.
selected_add_on_idsbodystring[]Ritual add-ons.
partner_reference*bodystringYour own order/reference ID, echoed back in webhooks and status lookups.
customer*bodyobject{ full_name, phone, address, email? }
puja_modebodystringRitual bookings only.
prasad_deliverybodyboolean | objectRitual bookings only.
participants*bodyarrayTraveller/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
GET/bookings/{partner_booking_id}

Reconcile payment and product-specific fulfilment status.

Parameters

NameInTypeDescription
partner_booking_id*pathstringID 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.

booking.confirmed
booking.payment_failed
booking.cancelled
booking.pandit_assigned
booking.guide_assigned
booking.livestream_available
booking.completed

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 access

Versioning 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