Skip to content
Last updated

MOGU Public API Documentation

Welcome to the MOGU Public API documentation. This API allows you to programmatically manage trips and catalogs for your travel business.

Getting Started

Authentication

The API supports two authentication modes. Pick the one that matches your integration:

1. API Key (user-bound)

Used by single-tenant integrations acting as one specific agent. Generate the key from your MOGU account; it starts with ak_.

Include it in the Authorization header of every request:

Authorization: Bearer YOUR_API_KEY

2. M2M on-behalf-of (server-to-server)

Used by partner backends that act on behalf of multiple agents inside one or more accounts. The integration uses a single Auth0 client_credentials token; the per-agent context is sent in the x-on-behalf-of header on each request.

Authorization: Bearer YOUR_AUTH0_M2M_TOKEN
x-on-behalf-of: agent@example.com           # uses the agent's current account
x-on-behalf-of: agent@example.com,456       # explicit account

Scope-based access. Your M2M client is provisioned with a set of Auth0 scopes (create:trips, read:trips, update:trips, …). Those scopes determine the full set of operations your backend can invoke — there is no per-endpoint allowlist. If your client is granted update:trips, every documented operation that requires update:trips is callable (for example PATCH /trips/{tripId} and PATCH /trips/{tripId}/config), not just the ones that explicitly show the M2M scheme in the parameters list.

Account scope. Your client is also bound to one or more accounts (its grant). The account resolved from x-on-behalf-of (either the explicit ,account_id or the agent's current account) must be within that grant — or, when the grant points at an organization parent, one of its active children.

Failure responses are uniform. Every on-behalf-of failure branch — unknown email, agent without a usable account, account outside the grant, agent without membership — returns the same 403 Forbidden. This prevents M2M callers from enumerating platform email addresses by inspecting response shapes.

Base URLs

  • Trips & Catalogs: https://trips.api.moguplatform.com
  • AI Trip Imports: https://ai.api.moguplatform.com

Quick Start

1. Obtain an API Key

To access the API, you'll need to generate an API Key from your MOGU account:

  1. Log in to your MOGU account
  2. Navigate to the Integrations tab in your account settings
  3. Generate a new API Key

The API Key will start with ak_ and will include the necessary permissions based on your account role.

Learn how to generate an API Key: For step-by-step instructions, see our How to generate an API Key guide.

2. Make Your First Request

Here's a simple example to list your trips:

curl -X GET https://trips.api.moguplatform.com/trips \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json"

Core Concepts

What is a Trip?

A Trip is a travel proposal or itinerary that you share with your travelers. Each trip has a title, dates, settings, and a visual layout defined by its TripConfig.

What is a TripConfig?

The TripConfig is the full content and layout of a trip. It contains:

  • blocks: The visual content tree (services, text, media, etc.)
  • contact: Agency/agent contact information
  • settings: Language, currency, date format preferences
  • theme: Branding (colors, logo, fonts)

The workflow is fetch, edit, replace:

  1. GET /trips/{tripId}/config — fetch the current config
  2. Modify the returned object locally
  3. PUT /trips/{tripId}/config — send the full updated config back

Glossary

A quick primer on the domain vocabulary used throughout this reference:

  • Block — the atomic unit of trip content. A block has a type, a content payload specific to that type, and shared envelope fields (id, parent, children, hidden, metadata).
  • Container block — a block that holds other blocks via children (e.g. itinerary, itineraryDay, group, box, dropdown). It structures layout rather than carrying a service.
  • Service block — a block representing a bookable/real-world item the traveler experiences: accomodation, flight, activity, food, cruise, transport, train, info.
  • TripConfig — the full content + layout of a trip: its blocks tree, contact, settings and theme. Fetched and replaced as a whole (see the workflow above).
  • Proposal — the traveler-facing presentation of a trip (the published itinerary the agent shares). "Trip" and "proposal" are often used interchangeably.
  • joinCode — a short code that lets a traveler join/access a trip.
  • slug — the URL-friendly identifier in a trip's public link (e.g. paris-summer-2025-abc). Generated on creation (but it can be edited too).
  • visualization — a recorded view of a trip by a traveler; used for engagement tracking.

Blocks

A trip is visually represented as a tree of blocks (TripConfig.blocks).

Shared envelope fields

Every block, regardless of type, carries the same envelope fields (the type-specific payload lives under content):

  • id (string, required) — keep it unique across the whole block tree.
  • type (string, required) — the discriminator that selects the content shape (e.g. accomodation, flight, text). See Block types below for the full list.
  • parent (string) — the id of the containing block. Use parent + children to model the hierarchy.
  • children (array) — child blocks. Order matters — it defines the visual order.
  • hidden (boolean) — hidden: true hides a block from public view, without removing it.
  • metadata (object) — a free-form key/value bag. MOGU stores it and echoes it back verbatim on GET /trips/{tripId}/config; it is never interpreted by the platform, so use it to carry your own external references (e.g. your own booking IDs). It is not surfaced to travelers.

Container blocks (like itinerary, itineraryDay, group, box, dropdown) can hold other blocks via children.

Spelling note: the accommodation service type is accomodation (single middle "m"). This is the canonical, intentional value — do not "correct" it to accommodation, or the block will be rejected.

optional on service blocks: setting optional: true on a service block marks it as a traveler-selectable add-on (shown as something the traveler can choose to include or exclude) rather than a guaranteed part of the trip.

The trip configuration endpoint is replace-based:

  • GET /trips/{tripId}/config returns the full config.
  • PUT /trips/{tripId}/config replaces the full config.

Recommended flow:

  1. Create or pick a trip (POST /trips or list existing trips)
  2. Fetch config: GET /trips/{tripId}/config
  3. Modify the returned object locally (typically blocks)
  4. Send the full updated config back: PUT /trips/{tripId}/config

Minimal example: itinerary with 2 days

This is an example of a minimal, practical block tree:

{
  "blocks": [
    {
      "id": "b-itinerary",
      "type": "itinerary",
      "content": {
        "selectedDay": 0,
        "showDates": true,
        "showMap": true,
        "design": "tabs"
      },
      "children": [
        {
          "id": "b-day-1",
          "type": "itineraryDay",
          "parent": "b-itinerary",
          "content": {
            "title": "Day 1 - Arrival",
            "description": "<p>Arrival and hotel check-in.</p>"
          },
          "children": [
            {
              "id": "b-title-1",
              "type": "title",
              "parent": "b-day-1",
              "content": { "title": "Welcome" }
            },
            {
              "id": "b-text-1",
              "type": "text",
              "parent": "b-day-1",
              "content": { "text": "<p>Meet your guide at the airport.</p>" }
            },
            {
              "id": "b-transport-1",
              "type": "transport",
              "parent": "b-day-1",
              "content": {
                "title": "Private transfer",
                "description": "<p>Airport → Hotel</p>"
              }
            }
          ]
        },
        {
          "id": "b-day-2",
          "type": "itineraryDay",
          "parent": "b-itinerary",
          "content": {
            "title": "Day 2 - City highlights"
          },
          "children": [
            {
              "id": "b-activity-1",
              "type": "activity",
              "parent": "b-day-2",
              "content": {
                "title": "Guided city tour",
                "description": "<p>Walking tour of the historic center.</p>"
              }
            },
            {
              "id": "b-map-1",
              "type": "map",
              "parent": "b-day-2",
              "content": {
                "mapUrl": "https://www.google.com/maps/d/viewer?mid=..."
              }
            }
          ]
        }
      ]
    },
    {
      "id": "b-services-summary",
      "type": "servicesSummary",
      "content": {
        "included": true,
        "includedText": "<ul><li>Hotel</li><li>Transfers</li></ul>",
        "notIncluded": true,
        "notIncludedText": "<ul><li>Travel insurance</li></ul>"
      }
    }
  ]
}

Example: adding a hotel to a day

{
  "id": "b-hotel-1",
  "type": "accomodation",
  "parent": "b-day-1",
  "content": {
    "title": "Hotel Ritz Paris",
    "location": "15 Place Vendôme, 75001 Paris",
    "startDate": "2025-07-15T00:00:00Z",
    "finishDate": "2025-07-22T00:00:00Z",
    "nights": 7,
    "roomType": "Double",
    "stayRegime": "BedAndBreakfast",
    "description": "5-star hotel in the heart of Paris",
    "images": [
      { "url": "https://example.com/ritz.jpg", "display": "cover" }
    ]
  }
}

Example: adding a flight

{
  "id": "b-flight-1",
  "type": "flight",
  "parent": "b-day-1",
  "content": {
    "flightNumber": "IB3170",
    "departureDateTime": "2025-07-15T08:00:00Z",
    "arrivalDateTime": "2025-07-15T10:15:00Z",
    "departureAirport": { "iata": "MAD", "name": "Adolfo Suárez Madrid–Barajas", "city": "Madrid", "country": "Spain" },
    "arrivalAirport": { "iata": "CDG", "name": "Charles de Gaulle Airport", "city": "Paris", "country": "France" },
    "airline": { "iata": "IB", "name": "Iberia" },
    "elapsedTime": 135
  }
}

Realistic example: a full day (flight + hotel + activity)

A complete, copy-pasteable itineraryDay wiring a flight, a hotel and an activity into one day — with dates, locations (including coordinates and a Google Place ID), and images. Place this itinerary inside your config's blocks array (see the replace-based workflow below).

{
  "id": "b-itinerary",
  "type": "itinerary",
  "content": { "showDates": true, "showMap": true, "design": "tabs" },
  "children": [
    {
      "id": "b-day-1",
      "type": "itineraryDay",
      "parent": "b-itinerary",
      "content": {
        "title": "Day 1 — Arrival in Paris",
        "description": "<p>Morning flight from Madrid, hotel check-in, evening at leisure.</p>"
      },
      "children": [
        {
          "id": "b-flight-1",
          "type": "flight",
          "parent": "b-day-1",
          "content": {
            "flightNumber": "IB3170",
            "departureDateTime": "2025-07-15T08:00:00Z",
            "arrivalDateTime": "2025-07-15T10:15:00Z",
            "departureAirport": { "iata": "MAD", "name": "Adolfo Suárez Madrid–Barajas", "city": "Madrid", "country": "Spain" },
            "arrivalAirport": { "iata": "CDG", "name": "Charles de Gaulle Airport", "city": "Paris", "country": "France" },
            "airline": { "iata": "IB", "name": "Iberia" },
            "elapsedTime": 135
          }
        },
        {
          "id": "b-hotel-1",
          "type": "accomodation",
          "parent": "b-day-1",
          "content": {
            "title": "Hôtel Ritz Paris",
            "location": "15 Place Vendôme, 75001 Paris, France",
            "lat": 48.8686,
            "lon": 2.3294,
            "googlePlaceId": "ChIJN1blFLsV44cRga5d4gjV62Q",
            "startDate": "2025-07-15T00:00:00Z",
            "finishDate": "2025-07-22T00:00:00Z",
            "nights": 7,
            "roomType": "Double",
            "stayRegime": "BedAndBreakfast",
            "description": "<p>5-star hotel in the heart of Paris.</p>",
            "images": [
              { "url": "https://example.com/ritz.jpg", "display": "cover" }
            ]
          }
        },
        {
          "id": "b-activity-1",
          "type": "activity",
          "parent": "b-day-1",
          "content": {
            "title": "Eiffel Tower — skip-the-line",
            "location": "Champ de Mars, 5 Av. Anatole France, 75007 Paris",
            "lat": 48.8584,
            "lon": 2.2945,
            "googlePlaceId": "ChIJLU7jZClu5kcR4PcOOO6p3I0",
            "startDate": "2025-07-15T18:00:00Z",
            "startTime": "2025-07-15T18:00:00Z",
            "description": "<p>Skip-the-line summit access at sunset.</p>",
            "optional": true,
            "images": [
              { "url": "https://example.com/eiffel.jpg", "display": "cover" }
            ]
          }
        }
      ]
    }
  ]
}

This single tree shows the common conventions in one place: container (itinerary) → day (itineraryDay) → service blocks (flight, accomodation, activity); parent/children linking; ISO 8601 dates; free-text location enriched with lat/lon + googlePlaceId; sanitized HTML descriptions; and an optional add-on activity.

Full workflow example

Create a trip, fetch its config, add a block, and save:

# 1. Create a new trip
curl -X POST https://trips.api.moguplatform.com/trips \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Paris Summer 2025", "duration": 7}'

# Response: { "id": 12345, "slug": "paris-summer-2025-abc", ... }

# 2. Fetch the trip config
curl -X GET https://trips.api.moguplatform.com/trips/12345/config \
  -H "Authorization: Bearer YOUR_API_KEY"

# 3. Modify the blocks array locally (add your blocks), then PUT the full config back
curl -X PUT https://trips.api.moguplatform.com/trips/12345/config \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d @updated-config.json

Important: PUT replaces the entire config. Always preserve existing blocks you don't intend to change.

Block types

Core block types you will commonly use:

  • itinerary (container) with itineraryDay children
  • Service blocks: accomodation, transport, train, cruise, info, activity, flight, food
  • Content blocks: text, title, box, dropdown
  • Media blocks: map, video, file, gallery
  • Layout: pageBreak, group
  • Other: servicesSummary, price, form, practicalInfo

Required content fields by block type

Block typeRequired fields in content
texttext (HTML string)
titletitle
accomodationtitle
activitytitle
flightflightNumber, departureDateTime, arrivalDateTime, departureAirport, arrivalAirport, airline, elapsedTime
traintitle, location (departure), arrivalLocation
transporttitle
cruisetitle
foodtitle
infotitle
boxcolor (CSS hex)
dropdowntitle
mapmapUrl (Google Maps URL)
videovideoUrl (YouTube/Vimeo)
filefileName, fileUrl
gallery(none — just images array)
itinerary(none — uses children for days)
itineraryDay(none — uses children for day content)
pricetitle, price, currency
servicesSummary(none)
practicalInfoselectedCountry, selectedLanguage, flag
pageBreak(no content needed)
form(see FormContent schema)
group(no content fields)

Some block content fields (e.g. text.content.text, itineraryDay.content.description) accept HTML-formatted rich text. See sanitization rules below.

HTML sanitization (important)

Rich text fields (e.g. text.content.text, itineraryDay.content.description, service block description, servicesSummary.includedText/notIncludedText) are sanitized server-side before rendering. Anything outside the allowlist below is silently stripped — it will not be stored or shown.

Allowed tags:

p, h1, h2, h3, h4, h5, h6,
ul, ol, li, strong, b, em, i, u, s,
a, br, hr, blockquote, pre, code,
table, thead, tbody, tr, td, th,
span, div, sub, sup

Allowed attributes:

AttributeNotes
hrefOn <a> links.
class
styleOnly text-align is kept; all other declarations are removed.
targetOn <a>. Links with target="_blank" get rel="noopener noreferrer" added automatically.
rel
colspan, rowspanOn table cells.

Not allowed:

  • <img> tags (no inline images / tracking pixels) — use a gallery, accomodation/activity etc. images array, or a map/video block instead.
  • data-* attributes.
  • Any tag or attribute not listed above.

Trips

Trips are the main entities in the MOGU platform. A trip represents a travel itinerary with all its details, configurations, and associated travelers.

Key operations:

  • Create and manage trips
  • Configure trip settings and branding
  • Manage trip visibility (public/private)
  • Track trip visualizations

Catalogs

Catalogs are collections of trips that can be organized and shared. They allow you to group related trips for easier management and presentation.

Key operations:

  • Create and organize catalogs
  • Add/remove trips from catalogs
  • Make catalogs publicly accessible
  • Filter and search catalog trips

API Features

Versioning

The API is currently at version 1.0.0. The version is specified in the OpenAPI specification and can be verified through the /status endpoint.

Current version: 1.0.0

We follow semantic versioning (SemVer) principles:

  • Major version changes indicate breaking changes
  • Minor version changes add functionality in a backward-compatible manner
  • Patch version changes are for backward-compatible bug fixes

When breaking changes are introduced, we will communicate them in advance through our changelog and support channels.

List endpoints support pagination to handle large datasets efficiently:

GET /trips?page=1&pageSize=20

Parameters:

  • page: Page number (starts at 1)
  • pageSize: Number of items per page (default: 20, max: 100)

Response format:

{
  "data": [...],
  "page": 1,
  "pageSize": 20,
  "totalCount": 150,
  "totalPages": 8
}

Filtering

Apply filters to narrow down results using JSON syntax:

GET /trips?filters=[{"field":"duration","operator":"gte","value":5}]

Supported operators:

  • eq: Equal to
  • neq: Not equal to
  • gt: Greater than
  • gte: Greater than or equal to
  • lt: Less than
  • lte: Less than or equal to
  • in: In array
  • contains: Contains value

Searching

Search across multiple fields using JSON syntax:

GET /trips?search={"fields":["title","code"],"term":"Paris"}

Sorting

Sort results by any field:

GET /trips?orderBy={"field":"createdAt","direction":"desc"}

Directions:

  • asc: Ascending order
  • desc: Descending order

Error Handling

The API uses standard HTTP status codes to indicate success or failure:

Success Codes

  • 200 OK: Request succeeded
  • 201 Created: Resource created successfully

Client Error Codes

  • 400 Bad Request: Invalid request parameters or body
  • 401 Unauthorized: Missing or invalid authentication token
  • 403 Forbidden: Insufficient permissions
  • 404 Not Found: Resource not found
  • 429 Too Many Requests: Rate limit exceeded

Server Error Codes

  • 500 Internal Server Error: Server-side error
  • 503 Service Unavailable: Service temporarily unavailable

Error Response Format

{
  "errors": {
    "title": ["Title is required"],
    "duration": ["Duration must be a positive number"]
  }
}

Support

Need help? We're here for you!

Terms of Service

By using the MOGU Public API, you agree to our Terms of Service and Privacy Policy.


Ready to get started? Check out our API Reference for detailed endpoint documentation.