Reusable Field Mappings API
A field mapping is authored on five surfaces — Reverse ETL syncs, syncs (audience), orchestration send tiles, Store feeds and event-forwarding rules — and each stores its own copy. These endpoints back the mapping library: named, reusable mappings plus a single lookup that returns every mapping that could be loaded into a given place, already checked against it.
See Field Mapping → Reusing an Existing Mapping for the product behaviour.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/field-mapping-library | Mappings that can be loaded into a given context, each with its verdict |
GET | /api/v1/workspaces/{id}/field-mapping-presets | List saved mappings |
POST | /api/v1/workspaces/{id}/field-mapping-presets | Save a mapping |
GET | /api/v1/workspaces/{id}/field-mapping-presets/{presetId} | Get a saved mapping |
PUT | /api/v1/workspaces/{id}/field-mapping-presets/{presetId} | Update a saved mapping |
DELETE | /api/v1/workspaces/{id}/field-mapping-presets/{presetId} | Delete a saved mapping |
Permissions: field_mapping_presets.read for the library and the reads, field_mapping_presets.write for the writes. The library additionally filters each origin by that origin’s own read permission — syncs.read, audience_syncs.read, journeys.read, stores.read, events.forwarding.read — so it never reveals a configuration you could not open directly.
Get the Mapping Library
GET /api/v1/workspaces/{id}/field-mapping-library
Returns every reusable mapping that could be loaded into the described context — saved mappings and the mappings already configured on other objects — each resolved against that context and carrying the adapted entry list. No second call is needed to apply one.
Query parameters
| Parameter | Required | Description |
|---|---|---|
surface | yes | Where the mapping would be pasted: model_sync, audience_sync, journey_send, store_feed, event_forwarding |
destination_type | yes, except store_feed | The destination type whose field vocabulary applies |
model_id | The model whose columns are in scope | |
parent_model_id | Anchor for related-column paths; defaults to model_id | |
store_id | Store feeds only — enables the field-ownership check | |
exclude_id | The object being edited, so it is never offered its own mapping | |
exclude_tile_id | Narrows exclude_id to one orchestration send tile, keeping the rest of that journey’s tiles available |
Response
{
"candidates": [
{
"origin": "preset",
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Meta CAPI standard identifiers",
"description": "Email, phone and external id, all pre-hashed",
"destination_type": "facebook_ads",
"source_model_id": "660e8400-e29b-41d4-a716-446655440000",
"source_model_name": "Customers",
"updated_at": "2026-08-10T11:04:00Z",
"entry_count": 3,
"same_model": true,
"verdict": "applies",
"mapping": [
{"source_column": "email", "destination_field": "email", "hash_pii": true},
{"source_column": "phone", "destination_field": "phone", "hash_pii": true},
{"source_column": "customer_id", "destination_field": "external_id"}
]
},
{
"origin": "audience_sync",
"id": "770e8400-e29b-41d4-a716-446655440000",
"name": "VIP members to Meta",
"destination_type": "facebook_ads",
"destination_name": "Meta Ads — Growth",
"source_model_id": "880e8400-e29b-41d4-a716-446655440000",
"source_model_name": "Loyalty members",
"updated_at": "2026-08-02T08:15:00Z",
"entry_count": 4,
"same_model": false,
"verdict": "partial",
"mapping": [
{"source_column": "email", "destination_field": "email", "hash_pii": true}
],
"issues": [
{
"index": 1,
"source_column": "customer_tier",
"destination_field": "tier",
"code": "unknown_source_column",
"severity": "error",
"message": "\"customer_tier\" is not on this model"
}
]
}
],
"truncated": false,
"context": {
"surface": "audience_sync",
"source_kind": "model",
"destination_type": "facebook_ads",
"model_id": "660e8400-e29b-41d4-a716-446655440000",
"parent_model_id": "660e8400-e29b-41d4-a716-446655440000"
}
}mapping is the adapted list — entries that could not be used here have already been removed, related-column aliases recomputed, and trait references rebound. Write it straight into the target object’s field_mapping.
Verdicts
verdict | Meaning |
|---|---|
applies | Every entry survived. Warnings are still possible. |
partial | Some entries survived; the rest are listed in issues. |
blocked | Nothing survived, or the source mapping was empty. Still returned, so a caller can explain why rather than appear to lose it. |
Issue codes
code | severity | Meaning |
|---|---|---|
missing_destination_field | error | The saved row names no destination field. |
unknown_source_column | error | A referenced source column is not on the target model. A multi-input transform is dropped whole if any input is missing. |
blocked_source_column | error | The column exists but the model marks it blocked, so a sync strips it at run time — the row would save and then deliver nothing. |
unknown_trait | error | The trait is neither present by id nor matchable by name on the target model. |
trait_rebound | warning | The trait was matched by name; trait_ids was rewritten to the local id. |
related_unsupported | error | The entry uses a related-model column, which this surface rejects. |
related_path_invalid | error | The relationship path does not resolve from this parent model. |
field_owned_by_other_feed | error | Store feeds only — another feed already owns that Store field. |
unknown_destination_field | warning | The destination type does not document that field. Kept, since many destinations accept custom attributes. |
duplicate_destination_field | error | An earlier surviving entry already claimed that destination field and path. |
index refers to the entry’s position in the original mapping, so a caller can point at a row that is absent from mapping.
truncated is true when the 200-candidate cap was reached; narrow with model_id or fall back to listing presets directly.
Example
curl -X GET "https://composable.zeotap.com/api/v1/workspaces/{id}/field-mapping-library?surface=audience_sync&destination_type=facebook_ads&model_id={modelId}" \
-H "Authorization: Bearer <token>" \
-H "X-Workspace-ID: <workspace-id>"List Saved Mappings
GET /api/v1/workspaces/{id}/field-mapping-presets
Returns every saved mapping in the workspace, most recently updated first. Unlike the library, this applies no compatibility filter and computes no verdicts.
Response
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"workspace_id": "660e8400-e29b-41d4-a716-446655440000",
"name": "Meta CAPI standard identifiers",
"description": "Email, phone and external id, all pre-hashed",
"destination_type": "facebook_ads",
"source_kind": "model",
"source_model_id": "770e8400-e29b-41d4-a716-446655440000",
"source_model_name": "Customers",
"mapping": [
{"source_column": "email", "destination_field": "email", "hash_pii": true}
],
"created_by": "880e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-08-10T11:04:00Z",
"updated_at": "2026-08-10T11:04:00Z"
}
]Save a Mapping
POST /api/v1/workspaces/{id}/field-mapping-presets
Request
{
"name": "Meta CAPI standard identifiers",
"description": "Email, phone and external id, all pre-hashed",
"surface": "audience_sync",
"destination_type": "facebook_ads",
"source_model_id": "770e8400-e29b-41d4-a716-446655440000",
"mapping": [
{"source_column": "email", "destination_field": "email", "hash_pii": true},
{"source_column": "customer_id", "destination_field": "external_id"}
]
}| Field | Required | Description |
|---|---|---|
name | yes | Unique within the workspace |
description | Free text | |
surface | yes | The surface the mapping was authored on: model_sync, audience_sync, journey_send, store_feed, event_forwarding. The server derives source_kind from it — and clears destination_type for store_feed. Deriving the kind client-side could store a preset with one the pickers filter on exactly, making it invisible forever with no save error |
destination_type | The destination type this mapping targets. Ignored for store_feed | |
source_model_id | The model the source columns were written against. Used for display and to rank exact-model matches first | |
mapping | yes | The mapping array. Rows with no destination field are dropped; related-column aliases are normalized. A mapping that reduces to nothing is rejected |
Returns 201 with the created object.
Get, Update, Delete
GET, PUT and DELETE on /api/v1/workspaces/{id}/field-mapping-presets/{presetId}.
PUT accepts name, description, source_model_id and mapping. destination_type and source_kind are not updatable: changing either would re-point the mapping at a different field vocabulary while keeping rows written for the old one. Save under a new name instead.
DELETE returns 204. Deleting a saved mapping never affects the syncs, tiles or feeds built from it — applying a mapping copies it.
Example
curl -X DELETE https://composable.zeotap.com/api/v1/workspaces/{id}/field-mapping-presets/{presetId} \
-H "Authorization: Bearer <token>" \
-H "X-Workspace-ID: <workspace-id>"