Guides

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_type is one of text, numeric, boolean, date, timestamp with time zone, enum, or multi-select.
  • For enum and multi-select fields, upserting merges new options into the existing list. Existing options are never removed, so values already stored on records stay valid.
  • The key is the stable machine name you use in custom_fields payloads. 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"]
    }
  }'

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.
  • enum values must be one of the field's options. Matching is case-insensitive, and the stored value uses the option's canonical casing.
  • multi-select values are arrays of options. Sending an array replaces the stored selection.
  • boolean values must be true or false, and numeric values must be numbers.
  • Send null to 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.

On this page