Sync records with the public API
Upsert accounts and contacts, write custom field values, manage field definitions, and log idempotent activities through the GTM Engine public API.
The public API lets you keep GTM Engine in sync with another system, such as your product database, a data warehouse, or an internal tool. Every write in this guide is idempotent, so it is safe to retry a request or re-run a full sync.
All requests go to https://api.gtmengine.ai and need an API key in the X-Api-Key header. Write endpoints require a key with read_write access. See the API reference for full request and response schemas. From TypeScript or JavaScript, the @gtmengine/sdk client wraps every call in this guide.
1. Define custom fields
Custom fields must exist before you can write values to them. Use PUT /v1/fields/{record_type}/{key} to create or update a field definition. The path identifies the field, so sending the same definition again is a no-op. The response is 201 with created: true for a new field and 200 with created: false for an existing one.
curl -X PUT https://api.gtmengine.ai/v1/fields/contacts/contact_type \
-H "X-Api-Key: $GTM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Contact Type",
"data_type": "enum",
"enum_values": ["User", "Offboarded"]
}'data_typeis one oftext,numeric,boolean,date,timestamp with time zone,enum, ormulti-select.- For
enumandmulti-selectfields, upserting merges new options into the existing list. Existing options are never removed, so values already stored on records stay valid. - The
keyis the stable machine name you use incustom_fieldspayloads. It must be lowercase letters, digits, and underscores.
List the fields that exist with GET /v1/fields?record_type=contacts. Add custom_only=true to skip built-in fields.
2. Upsert accounts
POST /v1/records/accounts/upsert matches an existing account by domain. If one exists, it is updated. If not, it is created. The response includes created: true | false.
curl -X POST https://api.gtmengine.ai/v1/records/accounts/upsert \
-H "X-Api-Key: $GTM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_name": "Acme",
"domain": "acme.com",
"relationship_type": "customer",
"custom_fields": {
"crm_integration": "Hubspot",
"onboarding_checklist": ["CRM Connected", "Team Invited"]
}
}'3. Upsert contacts and link them to accounts
POST /v1/records/contacts/upsert matches an existing contact by email. Pass account_ids to link the contact to one or more accounts. Links are added, never removed.
curl -X POST https://api.gtmengine.ai/v1/records/contacts/upsert \
-H "X-Api-Key: $GTM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@acme.com",
"first_name": "Jane",
"last_name": "Doe",
"account_ids": ["<account id from step 2>"],
"custom_fields": { "contact_type": "User" }
}'account_ids is also accepted on POST /v1/records/contacts and PATCH /v1/records/contacts/{id}.
Custom field values
custom_fields works the same way on account, contact, and opportunity create and update (plus account and contact upsert). GET responses return the stored values.
- Keys must match a field defined for that record type. Unknown keys return
400. enumvalues must be one of the field's options. Matching is case-insensitive, and the stored value uses the option's canonical casing.multi-selectvalues are arrays of options. Sending an array replaces the stored selection.booleanvalues must betrueorfalse, andnumericvalues must be numbers.- Send
nullto clear a value.
Writes through the API trigger the same CRM push and workflow triggers as edits made in the app.
4. Log activities idempotently
POST /v1/activities records an activity on contacts and opportunities. Pass an external_id from your system to make the call idempotent: if an activity with that external_id already exists, it is returned unchanged with created: false, and any new contact_ids or opportunity_ids are added.
curl -X POST https://api.gtmengine.ai/v1/activities \
-H "X-Api-Key: $GTM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "session:jane@acme.com:2026-10-04T18:00:00Z",
"content": "Started a session",
"tags": ["session"],
"date_time": "2026-10-04T18:00:00Z",
"contact_ids": ["<contact id from step 3>"]
}'interaction_type defaults to custom. Use tags to label your own event types. New activities fire Activity created workflow triggers.
Manage opportunity contacts
Associate the right people with an opportunity and use contact signals to understand buyer roles and engagement.
Use the TypeScript SDK
Install @gtmengine/sdk to upsert accounts and contacts, manage custom fields, and log idempotent activities from your own TypeScript or JavaScript code.