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.
{
"output_type": "reservation",
"root_path": "reservations",
"field_map": { ... },
"value_maps": { ... },
"nested": { ... },
"post_process": { ... }
}| Section | Required | Description |
|---|---|---|
output_type | Yes | The canonical entity type being produced (e.g., "reservation") |
root_path | No | JSON path to the data array in the response |
field_map | Yes | Maps source fields to canonical field names (with optional transforms) |
value_maps | No | Code translation tables for enumerated values |
nested | No | Configuration for parsing nested arrays (stays, guests, charges) |
post_process | No | Post-processing operations (flatten, deduplicate) |
Root Path
The root_path tells the parser where to find the array of records in your API response.
// Your API returns:
{ "reservations": [ { ... }, { ... } ] }
// Set root_path to extract the array:
"root_path": "reservations"// Your API returns:
{ "data": { "results": { "reservations": [...] } } }
// Use dot notation:
"root_path": "data.results.reservations"// Your API returns the array directly:
[ { ... }, { ... } ]
// Omit root_path or set to null:
"root_path": nullField 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.
{
"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.
{
"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.
{
"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.
{
"full_name": {
"_type": "template",
"template": "${first_name} ${last_name}",
"fields": ["first_name", "last_name"]
}
}Concat
Join multiple source fields with a separator.
{
"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.
{
"phone": {
"_type": "coalesce",
"sources": ["mobile_phone", "home_phone", "work_phone"]
}
}Transform Summary
| Type | Use Case | Key Fields |
|---|---|---|
split | Break a combined string into parts | source, delimiter, index |
boolean | Normalize varied values to true/false | source, truthy_values, falsy_values |
template | Compose a string from multiple fields | template, fields |
concat | Join multiple fields with a separator | sources, separator |
coalesce | First non-null from multiple sources | sources |
Value Maps
Value maps translate source system codes into canonical values. They are defined per field and applied after field mapping.
{
"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": {
"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 Type | Description | Can Nest Further |
|---|---|---|
stays | Room assignment records | Yes (guests within stays) |
guests | Guest records | No |
rooms | Room detail records | No |
charges | Charge / extra records | No |
Post-Processing
After field mapping and value translation, optional post-processing steps clean up the output data.
{
"post_process": {
"flatten_guests": true,
"dedupe_guests_by": "document_number"
}
}| Option | Type | Description |
|---|---|---|
flatten_guests | boolean | Collect guests from all stays into a single guests[] array on the reservation |
dedupe_guests_by | string | Remove 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.
{
"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
}
]
}
]
}
}{
"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.