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.
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET | /developers/contacts/fields | READ_CONTACTS or MANAGE_CONTACTS | List your fields |
POST | /developers/contacts/fields | MANAGE_CONTACTS | Create a field |
PATCH | /developers/contacts/fields/:id | MANAGE_CONTACTS | Change a field's label or choices |
DELETE | /developers/contacts/fields/:id | MANAGE_CONTACTS | Delete a field |
FULL_ACCESS grants every scope. :id is the field's UUID.
Field types
| Type | Holds | Accepted input |
|---|---|---|
text | A string, up to 500 characters | Any text (surrounding spaces are trimmed) |
number | A number | 85000, "85000", "85,000" |
date | A calendar date | YYYY-MM-DD, or DD/MM/YYYY (see below) |
boolean | true or false | true, false, or the text yes, y, 1, no, n, 0 (any case) |
choice | One of the field's options | Exactly 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
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"
}'| Parameter | Type | Required | Description |
|---|---|---|---|
key | string | Yes | lower_snake_case, starting with a letter, up to 40 characters (^[a-z][a-z0-9_]{0,39}$) |
label | string | Yes | Display name, 1 to 80 characters |
type | string | Yes | text, number, date, boolean or choice |
options | string[] | For choice | 1 to 50 choices, each 1 to 80 characters. Duplicates and blanks are removed. Ignored for other types |
Status Code: 201 Created
{
"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:
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:
| Status | When |
|---|---|
400 | The 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 |
403 | The key lacks MANAGE_CONTACTS |
409 | A 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
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
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" }'| Parameter | Type | Required | Description |
|---|---|---|---|
label | string | No | 1 to 80 characters |
options | string[] | No | Replaces 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
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:
{
"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
400listing every problem at once:
{
"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.datestep runs for each contact whose date field falls on the day you choose, for example 3 days beforedue_date. Publishing fails unless the field exists and has typedate. Seetrigger.date. - When a contact's field values change, MsGine records a
contact.updatedplatform event whosechangedarray lists the keys that changed, so a workflow can react to, say, a new balance:"balance" in trigger.changed. Saving identical values records nothing.
{
"contactId": "6a1f…",
"name": "Amina",
"phone": "+256701234567",
"email": "amina@example.com",
"fields": { "balance": 150000, "due_date": "2027-01-15" },
"changed": ["balance"]
}