openapi: 3.0.3 info: title: Travel Product Manager API version: 1.0.0 description: | Synthetic API for a single travel offer workflow. Intended order of operations: 1. get recent users, 2. get top hotels, 3. build hotel preference segments from users and hotels, 4. build user-to-hotel assignments from segments, 5. send hotel offers by email from assignments. Each endpoint has one specific responsibility. The workflow should be interpreted as a strict data pipeline where the output array of one step becomes the input field of the next step. servers: - url: http://demo-api:8010 - url: http://localhost:8010 - url: https://api.travel.example.com paths: /users/recent: get: operationId: getRecentUsers tags: - travel-offer-workflow summary: Get recent users for travel campaigns description: | Returns a list of recent users active in the last 7 days. By default this endpoint returns up to 30 users because the limit parameter defaults to 30. Output of this endpoint is the users array that should be passed as the users field to /segments/hotel. This endpoint does not retrieve hotels, create segments, create assignments, or send emails. parameters: - in: query name: last_active_after schema: type: string format: date-time required: false description: | Optional lower bound for user activity time. Only users active after this timestamp should be returned. If omitted, the endpoint behaves like "last 7 days". - in: query name: limit schema: type: integer minimum: 1 maximum: 100 default: 30 required: false description: | Maximum number of users to return. If omitted, the endpoint returns up to 30 users. responses: "200": description: | Successful response containing the users array for the first workflow step. This users array should be passed forward to /segments/hotel. content: application/json: schema: $ref: "#/components/schemas/RecentUsersResponse" examples: sample: value: users: - id: usr_001 email: user001@example.com last_active: "2026-03-13T10:00:00Z" - id: usr_002 email: user002@example.com last_active: "2026-03-13T09:55:00Z" - id: usr_003 email: user003@example.com last_active: "2026-03-13T09:50:00Z" - id: usr_004 email: user004@example.com last_active: "2026-03-13T09:45:00Z" - id: usr_005 email: user005@example.com last_active: "2026-03-13T09:40:00Z" - id: usr_006 email: user006@example.com last_active: "2026-03-13T09:35:00Z" - id: usr_007 email: user007@example.com last_active: "2026-03-13T09:30:00Z" - id: usr_008 email: user008@example.com last_active: "2026-03-13T09:25:00Z" - id: usr_009 email: user009@example.com last_active: "2026-03-13T09:20:00Z" - id: usr_010 email: user010@example.com last_active: "2026-03-13T09:15:00Z" - id: usr_011 email: user011@example.com last_active: "2026-03-13T09:10:00Z" - id: usr_012 email: user012@example.com last_active: "2026-03-13T09:05:00Z" - id: usr_013 email: user013@example.com last_active: "2026-03-13T09:00:00Z" - id: usr_014 email: user014@example.com last_active: "2026-03-13T08:55:00Z" - id: usr_015 email: user015@example.com last_active: "2026-03-13T08:50:00Z" - id: usr_016 email: user016@example.com last_active: "2026-03-13T08:45:00Z" - id: usr_017 email: user017@example.com last_active: "2026-03-13T08:40:00Z" - id: usr_018 email: user018@example.com last_active: "2026-03-13T08:35:00Z" - id: usr_019 email: user019@example.com last_active: "2026-03-13T08:30:00Z" - id: usr_020 email: user020@example.com last_active: "2026-03-13T08:25:00Z" - id: usr_021 email: user021@example.com last_active: "2026-03-13T08:20:00Z" - id: usr_022 email: user022@example.com last_active: "2026-03-13T08:15:00Z" - id: usr_023 email: user023@example.com last_active: "2026-03-13T08:10:00Z" - id: usr_024 email: user024@example.com last_active: "2026-03-13T08:05:00Z" - id: usr_025 email: user025@example.com last_active: "2026-03-13T08:00:00Z" - id: usr_026 email: user026@example.com last_active: "2026-03-13T07:55:00Z" - id: usr_027 email: user027@example.com last_active: "2026-03-13T07:50:00Z" - id: usr_028 email: user028@example.com last_active: "2026-03-13T07:45:00Z" - id: usr_029 email: user029@example.com last_active: "2026-03-13T07:40:00Z" - id: usr_030 email: user030@example.com last_active: "2026-03-13T07:35:00Z" /hotels/top: get: operationId: getTopHotels tags: - travel-offer-workflow summary: Get top hotels for offers description: | Returns a list of candidate hotels for the offer workflow. By default this endpoint returns up to 5 hotels because the limit parameter defaults to 5. Output of this endpoint is the hotels array that should be passed as the hotels field to /segments/hotel. This endpoint does not retrieve users, create segments, create assignments, or send emails. parameters: - in: query name: limit schema: type: integer minimum: 1 maximum: 20 default: 5 required: false description: | Maximum number of hotels to return. If omitted, the endpoint returns up to 5 hotels. - in: query name: city schema: type: string required: false description: | Optional city filter. If provided, only hotels from this city should be returned. responses: "200": description: | Successful response containing the hotels array for the second workflow step. This hotels array should be passed forward to /segments/hotel. content: application/json: schema: $ref: "#/components/schemas/TopHotelsResponse" examples: sample: value: hotels: - id: hotel_001 name: Hotel Aurora city: Berlin - id: hotel_002 name: Sea Breeze Resort city: Lisbon - id: hotel_003 name: Mountain Vista city: Zurich - id: hotel_004 name: City Loft city: Amsterdam - id: hotel_005 name: River Palace city: Prague /segments/hotel: post: operationId: segmentUsersByHotelPreferences tags: - travel-offer-workflow summary: Segment recent users by hotel preferences description: | Creates hotel-based user segments from two required inputs in one request: users and hotels. The users field must contain the users array returned by /users/recent. The hotels field must contain the hotels array returned by /hotels/top. A common workflow is: get up to 30 recent users, get top hotels, then send both arrays to this endpoint to distribute users across hotels by preference. Output of this endpoint is the segments array used as the segments field in /assignments/hotels. This endpoint does not send emails. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/HotelSegmentsRequest" examples: sample: value: users: - id: usr_001 email: user001@example.com last_active: "2026-03-13T10:00:00Z" hotels: - id: hotel_001 name: Hotel Aurora city: Berlin responses: "200": description: | Successful response containing the segments array. This segments array should be passed forward to /assignments/hotels. content: application/json: schema: $ref: "#/components/schemas/HotelSegmentsResponse" examples: sample: value: segments: - segment_id: seg_berlin hotel_id: hotel_001 user_ids: ["usr_001", "usr_002"] /assignments/hotels: post: operationId: assignUsersToHotels tags: - travel-offer-workflow summary: Assign users to hotels based on segments description: | Builds final user-to-hotel assignments from segments. The segments field must contain the segments array returned by /segments/hotel. Output of this endpoint is the assignments array used as the assignments field in /emails/send-offers. This endpoint does not send emails and does not fetch users or hotels. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AssignmentsRequest" examples: sample: value: segments: - segment_id: seg_berlin hotel_id: hotel_001 user_ids: ["usr_001", "usr_002"] responses: "200": description: | Successful response containing the assignments array. This assignments array should be passed forward to /emails/send-offers. content: application/json: schema: $ref: "#/components/schemas/AssignmentsResponse" examples: sample: value: assignments: - user_id: usr_001 hotel_id: hotel_001 - user_id: usr_002 hotel_id: hotel_001 /emails/send-offers: post: operationId: sendHotelOffersByEmail tags: - travel-offer-workflow summary: Send hotel offers by email description: | Sends hotel offer emails to users based on final assignments. The assignments field must contain the assignments array returned by /assignments/hotels. This endpoint is the final delivery step of the workflow. It does not build new segments or assignments. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/EmailOfferRequest" examples: sample: value: template_id: offer_template_2026 assignments: - user_id: usr_001 hotel_id: hotel_001 - user_id: usr_002 hotel_id: hotel_001 responses: "200": description: | Successful response containing the result of the email delivery step. This is the final output of the workflow. content: application/json: schema: $ref: "#/components/schemas/EmailOfferResponse" examples: sample: value: sent_count: 2 failed_count: 0 failed: [] components: schemas: User: description: | A recent user eligible to receive a hotel offer email. User objects are produced by /users/recent and then reused in /segments/hotel. type: object required: [id, email, last_active] properties: id: type: string description: Stable unique user identifier. email: type: string format: email description: Email address used in the final offer delivery step. last_active: type: string format: date-time description: Most recent activity timestamp used to identify recent users. Hotel: description: | A hotel candidate that may be recommended to users. Hotel objects are produced by /hotels/top and then reused in /segments/hotel. type: object required: [id, name, city] properties: id: type: string description: Stable unique hotel identifier. name: type: string description: Human-readable hotel name shown in offers. city: type: string description: City where the hotel is located. Segment: description: | A hotel preference segment that groups users for one hotel. Segment objects are produced by /segments/hotel and then reused in /assignments/hotels. type: object required: [segment_id, hotel_id, user_ids] properties: segment_id: type: string description: Stable unique segment identifier. hotel_id: type: string description: Hotel identifier associated with this segment. user_ids: type: array description: User identifiers that belong to this hotel preference segment. items: type: string Assignment: description: | A final mapping between one user and one hotel offer. Assignment objects are produced by /assignments/hotels and then reused in /emails/send-offers. type: object required: [user_id, hotel_id] properties: user_id: type: string description: Identifier of the user who should receive the offer. hotel_id: type: string description: Identifier of the hotel assigned to the user. RecentUsersResponse: description: Response containing the users array produced by /users/recent. type: object required: [users] properties: users: type: array description: | Recent users that should be copied into the users field of /segments/hotel. With the default limit this array usually contains up to 30 users. items: $ref: "#/components/schemas/User" TopHotelsResponse: description: Response containing the hotels array produced by /hotels/top. type: object required: [hotels] properties: hotels: type: array description: | Candidate hotels that should be copied into the hotels field of /segments/hotel. With the default limit this array usually contains up to 5 hotels. items: $ref: "#/components/schemas/Hotel" HotelSegmentsRequest: description: | Request body for building segments from users and hotels. This request combines the users array from /users/recent and the hotels array from /hotels/top. type: object required: [users, hotels] properties: users: type: array description: | Users from /users/recent. This is typically the same array of up to 30 recent users returned by the first step. These users are being distributed across candidate hotels by preference. items: $ref: "#/components/schemas/User" hotels: type: array description: | Hotels from /hotels/top that should be used as candidate destinations for user distribution. items: $ref: "#/components/schemas/Hotel" HotelSegmentsResponse: description: Response containing the segments array produced by /segments/hotel. type: object required: [segments] properties: segments: type: array description: | Segments that should be copied into the segments field of /assignments/hotels. items: $ref: "#/components/schemas/Segment" AssignmentsRequest: description: | Request body for building assignments from the segments array returned by /segments/hotel. type: object required: [segments] properties: segments: type: array description: | Segments from /segments/hotel that should be converted into final user-to-hotel assignments. items: $ref: "#/components/schemas/Segment" AssignmentsResponse: description: Response containing the assignments array produced by /assignments/hotels. type: object required: [assignments] properties: assignments: type: array description: | Assignments that should be copied into the assignments field of /emails/send-offers. items: $ref: "#/components/schemas/Assignment" EmailOfferRequest: description: | Request body for sending offer emails from the assignments array returned by /assignments/hotels. type: object required: [template_id, assignments] properties: template_id: type: string default: offer_template_2026 description: Identifier of the email template to use for every assignment in this request. assignments: type: array description: | Assignments from /assignments/hotels that should be emailed in the final step. items: $ref: "#/components/schemas/Assignment" EmailOfferResponse: description: | Result of the final email delivery step. This response does not contain new users, hotels, segments, or assignments. type: object required: [sent_count, failed_count, failed] properties: sent_count: type: integer description: Number of assignments for which an email was sent successfully. failed_count: type: integer description: Number of assignments for which email delivery failed. failed: type: array description: Failed deliveries with reasons for each affected user. items: type: object required: [user_id, reason] properties: user_id: type: string description: Identifier of the user whose email delivery failed. reason: type: string description: Human-readable explanation of why the email could not be sent.