Cobalt External API (1.0.0)

Download OpenAPI specification:

Customer-facing external API for Cobalt contacts, users, groups, roles, and incident reports.

  • All routes (except /health) require an x-api-key header. Each key is bound to one tenant; data is fully isolated per key.
  • Success bodies are wrapped in { data: ... }. Errors are returned as { errors: [{ msg: string }] }.
  • Batch endpoints accept up to 30 records per call and return per-record success/failure summaries (HTTP 207).

Health

Liveness probe

Public route — no x-api-key required.

Responses

Response samples

Content type
application/json
{
  • "status": true
}

Contact

Create a contact

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
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: []

Responses

Request samples

Content type
application/json
{
  • "title": "Mr.",
  • "firstName": "Jane",
  • "lastName": "Doe",
  • "email": "jane.doe@example.com",
  • "phones": [
    ],
  • "groupIds": [ ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List contacts

Authorizations:
ApiKeyAuth
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a contact by id

Authorizations:
ApiKeyAuth
path Parameters
contactId
required
integer >= 1

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a contact

Patches the contact. Already-deleted contacts return 404.

Errors

  • 400 — the patch is invalid.
  • 404 — contact not found.
Authorizations:
ApiKeyAuth
path Parameters
contactId
required
integer >= 1
Request Body schema: application/json
required
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 ]

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "firstName": "string",
  • "middleName": "string",
  • "lastName": "string",
  • "secondaryEmail": "user@example.com",
  • "phones": [
    ],
  • "groupIds": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a contact

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.
Authorizations:
ApiKeyAuth
path Parameters
contactId
required
integer >= 1

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Create up to 30 contacts

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
Array ([ 1 .. 30 ] items)
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: []

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update up to 30 contacts

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
Array ([ 1 .. 30 ] items)
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

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete up to 30 contacts

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
ids
required
Array of integers [ 1 .. 30 ] items [ items >= 1 ]

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

User

Create a user (requires existing contact)

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.
Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
username
required
string non-empty
active
boolean
Default: false
contactId
required
integer >= 1

Existing contact id.

required
object

Responses

Request samples

Content type
application/json
{
  • "username": "jdoe",
  • "active": false,
  • "contactId": 1,
  • "role": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List users

Authorizations:
ApiKeyAuth
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a user by id

Authorizations:
ApiKeyAuth
path Parameters
userId
required
integer >= 1

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a user (user-fields-only)

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.
Authorizations:
ApiKeyAuth
path Parameters
userId
required
integer >= 1
Request Body schema: application/json
required
active
boolean
object

Responses

Request samples

Content type
application/json
{
  • "active": true,
  • "role": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a user

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.
Authorizations:
ApiKeyAuth
path Parameters
userId
required
integer >= 1

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Create up to 30 users

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
Array ([ 1 .. 30 ] items)
username
required
string non-empty
active
boolean
Default: false
contactId
required
integer >= 1

Existing contact id.

required
object

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update up to 30 users

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
Array ([ 1 .. 30 ] items)
active
boolean
object
id
required
integer >= 1

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete up to 30 users

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
ids
required
Array of integers [ 1 .. 30 ] items [ items >= 1 ]

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Group

Create a group

Creates a new group. Returns the created group with id, name, and externalId.

Errors

  • 400 — invalid body or externalId already in use.
Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
name
required
string non-empty
externalId
string or null

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "externalId": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List groups

Authorizations:
ApiKeyAuth
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a group by id

Authorizations:
ApiKeyAuth
path Parameters
groupId
required
integer >= 1

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a group

Authorizations:
ApiKeyAuth
path Parameters
groupId
required
integer >= 1
Request Body schema: application/json
required
name
string non-empty
externalId
string or null

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "externalId": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a group

Removes the group. Any contacts that were members of the group remain — only the membership association is dropped.

Authorizations:
ApiKeyAuth
path Parameters
groupId
required
integer >= 1

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Add contacts to a group (idempotent)

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
groupId
required
integer >= 1
contactIds
required
Array of integers [ 1 .. 100 ] items [ items >= 1 ]

Responses

Request samples

Content type
application/json
{
  • "groupId": 1,
  • "contactIds": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Remove contacts from a group (bulk, idempotent)

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.

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
groupId
required
integer >= 1
contactIds
required
Array of integers [ 1 .. 100 ] items [ items >= 1 ]

Responses

Request samples

Content type
application/json
{
  • "groupId": 1,
  • "contactIds": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List contacts in a group

Authorizations:
ApiKeyAuth
path Parameters
groupId
required
integer >= 1
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Role

List assignable roles

Returns every role that can be passed as role.id to POST /user/create or PUT /user/{userId}.

Authorizations:
ApiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Phone

List phone prefixes

Returns every phone prefix that can be passed as prefixId on a phone (POST /contact/create, PUT /contact/{contactId}).

Authorizations:
ApiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

List phone types

Returns every phone type that can be passed as typeId on a phone (POST /contact/create, PUT /contact/{contactId}).

Authorizations:
ApiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Incident

List incident reports

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).

Authorizations:
ApiKeyAuth
query Parameters
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

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

SFTP CSV Import Guide

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.

File Format

  • Upload a comma-separated CSV file encoded as UTF-8.
  • The first row must contain column headers.
  • Upload the file inside the organization folder.
  • The S3/SFTP key must follow this pattern: <organization>/<organization>_<fileName>.csv.
  • Example for organization ndkcha: ndkcha/ndkcha_contacts_2026-06-26.csv.
  • Do not upload files directly at the bucket/root level. Files must be inside the organization folder and the file name must start with the same organization name.

Contact CSV Columns

Required columns for contact imports:

  • firstName
  • lastName
  • email

Common optional columns:

  • operation
  • title
  • middleName
  • secondary_email
  • phone1_prefix, phone1, phone1_type, phone1_extension
  • phone2_prefix, phone2, phone2_type, phone2_extension
  • group_id1, group_id2, group_id3
  • username and role_name when the row should create or update a user login

Phone 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.

Operation Column

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.

When The Operation Column Is Not Provided

If the CSV does not include an operation column at all, Cobalt treats the file as a full contact sync:

  1. Each CSV row is created or updated by email.
  2. After the file is processed, Cobalt checks which existing contacts were not present in the CSV.
  3. The import settings decide what happens to those missing contacts.

The frontend settings are:

  • "Delete contacts when operation column is not provided"
  • "Delete user when operation column is not provided"

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.

User Columns

A row only creates or updates a login user when user columns are included.

  • If username is present, role_name must also be present.
  • If role_name is present, username must also be present.
  • The column name must be exactly role_name; variants like roleName, rolename, or role-name are rejected.
  • role_name must match an assignable Cobalt role.
  • Empty username or role_name values fail the row.

Error Results

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 CSV Imports

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 delete
  • name
  • external_id for create rows when available