Skip to content

Contact fields ​

Add your own typed fields to contacts — a fee balance, a due date, a class, a district — and use them in workflows: in message templates, in conditions, and to trigger a workflow on a date.

Field definitions are managed over the API with the endpoints below. Field values on a contact are set from the dashboard (the contact form and file imports); see Setting values.

All paths are under https://api.msgine.net/api/v1.

MethodPathScopePurpose
GET/developers/contacts/fieldsREAD_CONTACTS or MANAGE_CONTACTSList your fields
POST/developers/contacts/fieldsMANAGE_CONTACTSCreate a field
PATCH/developers/contacts/fields/:idMANAGE_CONTACTSChange a field's label or choices
DELETE/developers/contacts/fields/:idMANAGE_CONTACTSDelete a field

FULL_ACCESS grants every scope. :id is the field's UUID.

Field types ​

TypeHoldsAccepted input
textA string, up to 500 charactersAny text (surrounding spaces are trimmed)
numberA number85000, "85000", "85,000"
dateA calendar dateYYYY-MM-DD, or DD/MM/YYYY (see below)
booleantrue or falsetrue, false, or the text yes, y, 1, no, n, 0 (any case)
choiceOne of the field's optionsExactly one of the options

Values are stored typed, so a number is a number and a date is YYYY-MM-DD wherever you read them back.

Dates ​

Dates are stored as YYYY-MM-DD. A date typed as 15/01/2027 is read day first (15 January), never month first. . and - also work as separators (15.01.2027, 15-01-2027). Impossible dates such as 31/02/2027 are rejected.

Create a field ​

bash
curl -X POST https://api.msgine.net/api/v1/developers/contacts/fields \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "due_date",
    "label": "Due date",
    "type": "date"
  }'
ParameterTypeRequiredDescription
keystringYeslower_snake_case, starting with a letter, up to 40 characters (^[a-z][a-z0-9_]{0,39}$)
labelstringYesDisplay name, 1 to 80 characters
typestringYestext, number, date, boolean or choice
optionsstring[]For choice1 to 50 choices, each 1 to 80 characters. Duplicates and blanks are removed. Ignored for other types

Status Code: 201 Created

json
{
  "id": "b3d2a1f0-9c8e-4d7b-a6f5-1e2d3c4b5a69",
  "userId": "0f6c1a6e-7a3e-4b8e-8a43-0d3f2c9b1e22",
  "key": "due_date",
  "label": "Due date",
  "type": "date",
  "options": null,
  "createdAt": "2026-11-03T08:00:00.000Z",
  "updatedAt": "2026-11-03T08:00:00.000Z"
}

A choice field:

bash
curl -X POST https://api.msgine.net/api/v1/developers/contacts/fields \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "district",
    "label": "District",
    "type": "choice",
    "options": ["Kampala", "Wakiso", "Mukono"]
  }'

Errors:

StatusWhen
400The body fails validation (bad key, missing label, unknown property, ...); a choice field has no options or more than 50; or you already have 50 fields
403The key lacks MANAGE_CONTACTS
409A field with this key already exists

Key and type are fixed ​

A field's key and type cannot be changed once created. Workflows read values as contact.fields.<key> and stored values depend on the type, so neither can be renamed or converted. To change either, create a new field and delete the old one. You can have 50 fields per account.

List fields ​

bash
curl https://api.msgine.net/api/v1/developers/contacts/fields \
  -H "X-Api-Key: YOUR_API_KEY"

Returns an array of fields, ordered by label, in the shape shown above. It is not paginated.

Update a field ​

bash
curl -X PATCH https://api.msgine.net/api/v1/developers/contacts/fields/FIELD_ID \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Rent due date" }'
ParameterTypeRequiredDescription
labelstringNo1 to 80 characters
optionsstring[]NoReplaces the choices of a choice field (1 to 50). Ignored for other types

Returns the updated field. 404 if the field is not yours.

Delete a field ​

bash
curl -X DELETE https://api.msgine.net/api/v1/developers/contacts/fields/FIELD_ID \
  -H "X-Api-Key: YOUR_API_KEY"

Returns 204. Deleting a definition also removes its value from every contact, so workflows that read the field will no longer find it. Update such workflows first.

Setting values ​

Values live on each contact under fields, keyed by the field's key:

json
{
  "name": "Amina",
  "phone": "+256701234567",
  "fields": { "due_date": "2027-01-15", "balance": 150000, "district": "Kampala" }
}

Contact records themselves are managed from the dashboard, which is where values are set today. The contact create and edit forms, and the file import on the Contacts page, check every value against your field definitions before saving:

  • A field key that is not one of your fields is rejected.
  • A value that does not fit its type is rejected: a number that is not a number, a date that is not a date, a choice that is not one of the options.
  • Sending null (or an empty string) for a field removes its value.
  • A rejected contact write returns 400 listing every problem at once:
json
{
  "statusCode": 400,
  "error": "validation_error",
  "message": "Invalid contact fields",
  "errors": ["\"Due date\" must be a date (YYYY-MM-DD or DD/MM/YYYY)"]
}

Importing from CSV or Excel ​

When you import a file, columns other than name, phone and email are matched to a field when the column header equals the field's key or label, ignoring case. Columns that match no field are ignored. Text such as 85,000, yes and 15/01/2027 is converted to the field's type.

A row with a value that does not fit its field is reported in the import's failed list with the reason, and the other rows still import. Rows for phone numbers you already have as contacts are counted as alreadyExisted and are not changed by the import.

Using fields in workflows ​

  • In templates and expressions: {{ contact.fields.due_date }}, contact.fields.balance > 100000. See Expressions.
  • As a schedule: a trigger.date step runs for each contact whose date field falls on the day you choose, for example 3 days before due_date. Publishing fails unless the field exists and has type date. See trigger.date.
  • When a contact's field values change, MsGine records a contact.updatedplatform event whose changed array lists the keys that changed, so a workflow can react to, say, a new balance: "balance" in trigger.changed. Saving identical values records nothing.
json
{
  "contactId": "6a1f…",
  "name": "Amina",
  "phone": "+256701234567",
  "email": "amina@example.com",
  "fields": { "balance": 150000, "due_date": "2027-01-15" },
  "changed": ["balance"]
}

Released under the MIT License.