Download OpenAPI specification:
Customer-facing external API for Cobalt contacts, users, groups, roles, and incident reports.
/health) require an x-api-key header. Each key is bound to one tenant; data is fully isolated per key.{ data: ... }. Errors are returned as { errors: [{ msg: string }] }.| title | string |
| firstName required | string non-empty |
| middleName | string |
| lastName required | string non-empty |
| email required | string <email> |
| secondaryEmail | string <email> |
Array of objects (PhoneInput) <= 5 items Default: [] | |
| groupIds | Array of integers[ items >= 1 ] Default: [] |
{- "title": "Mr.",
- "firstName": "Jane",
- "lastName": "Doe",
- "email": "jane.doe@example.com",
- "phones": [
- {
- "typeId": 1,
- "prefixId": 1,
- "number": "5551234567"
}
], - "groupIds": [ ]
}{- "data": {
- "id": 0,
- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "email": "user@example.com",
- "secondaryEmail": "user@example.com",
- "phones": [
- {
- "id": 0,
- "number": "string",
- "extension": "string",
- "prefix": {
- "id": 0,
- "country": "string",
- "code": "string"
}, - "type": {
- "id": 0,
- "name": "string"
}
}
], - "groups": [
- {
- "id": 0,
- "name": "string",
- "external_id": "string"
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}| page | integer >= 0 Default: 0 Zero-indexed page number. |
| size | integer [ 1 .. 100 ] Default: 20 Page size. |
| search | string Substring match across the resource's display fields. |
| ids | string Comma-separated list of ids to include. |
| exceptIds | string Comma-separated list of ids to exclude. |
{- "data": {
- "total": 0,
- "limit": 0,
- "offset": 0,
- "items": [
- {
- "id": 0,
- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "email": "user@example.com",
- "secondaryEmail": "user@example.com",
- "phones": [
- {
- "id": 0,
- "number": "string",
- "extension": "string",
- "prefix": {
- "id": 0,
- "country": "string",
- "code": "string"
}, - "type": {
- "id": 0,
- "name": "string"
}
}
], - "groups": [
- {
- "id": 0,
- "name": "string",
- "external_id": "string"
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}
}{- "data": {
- "id": 0,
- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "email": "user@example.com",
- "secondaryEmail": "user@example.com",
- "phones": [
- {
- "id": 0,
- "number": "string",
- "extension": "string",
- "prefix": {
- "id": 0,
- "country": "string",
- "code": "string"
}, - "type": {
- "id": 0,
- "name": "string"
}
}
], - "groups": [
- {
- "id": 0,
- "name": "string",
- "external_id": "string"
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Patches the contact. Already-deleted contacts return 404.
Errors
400 — the patch is invalid.404 — contact not found.| contactId required | integer >= 1 |
| title | string |
| firstName | string non-empty |
| middleName | string |
| lastName | string non-empty |
| secondaryEmail | string <email> |
Array of objects (PhoneInput) <= 5 items | |
| groupIds | Array of integers[ items >= 1 ] |
{- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "secondaryEmail": "user@example.com",
- "phones": [
- {
- "typeId": 1,
- "prefixId": 1,
- "number": "string",
- "extension": "string"
}
], - "groupIds": [
- 1
]
}{- "data": {
- "id": 0,
- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "email": "user@example.com",
- "secondaryEmail": "user@example.com",
- "phones": [
- {
- "id": 0,
- "number": "string",
- "extension": "string",
- "prefix": {
- "id": 0,
- "country": "string",
- "code": "string"
}, - "type": {
- "id": 0,
- "name": "string"
}
}
], - "groups": [
- {
- "id": 0,
- "name": "string",
- "external_id": "string"
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Permanently removes the contact. The contact's email is released for reuse and the contact is removed from any groups it belonged to. If the contact has a linked user, delete the user first via DELETE /user/{userId} — otherwise this call returns 400.
Errors
400 — contact still has a linked user.404 — contact not found.| contactId required | integer >= 1 |
{- "data": {
- "deleted": true,
- "id": 0
}
}| title | string |
| firstName required | string non-empty |
| middleName | string |
| lastName required | string non-empty |
| email required | string <email> |
| secondaryEmail | string <email> |
Array of objects (PhoneInput) <= 5 items Default: [] | |
| groupIds | Array of integers[ items >= 1 ] Default: [] |
[- {
- "title": "Mr.",
- "firstName": "Jane",
- "lastName": "Doe",
- "email": "jane.doe@example.com",
- "phones": [
- {
- "typeId": 1,
- "prefixId": 1,
- "number": "5551234567"
}
], - "groupIds": [ ]
}
]{- "data": {
- "summary": {
- "total": 0,
- "succeeded": 0,
- "failed": 0
}, - "results": [
- {
- "index": 0,
- "success": true,
- "data": null,
- "error": {
- "msg": "string",
- "code": "string"
}
}
]
}
}| title | string |
| firstName | string non-empty |
| middleName | string |
| lastName | string non-empty |
| secondaryEmail | string <email> |
Array of objects (PhoneInput) <= 5 items | |
| groupIds | Array of integers[ items >= 1 ] |
| id required | integer >= 1 |
[- {
- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "secondaryEmail": "user@example.com",
- "phones": [
- {
- "typeId": 1,
- "prefixId": 1,
- "number": "string",
- "extension": "string"
}
], - "groupIds": [
- 1
], - "id": 1
}
]{- "data": {
- "summary": {
- "total": 0,
- "succeeded": 0,
- "failed": 0
}, - "results": [
- {
- "index": 0,
- "success": true,
- "data": null,
- "error": {
- "msg": "string",
- "code": "string"
}
}
]
}
}| ids required | Array of integers [ 1 .. 30 ] items [ items >= 1 ] |
{- "ids": [
- 1,
- 2,
- 3
]
}{- "data": {
- "summary": {
- "total": 0,
- "succeeded": 0,
- "failed": 0
}, - "results": [
- {
- "index": 0,
- "success": true,
- "data": null,
- "error": {
- "msg": "string",
- "code": "string"
}
}
]
}
}Creates a login-capable user for an existing contact. The contact must exist and must not already be linked to another user — each contact can have at most one user. The supplied role must exist (call GET /role/all for the assignable list). Usernames are unique within the tenant.
No password is accepted on this endpoint. New users are created in a state where they must set a password before signing in, and the default welcome email is not sent — you must trigger your own onboarding flow (e.g., a password-reset / set-password link) to deliver credentials before the user can log in.
Errors
400 — contact already has a user, username taken, or tenant user limit reached.404 — role or contact not found.| username required | string non-empty |
| active | boolean Default: false |
| contactId required | integer >= 1 Existing contact id. |
required | object |
{- "username": "jdoe",
- "active": false,
- "contactId": 1,
- "role": {
- "id": 1
}
}{- "data": {
- "id": 0,
- "username": "string",
- "active": true,
- "contact": {
- "id": 0,
- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "email": "user@example.com",
- "secondaryEmail": "user@example.com"
}, - "role": {
- "id": 0,
- "name": "string"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}| page | integer >= 0 Default: 0 Zero-indexed page number. |
| size | integer [ 1 .. 100 ] Default: 20 Page size. |
| search | string Substring match across the resource's display fields. |
| ids | string Comma-separated list of ids to include. |
| exceptIds | string Comma-separated list of ids to exclude. |
{- "data": {
- "total": 0,
- "limit": 0,
- "offset": 0,
- "items": [
- {
- "id": 0,
- "username": "string",
- "active": true,
- "contact": {
- "id": 0,
- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "email": "user@example.com",
- "secondaryEmail": "user@example.com"
}, - "role": {
- "id": 0,
- "name": "string"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}
}{- "data": {
- "id": 0,
- "username": "string",
- "active": true,
- "contact": {
- "id": 0,
- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "email": "user@example.com",
- "secondaryEmail": "user@example.com"
}, - "role": {
- "id": 0,
- "name": "string"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Updates user-only fields (role, active). Personal details (firstName, email, phones, etc.) belong to the contact resource and aren't accepted here. Passwords are not accepted. Already-deleted users return 404.
Errors
400 — the tenant user limit would be exceeded by activating this user.404 — user or new role not found.| userId required | integer >= 1 |
| active | boolean |
object |
{- "active": true,
- "role": {
- "id": 1
}
}{- "data": {
- "id": 0,
- "username": "string",
- "active": true,
- "contact": {
- "id": 0,
- "title": "string",
- "firstName": "string",
- "middleName": "string",
- "lastName": "string",
- "email": "user@example.com",
- "secondaryEmail": "user@example.com"
}, - "role": {
- "id": 0,
- "name": "string"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Permanently removes the user and the linked contact. The user and contact share underlying personal data, so a single call cleans up both — calling DELETE /contact/{contactId} afterwards is unnecessary (and will return 404). After this call, the user can no longer sign in, the contact disappears from contact listings and lookups, the email is released for reuse, and group memberships are cleared.
Returns the original username and email so the caller can update its own systems if needed.
Errors
400 — the user has already been deleted.404 — user not found.| userId required | integer >= 1 |
{- "data": {
- "deleted": true,
- "id": 0,
- "contactId": 0,
- "username": "string",
- "userEmail": "string"
}
}| username required | string non-empty |
| active | boolean Default: false |
| contactId required | integer >= 1 Existing contact id. |
required | object |
[- {
- "username": "jdoe",
- "active": false,
- "contactId": 1,
- "role": {
- "id": 1
}
}
]{- "data": {
- "summary": {
- "total": 0,
- "succeeded": 0,
- "failed": 0
}, - "results": [
- {
- "index": 0,
- "success": true,
- "data": null,
- "error": {
- "msg": "string",
- "code": "string"
}
}
]
}
}| active | boolean |
object | |
| id required | integer >= 1 |
[- {
- "active": true,
- "role": {
- "id": 1
}, - "id": 1
}
]{- "data": {
- "summary": {
- "total": 0,
- "succeeded": 0,
- "failed": 0
}, - "results": [
- {
- "index": 0,
- "success": true,
- "data": null,
- "error": {
- "msg": "string",
- "code": "string"
}
}
]
}
}| ids required | Array of integers [ 1 .. 30 ] items [ items >= 1 ] |
{- "ids": [
- 1,
- 2,
- 3
]
}{- "data": {
- "summary": {
- "total": 0,
- "succeeded": 0,
- "failed": 0
}, - "results": [
- {
- "index": 0,
- "success": true,
- "data": null,
- "error": {
- "msg": "string",
- "code": "string"
}
}
]
}
}Creates a new group. Returns the created group with id, name, and externalId.
Errors
400 — invalid body or externalId already in use.| name required | string non-empty |
| externalId | string or null |
{- "name": "string",
- "externalId": "string"
}{- "data": {
- "id": 0,
- "name": "string",
- "externalId": "string"
}
}| page | integer >= 0 Default: 0 Zero-indexed page number. |
| size | integer [ 1 .. 100 ] Default: 20 Page size. |
| search | string Substring match across the resource's display fields. |
| ids | string Comma-separated list of ids to include. |
| exceptIds | string Comma-separated list of ids to exclude. |
{- "data": {
- "total": 0,
- "limit": 0,
- "offset": 0,
- "items": [
- {
- "id": 0,
- "name": "string",
- "externalId": "string"
}
]
}
}| groupId required | integer >= 1 |
| name | string non-empty |
| externalId | string or null |
{- "name": "string",
- "externalId": "string"
}{- "data": {
- "id": 0,
- "name": "string",
- "externalId": "string"
}
}| groupId required | integer >= 1 |
| contactIds required | Array of integers [ 1 .. 100 ] items [ items >= 1 ] |
{- "groupId": 1,
- "contactIds": [
- 1,
- 2,
- 3
]
}{- "data": {
- "groupId": 0,
- "results": [
- {
- "contactId": 0,
- "added": true,
- "alreadyMember": true,
- "error": "string"
}
]
}
}Symmetrical with POST /group/addContact. Sends { groupId, contactIds: [...] } in the body and receives a per-contact result. removed: false means the contact wasn't a member of the group — not an error.
| groupId required | integer >= 1 |
| contactIds required | Array of integers [ 1 .. 100 ] items [ items >= 1 ] |
{- "groupId": 1,
- "contactIds": [
- 1,
- 2,
- 3
]
}{- "data": {
- "groupId": 0,
- "results": [
- {
- "contactId": 0,
- "removed": true
}
]
}
}| groupId required | integer >= 1 |
| page | integer >= 0 Default: 0 Zero-indexed page number. |
| size | integer [ 1 .. 100 ] Default: 20 Page size. |
| search | string Substring match across the resource's display fields. |
| ids | string Comma-separated list of ids to include. |
| exceptIds | string Comma-separated list of ids to exclude. |
{- "data": {
- "total": 0,
- "limit": 0,
- "offset": 0,
- "items": [
- {
- "id": 0,
- "firstName": "string",
- "lastName": "string",
- "email": "user@example.com"
}
]
}
}Returns every phone prefix that can be passed as prefixId on a phone (POST /contact/create, PUT /contact/{contactId}).
{- "data": {
- "items": [
- {
- "id": 1,
- "country": "36",
- "code": "CA",
- "initials": "+1"
}, - {
- "id": 2,
- "country": "235",
- "code": "UK",
- "initials": "+44"
}
]
}
}Lists incident reports for the tenant.
By default, excludes incidents in the states ON_GOING, ALERT, MERGED, and ARCHIVED. Set type=ARCHIVED to surface only archived incidents (the exclusion flips to ON_GOING, ALERT, MERGED, CLOSED, IGNORED, COMPLETED).
| search | string Substring match (case-insensitive) on incident name. |
| contactIds | string Comma-separated contact ids — only incidents declared by these contacts are returned. |
| type | string Enum: "DEFAULT" "ARCHIVED" Set to ARCHIVED to surface archived incidents. |
| startDate | string <date-time> ISO timestamp — inclusive lower bound on the incident start time. |
| endDate | string <date-time> ISO timestamp — inclusive upper bound on the incident end time. |
| sortBy | string Default: "updated_at" Enum: "created_at" "updated_at" "name" Field to sort by. |
| sortOrder | string Default: "desc" Enum: "asc" "desc" |
| limit | integer [ 1 .. 500 ] Default: 100 |
| offset | integer >= 0 Default: 0 |
{- "data": {
- "items": [
- {
- "id": "060426-1775-51824-7872",
- "event": "Bris d'équipement",
- "status": "COMPLETED",
- "declaredBy": "Reyhaneh Tavakolipour",
- "startDate": "2026-04-06T16:30:00.000Z",
- "endDate": "2026-04-07T08:40:00.000Z"
}
], - "total": 1,
- "limit": 100,
- "offset": 0
}
}SFTP import is a file-based integration for clients that prefer to send CSV files instead of calling the REST endpoints. Cobalt provides the SFTP connection details and the organization folder name.
<organization>/<organization>_<fileName>.csv.ndkcha: ndkcha/ndkcha_contacts_2026-06-26.csv.Required columns for contact imports:
firstNamelastNameemailCommon optional columns:
operationtitlemiddleNamesecondary_emailphone1_prefix, phone1, phone1_type, phone1_extensionphone2_prefix, phone2, phone2_type, phone2_extensiongroup_id1, group_id2, group_id3username and role_name when the row should create or update a user loginPhone prefixes may be sent as +1 or 1. Spaces are ignored, so + 1 is treated as +1.
Group columns use Cobalt group external IDs. Multiple group columns can be used in the same row.
The optional operation column controls what each row should do:
| Value | Meaning |
|---|---|
C |
Create a contact. If the email already exists, the row fails. |
U |
Update an existing contact. If the email does not exist, the row fails. |
D |
Delete an existing contact. If the email does not exist, the row fails. |
| Blank | Create if the email does not exist, update if it already exists. |
Email is used to find existing contacts. Email itself is not changed during an update.
If the CSV does not include an operation column at all, Cobalt treats the file as a full contact sync:
The frontend settings are:
Behavior for contacts missing from the CSV:
| Settings | Result |
|---|---|
| Delete contacts on, contact has no user | Contact is deleted. |
| Delete contacts off, contact has no user | Contact is kept. |
| Delete user on, contact has a user | User is deleted, and the linked contact is removed. |
| Delete user off, contact has a user | Linked contact is kept. |
Important: this cleanup only runs when the operation column is completely absent. If the CSV includes an operation column, even if some row values are blank, cleanup does not run.
If the client does not want full-sync cleanup behavior, include the operation column or keep both cleanup settings disabled.
A row only creates or updates a login user when user columns are included.
username is present, role_name must also be present.role_name is present, username must also be present.role_name; variants like roleName, rolename, or role-name are rejected.role_name must match an assignable Cobalt role.username or role_name values fail the row.Row errors do not stop the whole file. Cobalt continues processing later rows.
If rows fail, Cobalt writes an error CSV to the SFTP folder:
<organization>/error/error_<fileName>.csv
The error CSV contains reason_for_failure plus the original row values so the client can fix and resend failed rows.
After a normal import completes, the original uploaded file is removed from the SFTP source folder. If the whole import fails before row processing can complete, the original file is left in place for investigation.
Group imports are also supported. Use a file name where the original file name ends with .group.csv, for example:
ndkcha/ndkcha_groups.group.csv
Group CSV columns:
operation: C to create or D to deletenameexternal_id for create rows when available