Field Mapping

How raw API responses are parsed and mapped to canonical fields.

Overview

Every integration endpoint has a parser_config that defines how to extract and transform data from your API response into the canonical data model. This configuration is set up during integration onboarding and stored as JSONB in the database.

Parser Configuration Structure

A parser config has five top-level sections. Only output_type and field_map are required.

parser_config structure
{
  "output_type": "reservation",
  "root_path": "reservations",
  "field_map": { ... },
  "value_maps": { ... },
  "nested": { ... },
  "post_process": { ... }
}
SectionRequiredDescription
output_typeYesThe canonical entity type being produced (e.g., "reservation")
root_pathNoJSON path to the data array in the response
field_mapYesMaps source fields to canonical field names (with optional transforms)
value_mapsNoCode translation tables for enumerated values
nestedNoConfiguration for parsing nested arrays (stays, guests, charges)
post_processNoPost-processing operations (flatten, deduplicate)

Root Path

The root_path tells the parser where to find the array of records in your API response.

wrapped response
// Your API returns:
{ "reservations": [ { ... }, { ... } ] }

// Set root_path to extract the array:
"root_path": "reservations"
deeply nested response
// Your API returns:
{ "data": { "results": { "reservations": [...] } } }

// Use dot notation:
"root_path": "data.results.reservations"
direct array response
// Your API returns the array directly:
[ { ... }, { ... } ]

// Omit root_path or set to null:
"root_path": null

Field Map

The field_map maps your source field names to canonical field names. Each key is the canonical field name; each value is either a simple string (direct rename) or an object (transform).

Simple String Mapping

The most common case: rename a source field to its canonical equivalent.

direct field rename
{
  "field_map": {
    "reservation_id": "booking_number",
    "check_in": "arrival_date",
    "check_out": "departure_date",
    "status": "booking_status"
  }
}

Key = canonical name, Value = source name. The parser reads booking_number from your response and writes it as reservation_id in the output.

Transform Types

When a simple rename is not enough, use a transform object. The _type field determines which transform is applied.

Split

Split a string into parts using a delimiter. Use index to select which part to keep.

split — "Doe, John" into first_name and last_name
{
  "first_name": {
    "_type": "split",
    "source": "guest_name",
    "delimiter": ", ",
    "index": 1,
    "trim": true
  },
  "last_name": {
    "_type": "split",
    "source": "guest_name",
    "delimiter": ", ",
    "index": 0,
    "trim": true
  }
}

Use delimiters (array) instead of delimiter to try multiple delimiters in order. The first match wins. Example: "delimiters": [", ", " / "]

Boolean

Convert varied source values into consistent true/false booleans.

boolean — normalize marketing consent
{
  "marketing_consent": {
    "_type": "boolean",
    "source": "accepts_marketing",
    "truthy_values": ["Y", "yes", "1", true],
    "falsy_values": ["N", "no", "0", false],
    "default": false
  }
}

Template

Build a string from multiple source fields using template syntax.

template — combine name fields
{
  "full_name": {
    "_type": "template",
    "template": "${first_name} ${last_name}",
    "fields": ["first_name", "last_name"]
  }
}

Concat

Join multiple source fields with a separator.

concat — build address from parts
{
  "address": {
    "_type": "concat",
    "sources": ["street", "city", "postal_code", "country"],
    "separator": ", "
  }
}

Coalesce

Take the first non-null value from a list of source fields. Useful when different systems store the same data in different fields.

coalesce — find the best phone number
{
  "phone": {
    "_type": "coalesce",
    "sources": ["mobile_phone", "home_phone", "work_phone"]
  }
}

Transform Summary

TypeUse CaseKey Fields
splitBreak a combined string into partssource, delimiter, index
booleanNormalize varied values to true/falsesource, truthy_values, falsy_values
templateCompose a string from multiple fieldstemplate, fields
concatJoin multiple fields with a separatorsources, separator
coalesceFirst non-null from multiple sourcessources

Value Maps

Value maps translate source system codes into canonical values. They are defined per field and applied after field mapping.

value_maps
{
  "value_maps": {
    "status": {
      "1": "confirmed",
      "2": "checked_in",
      "3": "checked_out",
      "4": "cancelled"
    },
    "document_type": {
      "P": "passport",
      "D": "national_id",
      "C": "drivers_license",
      "X": "other"
    },
    "board_type": {
      "AD": "RO",
      "BB": "BB",
      "HB": "HB",
      "FB": "FB",
      "TI": "AI"
    }
  }
}

Both keys and values are coerced to strings for matching. This means numeric source values like 1 will match the string key "1". If no match is found, the original value is preserved.

Nested Arrays

When your API response contains nested arrays (rooms within a reservation, guests within a room), use the nested section to configure parsing for each level.

nested configuration
{
  "nested": {
    "stays": {
      "source_path": "rooms",
      "field_map": {
        "room_type": "room_description",
        "board_type": "meal_plan",
        "adults": "num_adults",
        "children": "num_children"
      },
      "nested": {
        "guests": {
          "source_path": "occupants",
          "field_map": {
            "first_name": "name",
            "last_name": "surname",
            "email": "email_address"
          }
        }
      }
    }
  }
}

Each nested block has its own source_path (the field name in the source data containing the array), field_map, and optionally further nested blocks for deeper levels.

Nested TypeDescriptionCan Nest Further
staysRoom assignment recordsYes (guests within stays)
guestsGuest recordsNo
roomsRoom detail recordsNo
chargesCharge / extra recordsNo

Post-Processing

After field mapping and value translation, optional post-processing steps clean up the output data.

post_process
{
  "post_process": {
    "flatten_guests": true,
    "dedupe_guests_by": "document_number"
  }
}
OptionTypeDescription
flatten_guestsbooleanCollect guests from all stays into a single guests[] array on the reservation
dedupe_guests_bystringRemove duplicate guests by the specified field value (e.g., same document number)

When to use flatten_guests: Some PMS systems nest guests inside stays (rooms). If each room has its own guest list, enabling flatten_guests merges them into a single top-level guests[] array. Combine with dedupe_guests_by to remove duplicates when the same guest appears in multiple rooms.

Complete Example

This example shows a full parser configuration for a hotel PMS that returns reservations with nested room and guest data, numeric status codes, and combined name fields.

source API response
{
  "data": {
    "bookings": [
      {
        "booking_ref": "BK-9921",
        "confirm_no": "CN-44812",
        "arrival": "2026-04-10",
        "departure": "2026-04-13",
        "created": "2026-03-01 14:22:00",
        "state": 1,
        "sales_channel": "OTA",
        "total": 420.00,
        "net_total": 399.00,
        "curr": "EUR",
        "rooms": [
          {
            "type_desc": "Superior Double",
            "meal": "HB",
            "rate": "FLEX",
            "pax_adults": 2,
            "pax_children": 1,
            "pax_infants": 0,
            "occupants": [
              {
                "full_name": "Garcia, Maria",
                "email_addr": "maria@example.com",
                "mobile": "+34612345678",
                "birth_date": "1985-06-12",
                "country": "ES",
                "doc_type": "D",
                "doc_no": "12345678A",
                "mkt_consent": "Y"
              }
            ]
          }
        ],
        "charges": [
          {
            "svc_code": "SPA-001",
            "svc_name": "Spa Treatment",
            "svc_amount": 85.00,
            "svc_qty": 1
          }
        ]
      }
    ]
  }
}
parser_config
{
  "output_type": "reservation",
  "root_path": "data.bookings",
  "field_map": {
    "reservation_id": "booking_ref",
    "confirmation_number": "confirm_no",
    "check_in": "arrival",
    "check_out": "departure",
    "external_created_at": "created",
    "status": "state",
    "channel": "sales_channel",
    "amount": "total",
    "amount_net": "net_total",
    "currency": "curr"
  },
  "value_maps": {
    "status": {
      "1": "confirmed",
      "2": "checked_in",
      "3": "checked_out",
      "4": "cancelled",
      "5": "no_show"
    },
    "document_type": {
      "P": "passport",
      "D": "national_id",
      "N": "national_id",
      "C": "drivers_license",
      "X": "other"
    }
  },
  "nested": {
    "stays": {
      "source_path": "rooms",
      "field_map": {
        "room_type": "type_desc",
        "board_type": "meal",
        "rate_code": "rate",
        "adults": "pax_adults",
        "children": "pax_children",
        "babies": "pax_infants"
      },
      "nested": {
        "guests": {
          "source_path": "occupants",
          "field_map": {
            "first_name": {
              "_type": "split",
              "source": "full_name",
              "delimiter": ", ",
              "index": 1,
              "trim": true
            },
            "last_name": {
              "_type": "split",
              "source": "full_name",
              "delimiter": ", ",
              "index": 0,
              "trim": true
            },
            "email": "email_addr",
            "phone": "mobile",
            "date_of_birth": "birth_date",
            "nationality": "country",
            "document_type": "doc_type",
            "document_number": "doc_no",
            "marketing_consent": {
              "_type": "boolean",
              "source": "mkt_consent",
              "truthy_values": ["Y", "yes", "1", true],
              "falsy_values": ["N", "no", "0", false],
              "default": false
            }
          }
        }
      }
    },
    "charges": {
      "source_path": "charges",
      "field_map": {
        "code": "svc_code",
        "name": "svc_name",
        "amount": "svc_amount",
        "quantity": "svc_qty"
      }
    }
  },
  "post_process": {
    "flatten_guests": true,
    "dedupe_guests_by": "document_number"
  }
}

After parsing, each record in the output conforms to the canonical data model and is ready for normalization, transformation, and delivery.