User fields and company-specific fields

Discover your company's profile field keys, understand why request bodies are camelCase but field maps are snake_case, and read the response that tells you whether a write actually landed.

The account vs the profile

Two different endpoints, two different key styles. This is the single most common source of confusion.

POST /usersPOST /users/{id}/data
Createsthe accountnothing — writes values onto an existing account
Bodya fixed, documented schemaa free-form map of field key → value
KeyscamelCase — firstName, lastName, email, roleIdsnake_case field keys — birthday, nationality
Configurable?no, same for every companyyes, partly company-specific
Responsethe created user{ skipped, needsApproval, unknown }

So firstName and first_name are both real, and they are not interchangeable:

# Create the account
curl -X POST https://api.premote.io/v1/users \
  -H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
  -d '{ "email": "[email protected]", "firstName": "Jane", "lastName": "Doe" }'

# Fill in the profile
curl -X POST https://api.premote.io/v1/users/123/data \
  -H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
  -d '{ "birthday": "1990-04-12", "nationality": "DE" }'

There are three profile-write endpoints — pick by whose profile you are writing:

EndpointWritesPermission model
POST /users/{id}/dataanother user's profileadmin / super-admin write through; a personal assistant is held to the same per-field permissions as the profile owner
POST /users/datathe calling user's own profileper-field permissions apply (super-admins excepted)
POST /users/bulk-importmany users at oncesuper-admin
🚧

POST /users/bulk-import uses snake_case for the name too

Bulk import is the one place where the account fields are also snake_case: email, first_name, last_name, employee_number. Every other key in the row is treated as a user-field key. It reports only a count — it does not tell you what was skipped, held for approval or unrecognised — so use POST /users/{id}/data when you need that feedback. Empty values are ignored there rather than clearing the field.

📘

Why is it a POST and not a PATCH?

Because it is an upsert keyed by field, not a partial update of a resource: keys you omit are left untouched, and a key may produce an approval request instead of a write. There is no PATCH alias — use POST.


Discovering the fields for your company

GET /user-fields — API Reference

curl https://api.premote.io/v1/user-fields \
  -H 'Authorization: Bearer <token>'

The response is a list of categories; the field definitions live in each category's items:

{
  "userFields": [
    {
      "id": 1,
      "key": "personal",
      "label": { "en": "Personal data", "de": "Persönliche Daten" },
      "customizable": false,
      "items": [
        {
          "id": 5,
          "key": "birthday",
          "label": { "en": "Birthday" },
          "type": "DATE",
          "permission": "EDIT",
          "options": []
        },
        {
          "id": 6,
          "key": "nationality",
          "label": { "en": "Nationality" },
          "type": "REFERENCE_COUNTRIES",
          "permission": "VIEW",
          "options": [{ "value": "DE", "label": "Germany" }]
        }
      ]
    }
  ]
}

Every items[].key is a key you may send to POST /users/{id}/data.

key vs label — always integrate against key

  • key is the stable, snake_case identifier. This is what you send and what you match on.
  • label is human-readable and localised ({ en, de }). It is renamed freely by administrators and must never appear in your code.

The same applies to dropdown options: send options[].value, never options[].label.

Global vs company-specific fields

Some fields exist for every premote customer (birthday, nationality); others are created by your company (employee_number, cost_centre). Items carry customizable: true and a companyId when the definition belongs to your company.

You cannot infer the list from another tenant, from a fixture, or from this page — call GET /user-fields per company and cache the result. A company may also rename or re-configure a global field, so even the shared keys can differ in label, options and permissions.

Narrowing to what a specific traveler actually needs

GET /user-fields/profile/USERID — API Reference — returns the same grouped structure, filtered to the fields that traveler's active compliance processes require, with a countryCodes hint per field.

Both endpoints return definitions, not values.


Reading current values

GET /users/{id}/data — API Reference — a flat array, one entry per field:

[
  { "id": 5, "fieldKey": "birthday", "categoryKey": "personal", "fieldType": "DATE", "value": "1990-04-12" },
  { "id": 6, "fieldKey": "nationality", "categoryKey": "personal", "fieldType": "REFERENCE_COUNTRIES", "value": "DE,RU" }
]

Note the shape difference: the definitions endpoints are grouped by category, this one is flat. Send fieldKey back as the key when writing.

To find out what is still missing for a given trip, use POST /trips/completeness/USERID — its missingUserFields array is the authoritative answer.


Writing values

curl -X POST https://api.premote.io/v1/users/123/data \
  -H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
  -d '{ "birthday": "1990-04-12", "employee_number": "AC-4471" }'

A 201 does not mean it was written

Always inspect the response:

{ "skipped": [], "needsApproval": [], "unknown": [] }
ArrayMeaningWhat to do
unknownthe key matches no field for this company — a typo, or a field that has not been created yetfix your mapping; the value was dropped
skippedthe per-field permission refused the write (VIEW, or EDIT_IF_EMPTY on a field that already has a value)expected for self-service callers; unexpected for an admin token
needsApprovalthe field is EDIT_REQUEST: no value was written, an approval request was filed insteadwait for an administrator to approve; the value is not live yet

Treat any non-empty array as a condition your integration must surface, not as success.

Per-field permissions

PermissionEnd users mayNotes
EDITwrite freelythe default
VIEWnot writevalue appears in skipped
EDIT_IF_EMPTYset it once while emptylater writes appear in skipped
EDIT_REQUESTsubmit for approvalvalue appears in needsApproval, is not stored yet

Admin and super-admin tokens bypass this policy entirely and write through — so skipped and needsApproval are always empty for them. If you are integrating with an admin token and expecting approval workflows, you will not get them.


Value formats

Every value is sent and stored as a string. What that string must contain depends on the field's type:

typeSend
TEXT, TEXTAREA, WYSIWYGthe text
NUMBERthe number as a string — "42"
DATEISO date — "1990-04-12"
TIME"14:30"
BOOLEAN"true" / "false"
REFERENCE_COUNTRYone ISO-3166 ALPHA-2 code — "DE"
REFERENCE_COUNTRIEScomma-separated ISO-3166 ALPHA-2 codes — "DE,RU" (dual nationals are normal)
REFERENCE, REFERENCE_MULTI, AUTOCOMPLETE, SEARCHone of options[].value from the field definition — company-specific
FILEthe File id as a string — "501"
FILEScomma-separated File ids — "501,502"

For FILE and FILES see Working with files — the id comes from a separate upload call. Sending a FILES value replaces the whole list, so read the current value first and append.

📘

Clearing a value

Send "" (or null, which is coerced to "") to clear a field. Both profile-write endpoints treat an explicit empty as a clear, and the change is recorded in the profile changelog like any other edit. Omitting the key leaves the value untouched — omission and clearing are different operations.


Recommended integration shape

  1. On first run per company, GET /user-fields and build a map from your source system's attribute → premote key. Store it; do not hardcode keys in code that ships to more than one tenant.
  2. Re-fetch periodically — administrators add and rename fields without telling integrators.
  3. On every write, assert skipped, needsApproval and unknown are all empty, and alert on anything else. A non-empty unknown almost always means your map has drifted from the company's configuration.

See also


Did this page help you?