# 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](https://help.moguplatform.com/en/articles/12868814-how-to-generate-an-api-key).

### 2. Make Your First Request

Here's a simple example to list your trips:

```bash
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](#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.


### How to build a trip (recommended workflow)

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:

```json
{
  "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

```json
{
  "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

```json
{
  "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).

```json
{
  "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:

```bash
# 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 type | Required fields in `content` |
|  --- | --- |
| `text` | `text` (HTML string) |
| `title` | `title` |
| `accomodation` | `title` |
| `activity` | `title` |
| `flight` | `flightNumber`, `departureDateTime`, `arrivalDateTime`, `departureAirport`, `arrivalAirport`, `airline`, `elapsedTime` |
| `train` | `title`, `location` (departure), `arrivalLocation` |
| `transport` | `title` |
| `cruise` | `title` |
| `food` | `title` |
| `info` | `title` |
| `box` | `color` (CSS hex) |
| `dropdown` | `title` |
| `map` | `mapUrl` (Google Maps URL) |
| `video` | `videoUrl` (YouTube/Vimeo) |
| `file` | `fileName`, `fileUrl` |
| `gallery` | (none — just `images` array) |
| `itinerary` | (none — uses `children` for days) |
| `itineraryDay` | (none — uses `children` for day content) |
| `price` | `title`, `price`, `currency` |
| `servicesSummary` | (none) |
| `practicalInfo` | `selectedCountry`, `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:**

| Attribute | Notes |
|  --- | --- |
| `href` | On `<a>` links. |
| `class` |  |
| `style` | **Only `text-align`** is kept; all other declarations are removed. |
| `target` | On `<a>`. Links with `target="_blank"` get `rel="noopener noreferrer"` added automatically. |
| `rel` |  |
| `colspan`, `rowspan` | On 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.

### Pagination

List endpoints support pagination to handle large datasets efficiently:

```bash
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:**

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

### Filtering

Apply filters to narrow down results using JSON syntax:

```bash
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:

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

### Sorting

Sort results by any field:

```bash
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

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

## Support

Need help? We're here for you!

- **Email**: [support@moguplatform.com](mailto:support@moguplatform.com)
- **Help Center**: [https://help.moguplatform.com](https://help.moguplatform.com)


## Terms of Service

By using the MOGU Public API, you agree to our [Terms of Service](https://moguplatform.com/terms) and [Privacy Policy](https://moguplatform.com/privacy).

**Ready to get started?** Check out our [API Reference](/openapi) for detailed endpoint documentation.