# Sefera Partner API v1.2 ## Overview Server-to-server read API for publishable programs of one tenant. Tenant comes from the Bearer token. Do not send companyId. ## Base URL https://api.sefera.com.tr ## Authentication Authorization: Bearer Required scope: programs.read Invalid/revoked token: 401 unauthorized Valid token missing scope: 403 forbidden ## Server-to-server requirement Use only from a backend. Do not put the token in browsers, mobile apps, or public frontend bundles. CORS is not a security boundary. ## GET /v1/programs HTTP METHOD: GET PATH: /v1/programs PURPOSE: List publishable programs for the token tenant. AUTHENTICATION: Bearer token REQUIRED SCOPE: programs.read PATH PARAMETERS: None QUERY PARAMETERS: None REQUEST BODY: None RESPONSE: 200 ProgramListResponse ERRORS: 401, 403, 405, 429, 500 Canonical URL: https://api.sefera.com.tr/v1/programs Only publishable programs are returned. Full programs with remaining=0 are still returned. ### Example request curl -sS \ -H "Authorization: Bearer $SEFERA_PARTNER_TOKEN" \ -H "Accept: application/json" \ "https://api.sefera.com.tr/v1/programs" ### Example response { "data": [ { "id": "abc123", "name": "15 Günlük Umre", "status": "active", "departureDate": "2026-10-12", "returnDate": "2026-10-25", "durationDays": 14, "duration": { "days": 14, "nights": 13 }, "capacity": { "total": 45, "used": 23, "remaining": 22 }, "currency": "USD", "category": { "type": "Yurt Dışı", "subtype": "Umre" }, "guide": { "name": "Rehber Adı" }, "prices": { "startingAmount": 1950, "rooms": [ { "occupancy": 2, "amount": 1950 } ], "children": [ { "minAge": 2, "maxAge": 7, "amount": 1400 } ] } } ], "meta": { "count": 1 } } ## GET /v1/programs/{programId} HTTP METHOD: GET PATH: /v1/programs/{programId} PURPOSE: Return one publishable program. AUTHENTICATION: Bearer token REQUIRED SCOPE: programs.read PATH PARAMETERS: programId (string, required) QUERY PARAMETERS: None REQUEST BODY: None RESPONSE: 200 ProgramDetailResponse ERRORS: 400, 401, 403, 404, 405, 429, 500 Unknown, unpublished, or other-tenant IDs return 404 not_found with the same error body. ### Example request curl -sS \ -H "Authorization: Bearer $SEFERA_PARTNER_TOKEN" \ -H "Accept: application/json" \ "https://api.sefera.com.tr/v1/programs/abc123" ### Example response { "data": { "id": "abc123", "name": "15 Günlük Umre", "status": "active", "departureDate": "2026-10-12", "returnDate": "2026-10-25", "durationDays": 14, "duration": { "days": 14, "nights": 13 }, "capacity": { "total": 45, "used": 23, "remaining": 22 }, "currency": "USD", "category": { "type": "Yurt Dışı", "subtype": "Umre" }, "guide": { "name": "Rehber Adı" }, "prices": { "startingAmount": 1950, "rooms": [{ "occupancy": 2, "amount": 1950 }], "children": [{ "minAge": 2, "maxAge": 7, "amount": 1400 }] }, "description": "Program açıklaması", "departurePoint": "Istanbul", "hotels": [ { "name": "Hotel Name", "location": "Mekke", "checkIn": "2026-10-12", "checkOut": "2026-10-18", "starLevel": 5 } ], "marketingTags": ["Ramazan"], "staff": { "leadGuide": { "name": "Rehber Adı" }, "teachers": [ { "name": "Hoca Adı", "gender": "male", "roleLabel": "Hoca" } ] }, "accommodation": { "stays": [ { "name": "Hotel Name", "location": "Mekke", "checkIn": "2026-10-12", "checkOut": "2026-10-18", "starLevel": 5 } ], "description": "Konaklama açıklaması" }, "transport": { "flights": [ { "airline": { "code": "THY", "logoUrl": "https://api.sefera.com.tr/v1/assets/airlines/thy.svg" } } ] }, "media": { "coverUrl": null, "galleryUrls": [] }, "content": { "included": ["Uçak bileti"], "excluded": ["Kişisel harcamalar"], "visits": ["Mescid-i Haram"], "accommodationDescription": "Konaklama açıklaması", "faq": [ { "question": "Vize dahil mi?", "answer": "Evet." } ], "itinerary": [ { "title": "Varış", "description": "Havalimanı karşılama" } ] } } } ## Public fields ProgramListItem: id (string, required), name (string, required), status (string enum active, required), departureDate (date, nullable), returnDate (date, nullable), durationDays (integer, nullable, inclusive date-only difference), duration ({days, nights}, nullable), capacity (Capacity, required), currency (string, nullable), category (Category, nullable), guide (Guide, nullable), prices (Prices, required). ProgramDetail adds: description (string, nullable), departurePoint (string, nullable), hotels (HotelStay[]), marketingTags (string[] | null), staff (Staff, nullable), accommodation (Accommodation, nullable), transport (Transport, nullable), media (PublicMedia, nullable), content (PublicContent, nullable). Capacity: total, used, remaining (integers, required). remaining = max(0, total - used). Prices: startingAmount (number, nullable), rooms, children. RoomPrice: occupancy (integer >= 1), amount (number). ChildPrice: minAge (integer), maxAge (integer), amount (number). Duration: days (integer), nights (integer). HotelStay: name, location, checkIn (date), checkOut (date), starLevel (1-5, nullable). Category: type, subtype; nullable strings. Guide: name (string). Staff: leadGuide (Guide, nullable), teachers (StaffTeacher[]). StaffTeacher: name (string), gender (male|female, nullable), roleLabel (string, nullable). Accommodation: stays (HotelStay[]), description (string, nullable). Transport: flights[].airline {code, logoUrl}. PublicMedia: coverUrl (string, nullable), galleryUrls (string[]). PublicContent: included, excluded, visits (string[]), accommodationDescription (string, nullable), faq [{question, answer}], itinerary [{title, description}]. Dates are YYYY-MM-DD. Raw dailyPlan, PII, internal IDs, hotel phone/address/internal notes are never published. ## Capacity semantics used counts operational passenger enrollments only. Terminal enrollments excluded. Program staff excluded. remaining=0 does not hide the program. ## Pricing semantics rooms map occupancy + amount. children map minAge, maxAge, amount. If room prices are empty and a legacy single price exists, it is published as occupancy 2. ## Hotels and guide Hotels come from program stays: name, location, checkIn, checkOut, optional starLevel. Guide is display name only. Staff teachers expose name plus optional gender and roleLabel. ## Errors { "error": { "code": "unauthorized", "message": "Authentication required" }, "requestId": "req-example" } HTTP 400 invalid_request HTTP 401 unauthorized HTTP 403 forbidden HTTP 404 not_found HTTP 405 method_not_allowed HTTP 429 rate_limit_exceeded HTTP 500 internal_error JSON UTF-8. No stack traces. ## Rate limits Per token. 429 rate_limit_exceeded with Retry-After. ## X-Request-Id Always returned. Optional request header X-Request-Id may be echoed if it matches [A-Za-z0-9._-]+ up to 128 characters. Errors also include requestId. ## Token rotation/revocation Rotate issues a new token and revokes the previous active token. Revoke makes previous tokens 401. Store plaintext only in a secret store. ## Versioning Documentation version v1.2 is additive. Endpoint paths stay /v1/programs. Tolerate new optional fields. Breaking changes use a new path prefix. Legacy deprecated: GET /api/v1/partner/programs returns { success, programs: [{ name, capacity }] }. New integrations must use /v1/programs. ## PHP example true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token, 'Accept: application/json', ], ]); $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code !== 200) { throw new RuntimeException('Partner API HTTP ' . $code); } $payload = json_decode($body, true); ## Node.js example const token = process.env.SEFERA_PARTNER_TOKEN; const base = 'https://api.sefera.com.tr'; const res = await fetch(`${base}/v1/programs`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) { throw new Error(`Partner API HTTP ${res.status}`); } const payload = await res.json(); ## cURL example curl -sS \ -H "Authorization: Bearer $SEFERA_PARTNER_TOKEN" \ -H "Accept: application/json" \ "https://api.sefera.com.tr/v1/programs"