Nolmo API 1.0.0

Another system works with a Nolmo workspace the way a person does, within what an admin lets its key do. Make a key in Settings, API keys, and send it as Authorization: Bearer <key>. Start with the guides.

Authentication. Make a key in Settings, API keys, and send it as Authorization: Bearer <key>. A key has scopes (cases:read, cases:write, cases:reply, contacts:read, contacts:write, webhooks:manage, knowledge:read, knowledge:write) and sees only the groups it was given (articles belong to the workspace, not to a group). Keep the key on a server; anyone who has it can do what it may.

Errors. Every error is JSON: {"error": {"code": "...", "message": "...", "details": null}}. Branch on code, show message to a developer, never to a customer.

Limits. Every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. Past the limit the answer is 429 with Retry-After. Send an Idempotency-Key on every POST so a retry writes once.

Versions. Within /v1 fields and events are only ever added, so ignore the fields and events you do not know. A removal, a rename or a change of meaning is /v2, announced six months before and served beside /v1 for twelve. The machine-readable document is /v1/openapi.json.

Guides

Account

GET /v1/me

Who is calling

The key's name, workspace, scopes and groups. Every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset.

200 Successful Response

Body application/json: Me

{
  "all_groups": false,
  "groups": [
    {
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f8",
      "name": "Support"
    }
  ],
  "key": {
    "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
    "name": "Contact form",
    "prefix": "nolmo_live_k3J9fQ2a"
  },
  "scopes": [
    "cases:read",
    "cases:write"
  ],
  "tenant": {
    "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f7",
    "name": "Furu & Lin",
    "slug": "furu-lin"
  }
}
FieldTypeAbout
keyMeKeyrequired
tenantMeTenantrequired
scopeslist of cases:read | cases:write | cases:reply | contacts:read | contacts:write | webhooks:manage | knowledge:read | knowledge:writerequired

What the key may do

all_groupsbooleanrequired

The key sees every group's cases

groupslist of MeGrouprequired

The groups it sees; empty with all_groups

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

Cases

GET /v1/cases/search

Search cases

Find cases by what was said in them, from the cases the key sees. A case in a group the key does not see is never found, with or without a filter.

This is the app's own search, so the inbox and the API find the same cases. Searching the team's internal notes too is opt-in with include_notes. Page with after, as with GET /v1/cases.

Parameters

NameInTypeAbout
qquerystringrequired

What to look for: words from the subject or a message, or a case number


e.g. cracked tiles
statusquerylist of new | open | pending | hold | solved | closed or null

Only cases in these statuses

priorityquerylist of low | normal | high | urgent or null

Only cases with these priorities

typequerylist of string or null

Only cases of these types, by key (GET /v1/case-types)


e.g. ['return']
group_idquerylist of string or null

Only cases in these groups (GET /v1/groups); none is the cases with no group


e.g. ['0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6']
assignee_idquerylist of string or null

Only cases assigned to these people (GET /v1/agents); none is the unassigned


e.g. ['0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6']
tagquerylist of string or null

Only cases with every one of these tags


e.g. ['delivery']
channelquerylist of string or null

Only cases that arrived on these channels


e.g. ['email', 'api']
created_fromquerystring (date-time) or null

Created at or after this time


e.g. 2026-11-01T00:00:00Z
created_toquerystring (date-time) or null

Created before this time


e.g. 2026-12-01T00:00:00Z
updated_fromquerystring (date-time) or null

Updated at or after this time


e.g. 2026-11-01T00:00:00Z
updated_toquerystring (date-time) or null

Updated before this time


e.g. 2026-12-01T00:00:00Z
include_notesqueryboolean

Also count the team's internal notes, which are left out unless you ask for them

afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. eyJ2Ijog
limitqueryinteger

How many to return, at most

200 Successful Response

Body application/json: CaseList

{
  "items": [
    {
      "channel": "api",
      "contact": {
        "email": "kari@example.no",
        "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
        "language": "nb",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-09T09:00:00Z",
      "custom_fields": {},
      "external_ref": "order-12345",
      "group_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "last_message_at": "2026-11-09T09:00:00Z",
      "number": 1042,
      "priority": "normal",
      "source": "Returns page",
      "status": "new",
      "subject": "Cracked tiles in the delivery",
      "tags": [
        "delivery"
      ],
      "updated_at": "2026-11-09T09:00:00Z",
      "version": 1
    }
  ]
}
FieldTypeAbout
itemslist of Caserequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

400 A filter is not an id, or the cursor is not one this API issued

Body application/json: ErrorResponse

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `cases:read`

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/cases/{number}/attachments

List a case's files

The files on the case's messages, oldest first, a page at a time. Files on the team's internal notes are left out unless include_notes is true. Download one with GET /v1/attachments/{id}.

Parameters

NameInTypeAbout
numberpathintegerrequired

The case's number in the workspace


e.g. 1042
include_notesqueryboolean

Also count the team's internal notes, which are left out unless you ask for them

afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. eyJ2Ijog
limitqueryinteger

About how many files to return; a message's files stay together

200 Successful Response

Body application/json: CaseFileList

{
  "items": [
    {
      "content_type": "image/jpeg",
      "created_at": "2026-11-09T10:30:00Z",
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "message_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "name": "damaged-tiles.jpg",
      "on": "message",
      "size": 183204
    }
  ]
}
FieldTypeAbout
itemslist of CaseFilerequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `cases:read`

Body application/json: ErrorResponse

404 No such case or file, or one in a group the key does not see

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/attachments/{attachment_id}

Download a file

The file, or a redirect (307) to a signed address that lives for a few minutes and carries no key; follow it without sending your Authorization header on. A file on a case the key does not see, a file on an internal note (unless include_notes is true) and an id that does not exist are the same 404.

Parameters

NameInTypeAbout
attachment_idpathstringrequired

The file's id from GET /v1/cases/{number}/attachments


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6
include_notesqueryboolean

Also count the team's internal notes, which are left out unless you ask for them

200 The file itself

Body application/octet-stream: string (binary)

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `cases:read`

Body application/json: ErrorResponse

404 No such case or file, or one in a group the key does not see

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

307 A signed URL that works for a few minutes; follow it

GET /v1/cases/{number}/events

List a case's events

What happened to the case, oldest first, a page at a time: when it was made, each reply, and every change of status, assignee, priority or type, with who did it. A person is a first name, a key is its name.

The kinds are created, customer_replied, team_replied, status_changed, reopened, assigned, priority_changed and type_changed. Other kinds may be added later; ignore the ones you do not know. Nothing about the workspace's clocks, the AI's work or the team's notes is part of it.

Parameters

NameInTypeAbout
numberpathintegerrequired

The case's number in the workspace


e.g. 1042
afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. eyJ2Ijog
limitqueryinteger

How many to return, at most

200 Successful Response

Body application/json: CaseEventList

{
  "items": [
    {
      "actor": {
        "name": "Astrid",
        "type": "team"
      },
      "at": "2026-11-09T11:02:00Z",
      "from": "open",
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "kind": "status_changed",
      "to": "solved"
    }
  ]
}
FieldTypeAbout
itemslist of CaseEventrequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `cases:read`

Body application/json: ErrorResponse

404 No such case or file, or one in a group the key does not see

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/cases

List or find cases

The cases the key sees, newest first. A case in a group the key does not see is not listed, and not found by its external_ref either.

To find the case you made, send its external_ref (and the source you made it under, if you gave one).

Parameters

NameInTypeAbout
external_refquerystring or null

Your own id for the case: finds the case you made with it


e.g. order-12345
sourcequerystring or null

Only cases made under this source


e.g. Returns page
statusquerylist of new | open | pending | hold | solved | closed or null

Only cases in these statuses

afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. eyJ2Ijog
limitqueryinteger

How many to return, at most

200 Successful Response

Body application/json: CaseList

{
  "items": [
    {
      "channel": "api",
      "contact": {
        "email": "kari@example.no",
        "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
        "language": "nb",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-09T09:00:00Z",
      "custom_fields": {},
      "external_ref": "order-12345",
      "group_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "last_message_at": "2026-11-09T09:00:00Z",
      "number": 1042,
      "priority": "normal",
      "source": "Returns page",
      "status": "new",
      "subject": "Cracked tiles in the delivery",
      "tags": [
        "delivery"
      ],
      "updated_at": "2026-11-09T09:00:00Z",
      "version": 1
    }
  ]
}
FieldTypeAbout
itemslist of Caserequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope, or the group is not one of the key's

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/cases

Create a case

Create a case for a person, with what they wrote as its first message.

The person is found by email, then phone, then an id in external_ids, and made when none is known. The case is routed by the workspace's rules, gets its clocks and is assessed, exactly as a mail would. It arrives on the channel api, and its timeline says which key made it.

Send an Idempotency-Key so that a retry makes no second case. An external_ref finds the case again (GET /v1/cases?external_ref=); a second create with the same external_ref and source is 409 external_ref_taken and names the case that has it.

Parameters

NameInTypeAbout
Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

Request body application/json: CaseIn

{
  "contact": {
    "email": "kari@example.no",
    "name": "Kari Kunde"
  },
  "external_ref": "order-12345",
  "message": {
    "body_text": "Three of the tiles in the pack were cracked. Order 12345."
  },
  "priority": "normal",
  "source": "Returns page",
  "subject": "Cracked tiles in the delivery",
  "tags": [
    "delivery"
  ]
}
FieldTypeAbout
contactContactInrequired
subjectstringrequired

The case's title

messageFirstMessageInrequired
prioritylow | normal | high | urgent

low, normal, high or urgent

typestring or null

One of the workspace's case types, by its key

tagslist of string

Labels to put on the case

brand_idstring (uuid) or null

The brand or store the case belongs to

group_idstring (uuid) or null

The group the case goes to. It must be one of the key's groups (GET /v1/me); left out, the workspace's rules choose

custom_fieldsobject

Values for the workspace's case fields, by the field's key

external_refstring or null

Your own id for the case. A second create with the same id from the same source is refused with 409 external_ref_taken and the number of the case that has it

sourcestring or null

What you call your system, such as Contact form. external_ref is unique within it. Left out, it is the key's name

201 Successful Response

Body application/json: Case

{
  "channel": "api",
  "contact": {
    "email": "kari@example.no",
    "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
    "language": "nb",
    "name": "Kari Kunde"
  },
  "created_at": "2026-11-09T09:00:00Z",
  "custom_fields": {},
  "external_ref": "order-12345",
  "group_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "last_message_at": "2026-11-09T09:00:00Z",
  "number": 1042,
  "priority": "normal",
  "source": "Returns page",
  "status": "new",
  "subject": "Cracked tiles in the delivery",
  "tags": [
    "delivery"
  ],
  "updated_at": "2026-11-09T09:00:00Z",
  "version": 1
}
FieldTypeAbout
numberintegerrequired

The case's number in the workspace; the id in the paths

subjectstringrequired
statusnew | open | pending | hold | solved | closedrequired

new, open, pending, hold, solved or closed

prioritylow | normal | high | urgentrequired
typestring or nullrequired

The case type's key

tagslist of stringrequired
custom_fieldsobjectrequired
external_refstring or nullrequired

Your own id for the case, if it has one

sourcestring or nullrequired

The source the external_ref belongs to

channelstringrequired

How the case arrived: api for one made here

group_idstring (uuid) or nullrequired
brand_idstring (uuid) or nullrequired
assignee_first_namestring or nullrequired

The first name of the person who has the case

contactContactrequired
created_atstring (date-time)required
updated_atstring (date-time)required
solved_atstring (date-time) or nullrequired
last_message_atstring (date-time) or nullrequired
versionintegerrequired

Send it back in version of a PATCH

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope, or the group is not one of the key's

Body application/json: ErrorResponse

409 The case has changed, is closed, or the `external_ref` is taken

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/cases/{number}

Get a case

One case by its number. A case in a group the key does not see is a 404.

Parameters

NameInTypeAbout
numberpathintegerrequired

The case's number in the workspace


e.g. 1042

200 Successful Response

Body application/json: Case

{
  "channel": "api",
  "contact": {
    "email": "kari@example.no",
    "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
    "language": "nb",
    "name": "Kari Kunde"
  },
  "created_at": "2026-11-09T09:00:00Z",
  "custom_fields": {},
  "external_ref": "order-12345",
  "group_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "last_message_at": "2026-11-09T09:00:00Z",
  "number": 1042,
  "priority": "normal",
  "source": "Returns page",
  "status": "new",
  "subject": "Cracked tiles in the delivery",
  "tags": [
    "delivery"
  ],
  "updated_at": "2026-11-09T09:00:00Z",
  "version": 1
}
FieldTypeAbout
numberintegerrequired

The case's number in the workspace; the id in the paths

subjectstringrequired
statusnew | open | pending | hold | solved | closedrequired

new, open, pending, hold, solved or closed

prioritylow | normal | high | urgentrequired
typestring or nullrequired

The case type's key

tagslist of stringrequired
custom_fieldsobjectrequired
external_refstring or nullrequired

Your own id for the case, if it has one

sourcestring or nullrequired

The source the external_ref belongs to

channelstringrequired

How the case arrived: api for one made here

group_idstring (uuid) or nullrequired
brand_idstring (uuid) or nullrequired
assignee_first_namestring or nullrequired

The first name of the person who has the case

contactContactrequired
created_atstring (date-time)required
updated_atstring (date-time)required
solved_atstring (date-time) or nullrequired
last_message_atstring (date-time) or nullrequired
versionintegerrequired

Send it back in version of a PATCH

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope, or the group is not one of the key's

Body application/json: ErrorResponse

404 No such case, or one in a group the key does not see

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

PATCH /v1/cases/{number}

Change a case

Change a case's tags, priority, custom fields or external_ref. Status and assignment are the team's, and sending them is a 422. Send the version you read: if the case has moved on the answer is 409 case_changed, and nothing is written.

Parameters

NameInTypeAbout
numberpathintegerrequired

The case's number in the workspace


e.g. 1042

Request body application/json: CasePatchIn

{
  "priority": "high",
  "tags": [
    "delivery",
    "refund"
  ],
  "version": 3
}
FieldTypeAbout
tagslist of string or null

Replaces the case's tags

prioritylow | normal | high | urgent or null

low, normal, high or urgent

custom_fieldsobject or null

Values for the case fields, merged key by key

external_refstring or null

Your own id for the case; null takes it away

versioninteger or null

The version you read. When the case has moved on the answer is 409 case_changed

200 Successful Response

Body application/json: Case

{
  "channel": "api",
  "contact": {
    "email": "kari@example.no",
    "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
    "language": "nb",
    "name": "Kari Kunde"
  },
  "created_at": "2026-11-09T09:00:00Z",
  "custom_fields": {},
  "external_ref": "order-12345",
  "group_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "last_message_at": "2026-11-09T09:00:00Z",
  "number": 1042,
  "priority": "normal",
  "source": "Returns page",
  "status": "new",
  "subject": "Cracked tiles in the delivery",
  "tags": [
    "delivery"
  ],
  "updated_at": "2026-11-09T09:00:00Z",
  "version": 1
}
FieldTypeAbout
numberintegerrequired

The case's number in the workspace; the id in the paths

subjectstringrequired
statusnew | open | pending | hold | solved | closedrequired

new, open, pending, hold, solved or closed

prioritylow | normal | high | urgentrequired
typestring or nullrequired

The case type's key

tagslist of stringrequired
custom_fieldsobjectrequired
external_refstring or nullrequired

Your own id for the case, if it has one

sourcestring or nullrequired

The source the external_ref belongs to

channelstringrequired

How the case arrived: api for one made here

group_idstring (uuid) or nullrequired
brand_idstring (uuid) or nullrequired
assignee_first_namestring or nullrequired

The first name of the person who has the case

contactContactrequired
created_atstring (date-time)required
updated_atstring (date-time)required
solved_atstring (date-time) or nullrequired
last_message_atstring (date-time) or nullrequired
versionintegerrequired

Send it back in version of a PATCH

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope, or the group is not one of the key's

Body application/json: ErrorResponse

404 No such case, or one in a group the key does not see

Body application/json: ErrorResponse

409 The case has changed, is closed, or the `external_ref` is taken

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/cases/{number}/messages

List a case's messages

The case's public thread, oldest first, a page at a time. The team's internal notes are not part of it unless include_notes is true; a side conversation never is.

Parameters

NameInTypeAbout
numberpathintegerrequired

The case's number in the workspace


e.g. 1042
include_notesqueryboolean

Also the team's internal notes, which are left out unless you ask

afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. eyJ2Ijog
limitqueryinteger

How many to return, at most

200 Successful Response

Body application/json: MessageList

{
  "items": [
    {
      "attachments": [],
      "author": {
        "name": "Astrid",
        "type": "team"
      },
      "body_text": "Hi Kari, we are sending new tiles today.",
      "created_at": "2026-11-09T10:30:00Z",
      "delivery": "sent",
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "kind": "reply"
    }
  ]
}
FieldTypeAbout
itemslist of Messagerequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope, or the group is not one of the key's

Body application/json: ErrorResponse

404 No such case, or one in a group the key does not see

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/cases/{number}/messages

Add a message to a case

Add to a case. kind says what it is:

Parameters

NameInTypeAbout
numberpathintegerrequired

The case's number in the workspace


e.g. 1042
Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

Request body application/json: MessageIn

{
  "body_text": "Any news on this? I still have not heard anything.",
  "kind": "customer"
}
FieldTypeAbout
kindcustomer | note | replyrequired

customer: a message from the person the case is about; it reopens a solved case and is refused on a closed one. note: internal, for the team, authored by the key. reply: a public answer to the customer, sent through the case's channel as a team reply is; it needs the scope cases:reply

body_textstringrequired
body_htmlstring or null

Cleaned before it is stored, as in a new case

attachmentslist of string

Ids from POST /v1/uploads; only your own, not yet used ones

201 Successful Response

Body application/json: Message

{
  "attachments": [],
  "author": {
    "name": "Astrid",
    "type": "team"
  },
  "body_text": "Hi Kari, we are sending new tiles today.",
  "created_at": "2026-11-09T10:30:00Z",
  "delivery": "sent",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "kind": "reply"
}
FieldTypeAbout
idstring (uuid)required
kindcustomer | note | replyrequired

customer, reply (public, from the team, a key or the AI) or note (internal; only when asked for)

authorAuthorrequired
body_textstringrequired
body_htmlstring or nullrequired
attachmentslist of Attachmentrequired
deliveryqueued | sent | failed or nullrequired

For a reply that leaves by email: where it is. Null for everything else

created_atstring (date-time)required

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope, or the group is not one of the key's

Body application/json: ErrorResponse

404 No such case, or one in a group the key does not see

Body application/json: ErrorResponse

409 The case has changed, is closed, or the `external_ref` is taken

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/uploads

Upload a file

Upload a file as multipart/form-data (the part is called file), then name its id in attachments of a new case or of a message. The same kinds of file and the same size limit as in the app apply (pictures, documents, archives, mail, text; not pages or scripts). A file is yours until a message uses it, and only you can use it; one that is never used is deleted after a day.

Parameters

NameInTypeAbout
Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

Request body multipart/form-data: Body_upload_file_v1_uploads_post

FieldTypeAbout
filestringrequired

201 Successful Response

Body application/json: Upload

{
  "content_type": "image/jpeg",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "name": "damaged-tiles.jpg",
  "size": 183204
}
FieldTypeAbout
idstringrequired

Name it in attachments of a case or a message

namestringrequired
content_typestringrequired
sizeintegerrequired

Bytes

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope, or the group is not one of the key's

Body application/json: ErrorResponse

413 The file is larger than the limit

Body application/json: ErrorResponse

415 That kind of file cannot be attached

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

Contacts

GET /v1/contacts

List or find contacts

The workspace's contacts, by name, a page at a time. Every filter narrows the list; email, phone and external_system with external_id are exact matches, so they find the one person you know. A key sees every contact, whatever its groups; the counts are of the cases it sees.

Parameters

NameInTypeAbout
qquerystring or null

Words to search for in the name, email address, phone number and customer number

emailquerystring or null

Exactly this email address


e.g. kari@example.no
phonequerystring or null

Exactly this phone number, compared as digits


e.g. +47 900 00 000
external_systemquerystring or null

With external_id: the system whose id you know, as you named it in external_ids


e.g. shop
external_idquerystring or null

With external_system: the id in that system


e.g. C-1042
organization_idquerystring (uuid) or null

Only people in this company

afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. eyJ2Ijog
limitqueryinteger

How many to return, at most

200 Successful Response

Body application/json: CustomerList

{
  "items": [
    {
      "case_counts": {
        "cases": 3,
        "last_contact_at": "2026-10-20T08:15:00Z",
        "open": 1
      },
      "created_at": "2026-09-02T10:00:00Z",
      "customer_no": "10442",
      "email": "kari@example.no",
      "external_ids": {
        "shop": "C-1042"
      },
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "language": "nb",
      "name": "Kari Kunde",
      "organization_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
      "organization_name": "Fliser AS",
      "phone": "+4790000000",
      "updated_at": "2026-10-20T08:15:00Z"
    }
  ]
}
FieldTypeAbout
itemslist of Customerrequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/contacts

Create a contact

Make a contact. A person is never made twice: when the email address, the phone number (as digits) or an id in external_ids already belongs to a contact, nothing is written and the answer is 409 contact_exists with that contact's id in details.contact_id. Look the person up first with GET /v1/contacts, or just create and read the 409. A person with an address on a company's domain joins that company, as for a mail.

Send an Idempotency-Key so that a retry makes nothing twice.

Parameters

NameInTypeAbout
Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

Request body application/json: CustomerIn

{
  "email": "kari@example.no",
  "external_ids": {
    "shop": "C-1042"
  },
  "language": "nb",
  "name": "Kari Kunde",
  "phone": "+47 900 00 000"
}
FieldTypeAbout
namestringrequired

Their name

emailstring (email) or null

Their email address

phonestring or null

Their phone number

languagestring or null

nb or en

customer_nostring or null
member_nostring or null
organization_idstring (uuid) or null

The company they belong to. Left out, a company with their email's domain is used

external_idsmap of string

Their ids in your own systems, by the system's name: {"shop": "C-1042"}

201 Successful Response

Body application/json: Customer

{
  "case_counts": {
    "cases": 3,
    "last_contact_at": "2026-10-20T08:15:00Z",
    "open": 1
  },
  "created_at": "2026-09-02T10:00:00Z",
  "customer_no": "10442",
  "email": "kari@example.no",
  "external_ids": {
    "shop": "C-1042"
  },
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "name": "Kari Kunde",
  "organization_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "organization_name": "Fliser AS",
  "phone": "+4790000000",
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
emailstring or nullrequired

Lowercased. Unique in the workspace

phonestring or nullrequired

Digits with a leading +

languagestring or nullrequired

nb or en, when known

customer_nostring or nullrequired

Their customer number

member_nostring or nullrequired

Their member number

external_idsmap of stringrequired

Their ids in your own systems, by the system's name: {"shop": "C-1042"}

organization_idstring (uuid) or nullrequired

The company they belong to

organization_namestring or nullrequired
case_countsCaseCountsrequired
created_atstring (date-time)required
updated_atstring (date-time)required

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such contact or company, or a company named in the body does not exist

Body application/json: ErrorResponse

409 The email, phone number or external id is held by another, or the name is taken; `details` names the existing one

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/contacts/{contact_id}

Get a contact

One contact by id, with the counts of their cases the key sees.

Parameters

NameInTypeAbout
contact_idpathstring (uuid)required

The contact's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6

200 Successful Response

Body application/json: Customer

{
  "case_counts": {
    "cases": 3,
    "last_contact_at": "2026-10-20T08:15:00Z",
    "open": 1
  },
  "created_at": "2026-09-02T10:00:00Z",
  "customer_no": "10442",
  "email": "kari@example.no",
  "external_ids": {
    "shop": "C-1042"
  },
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "name": "Kari Kunde",
  "organization_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "organization_name": "Fliser AS",
  "phone": "+4790000000",
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
emailstring or nullrequired

Lowercased. Unique in the workspace

phonestring or nullrequired

Digits with a leading +

languagestring or nullrequired

nb or en, when known

customer_nostring or nullrequired

Their customer number

member_nostring or nullrequired

Their member number

external_idsmap of stringrequired

Their ids in your own systems, by the system's name: {"shop": "C-1042"}

organization_idstring (uuid) or nullrequired

The company they belong to

organization_namestring or nullrequired
case_countsCaseCountsrequired
created_atstring (date-time)required
updated_atstring (date-time)required

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such contact or company, or a company named in the body does not exist

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

PATCH /v1/contacts/{contact_id}

Change a contact

Change the fields you send; the rest are not touched. external_ids is merged key by key (a system's id is set, or forgotten with null). Giving the contact an email address, phone number or external id that another contact holds is 409 contact_exists with that contact's id, and nothing is changed.

Parameters

NameInTypeAbout
contact_idpathstring (uuid)required

The contact's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6

Request body application/json: CustomerPatchIn

{
  "external_ids": {
    "shop": "C-2000"
  },
  "organization_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "phone": "+47 911 22 333"
}
FieldTypeAbout
namestring or null

Never null

emailstring (email) or null

Another email address; null takes it away

phonestring or null
languagestring or null
customer_nostring or null
member_nostring or null
organization_idstring (uuid) or null

null takes them out of their company

external_idsmap of string or null

Merged key by key: a system's id is set, or forgotten with null. Systems you leave out are kept

200 Successful Response

Body application/json: Customer

{
  "case_counts": {
    "cases": 3,
    "last_contact_at": "2026-10-20T08:15:00Z",
    "open": 1
  },
  "created_at": "2026-09-02T10:00:00Z",
  "customer_no": "10442",
  "email": "kari@example.no",
  "external_ids": {
    "shop": "C-1042"
  },
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "name": "Kari Kunde",
  "organization_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "organization_name": "Fliser AS",
  "phone": "+4790000000",
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
emailstring or nullrequired

Lowercased. Unique in the workspace

phonestring or nullrequired

Digits with a leading +

languagestring or nullrequired

nb or en, when known

customer_nostring or nullrequired

Their customer number

member_nostring or nullrequired

Their member number

external_idsmap of stringrequired

Their ids in your own systems, by the system's name: {"shop": "C-1042"}

organization_idstring (uuid) or nullrequired

The company they belong to

organization_namestring or nullrequired
case_countsCaseCountsrequired
created_atstring (date-time)required
updated_atstring (date-time)required

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such contact or company, or a company named in the body does not exist

Body application/json: ErrorResponse

409 The email, phone number or external id is held by another, or the name is taken; `details` names the existing one

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/contacts/{contact_id}/cases

List a contact's cases

The cases this person is the customer of, newest first, in the same shape as GET /v1/cases. It needs the scope cases:read besides contacts:read, and a case in a group the key does not see is not listed.

Parameters

NameInTypeAbout
contact_idpathstring (uuid)required

The contact's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6
afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. eyJ2Ijog
limitqueryinteger

How many to return, at most

200 Successful Response

Body application/json: CaseList

{
  "items": [
    {
      "channel": "api",
      "contact": {
        "email": "kari@example.no",
        "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
        "language": "nb",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-09T09:00:00Z",
      "custom_fields": {},
      "external_ref": "order-12345",
      "group_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "last_message_at": "2026-11-09T09:00:00Z",
      "number": 1042,
      "priority": "normal",
      "source": "Returns page",
      "status": "new",
      "subject": "Cracked tiles in the delivery",
      "tags": [
        "delivery"
      ],
      "updated_at": "2026-11-09T09:00:00Z",
      "version": 1
    }
  ]
}
FieldTypeAbout
itemslist of Caserequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such contact or company, or a company named in the body does not exist

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/organizations

List or find companies

The workspace's companies, by name, a page at a time. external_system with external_id finds the one you know by its id in your own system.

Parameters

NameInTypeAbout
qquerystring or null

Words in the company's name


e.g. fliser
external_systemquerystring or null

With external_id: the system whose id you know, as you named it in external_ids


e.g. shop
external_idquerystring or null

With external_system: the id in that system


e.g. C-1042
afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. eyJ2Ijog
limitqueryinteger

How many to return, at most

200 Successful Response

Body application/json: OrganizationList

{
  "items": [
    {
      "case_counts": {
        "cases": 9,
        "last_contact_at": "2026-10-20T08:15:00Z",
        "open": 2
      },
      "created_at": "2026-09-02T10:00:00Z",
      "domains": [
        "fliser.no"
      ],
      "external_ids": {
        "crm": "ORG-77"
      },
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
      "name": "Fliser AS",
      "people": 4,
      "updated_at": "2026-10-20T08:15:00Z"
    }
  ]
}
FieldTypeAbout
itemslist of Organizationrequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/organizations

Create a company

Make a company. One with the same name, or the same id in external_ids, is not made twice: nothing is written and the answer is 409 organization_exists with the existing company's id in details.organization_id. Send an Idempotency-Key so that a retry makes nothing twice.

Parameters

NameInTypeAbout
Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

Request body application/json: OrganizationIn

{
  "domains": [
    "fliser.no"
  ],
  "external_ids": {
    "crm": "ORG-77"
  },
  "name": "Fliser AS"
}
FieldTypeAbout
namestringrequired
domainslist of string

Email domains; a new contact with an address there joins the company

external_idsmap of string

Its ids in your own systems, by the system's name

201 Successful Response

Body application/json: Organization

{
  "case_counts": {
    "cases": 9,
    "last_contact_at": "2026-10-20T08:15:00Z",
    "open": 2
  },
  "created_at": "2026-09-02T10:00:00Z",
  "domains": [
    "fliser.no"
  ],
  "external_ids": {
    "crm": "ORG-77"
  },
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "name": "Fliser AS",
  "people": 4,
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
domainslist of stringrequired

Email domains whose people belong here, lowercased

external_idsmap of stringrequired

Its ids in your own systems, by the system's name

peopleintegerrequired

How many contacts belong to it

case_countsCaseCountsrequired
created_atstring (date-time)required
updated_atstring (date-time)required

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

409 The email, phone number or external id is held by another, or the name is taken; `details` names the existing one

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/organizations/{organization_id}

Get a company

One company by id, with how many people belong to it and the counts of its cases the key sees.

Parameters

NameInTypeAbout
organization_idpathstring (uuid)required

The company's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9

200 Successful Response

Body application/json: Organization

{
  "case_counts": {
    "cases": 9,
    "last_contact_at": "2026-10-20T08:15:00Z",
    "open": 2
  },
  "created_at": "2026-09-02T10:00:00Z",
  "domains": [
    "fliser.no"
  ],
  "external_ids": {
    "crm": "ORG-77"
  },
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "name": "Fliser AS",
  "people": 4,
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
domainslist of stringrequired

Email domains whose people belong here, lowercased

external_idsmap of stringrequired

Its ids in your own systems, by the system's name

peopleintegerrequired

How many contacts belong to it

case_countsCaseCountsrequired
created_atstring (date-time)required
updated_atstring (date-time)required

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such contact or company, or a company named in the body does not exist

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

PATCH /v1/organizations/{organization_id}

Change a company

Change the fields you send; the rest are not touched. domains replaces the list; external_ids is merged key by key. A name or an external id that another company has is 409 organization_exists with that company's id, and nothing is changed.

Parameters

NameInTypeAbout
organization_idpathstring (uuid)required

The company's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9

Request body application/json: OrganizationPatchIn

{
  "domains": [
    "fliser.no",
    "fliser.com"
  ],
  "external_ids": {}
}
FieldTypeAbout
namestring or null

Never null

domainslist of string or null

Replaces the domains

external_idsmap of string or null

Merged key by key: set, or forgotten with null

200 Successful Response

Body application/json: Organization

{
  "case_counts": {
    "cases": 9,
    "last_contact_at": "2026-10-20T08:15:00Z",
    "open": 2
  },
  "created_at": "2026-09-02T10:00:00Z",
  "domains": [
    "fliser.no"
  ],
  "external_ids": {
    "crm": "ORG-77"
  },
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "name": "Fliser AS",
  "people": 4,
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
domainslist of stringrequired

Email domains whose people belong here, lowercased

external_idsmap of stringrequired

Its ids in your own systems, by the system's name

peopleintegerrequired

How many contacts belong to it

case_countsCaseCountsrequired
created_atstring (date-time)required
updated_atstring (date-time)required

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such contact or company, or a company named in the body does not exist

Body application/json: ErrorResponse

409 The email, phone number or external id is held by another, or the name is taken; `details` names the existing one

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/organizations/{organization_id}/contacts

List a company's contacts

The people who belong to the company, by name, a page at a time, in the shape of GET /v1/contacts.

Parameters

NameInTypeAbout
organization_idpathstring (uuid)required

The company's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9
afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. eyJ2Ijog
limitqueryinteger

How many to return, at most

200 Successful Response

Body application/json: CustomerList

{
  "items": [
    {
      "case_counts": {
        "cases": 3,
        "last_contact_at": "2026-10-20T08:15:00Z",
        "open": 1
      },
      "created_at": "2026-09-02T10:00:00Z",
      "customer_no": "10442",
      "email": "kari@example.no",
      "external_ids": {
        "shop": "C-1042"
      },
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "language": "nb",
      "name": "Kari Kunde",
      "organization_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
      "organization_name": "Fliser AS",
      "phone": "+4790000000",
      "updated_at": "2026-10-20T08:15:00Z"
    }
  ]
}
FieldTypeAbout
itemslist of Customerrequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such contact or company, or a company named in the body does not exist

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

Knowledge

GET /v1/articles

List or search articles

The workspace's articles, newest first, a page at a time. q is the search the app's Knowledge page uses: it matches words, not meaning, and a request never calls a model. An article an import has retired is not listed.

Parameters

NameInTypeAbout
qquerystring or null

Words to look for in the title and text, in Norwegian or English, or letters of the title


e.g. returfrist
statusquerydraft | published or null

Only drafts, or only published

audiencequeryinternal | public or null

Only public or only internal

languagequerystring or null

Only this language


e.g. nb
brand_idquerystring (uuid) or null

Only this brand's articles

topicquerystring or null

A case type's key; none is the articles with no topic


e.g. return
originquerywritten | gap | import or null

Only articles that came in this way

afterquerystring or null

The next_cursor of the previous page; leave out for the first


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6
limitqueryinteger

How many to return, at most

200 Successful Response

Body application/json: ArticleList

{
  "items": [
    {
      "audience": "public",
      "created_at": "2026-10-20T08:00:00Z",
      "excerpt": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "language": "nb",
      "origin": "written",
      "published_at": "2026-10-20T08:15:00Z",
      "status": "published",
      "title": "Returfrist for fliser",
      "topics": [
        "return"
      ],
      "updated_at": "2026-10-20T08:15:00Z"
    }
  ]
}
FieldTypeAbout
itemslist of ArticleSummaryrequired
next_cursorstring or null

Send as after for the next page; null on the last

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/articles

Create an article

Write an article. It is a draft and internal unless you say otherwise, and it is indexed for search as soon as it is saved, exactly as one written in the app is. The key is named in the workspace's audit log.

Send an Idempotency-Key so that a retry writes one article.

Parameters

NameInTypeAbout
Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

Request body application/json: ArticleIn

{
  "audience": "public",
  "body": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "language": "nb",
  "status": "published",
  "title": "Returfrist for fliser",
  "topics": [
    "return"
  ]
}
FieldTypeAbout
titlestringrequired

The title

bodystringrequired

The text, Markdown or plain. If it holds HTML tags, scripts, styles and anything else a page may not carry are removed before it is stored

languagestring or null

The language of the text. Left out, the workspace's default language

brand_idstring (uuid) or null

A brand of the workspace; null is every brand

audienceinternal | public

public lets the bot use it once published

statusdraft | published

published makes it usable at once

topicslist of string

Case types, by key, the article serves

201 Successful Response

Body application/json: Article

{
  "audience": "public",
  "body": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "created_at": "2026-10-20T08:00:00Z",
  "excerpt": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "origin": "written",
  "published_at": "2026-10-20T08:15:00Z",
  "status": "published",
  "title": "Returfrist for fliser",
  "topics": [
    "return"
  ],
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required

The article's id

titlestringrequired

The title

excerptstringrequired

The body's first 200 characters

languagestringrequired

The language of the text, such as nb or en

statusdraft | publishedrequired

draft is the team's alone; published can be used

audienceinternal | publicrequired

public articles may be used to answer customers; internal ones are for the team

originwritten | gap | importrequired

written by a person or a key, gap from a knowledge gap, import from a help centre

topicslist of stringrequired

The case types (by key) the article serves; empty is no topic

brand_idstring (uuid) or nullrequired

The brand it belongs to; null is every brand

source_urlstring or nullrequired

An imported page's address, else null

published_atstring (date-time) or nullrequired

When it was first published; null for a draft

created_atstring (date-time)required
updated_atstring (date-time)required
bodystringrequired

The text, as it is stored: Markdown or plain text

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/articles/{article_id}

Get an article

One article with its text. An id from another workspace, or of an imported page the site no longer has, is a 404.

Parameters

NameInTypeAbout
article_idpathstring (uuid)required

The article's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6

200 Successful Response

Body application/json: Article

{
  "audience": "public",
  "body": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "created_at": "2026-10-20T08:00:00Z",
  "excerpt": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "origin": "written",
  "published_at": "2026-10-20T08:15:00Z",
  "status": "published",
  "title": "Returfrist for fliser",
  "topics": [
    "return"
  ],
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required

The article's id

titlestringrequired

The title

excerptstringrequired

The body's first 200 characters

languagestringrequired

The language of the text, such as nb or en

statusdraft | publishedrequired

draft is the team's alone; published can be used

audienceinternal | publicrequired

public articles may be used to answer customers; internal ones are for the team

originwritten | gap | importrequired

written by a person or a key, gap from a knowledge gap, import from a help centre

topicslist of stringrequired

The case types (by key) the article serves; empty is no topic

brand_idstring (uuid) or nullrequired

The brand it belongs to; null is every brand

source_urlstring or nullrequired

An imported page's address, else null

published_atstring (date-time) or nullrequired

When it was first published; null for a draft

created_atstring (date-time)required
updated_atstring (date-time)required
bodystringrequired

The text, as it is stored: Markdown or plain text

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such article

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

PATCH /v1/articles/{article_id}

Change an article

Change what you send: title, text, language, brand, audience or topics. A change to the title or text is indexed again. Whether it is published is publish and unpublish.

Parameters

NameInTypeAbout
article_idpathstring (uuid)required

The article's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6

Request body application/json: ArticlePatch

{
  "audience": "public",
  "body": "Returfristen er 60 dager."
}
FieldTypeAbout
titlestring or null

A new title

bodystring or null

A new text; HTML is cleaned as on create

languagestring or null

A new language

brand_idstring (uuid) or null

A brand of the workspace, or null for every brand

audienceinternal | public or null

public or internal

topicslist of string or null

Replaces the case types the article serves

200 Successful Response

Body application/json: Article

{
  "audience": "public",
  "body": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "created_at": "2026-10-20T08:00:00Z",
  "excerpt": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "origin": "written",
  "published_at": "2026-10-20T08:15:00Z",
  "status": "published",
  "title": "Returfrist for fliser",
  "topics": [
    "return"
  ],
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required

The article's id

titlestringrequired

The title

excerptstringrequired

The body's first 200 characters

languagestringrequired

The language of the text, such as nb or en

statusdraft | publishedrequired

draft is the team's alone; published can be used

audienceinternal | publicrequired

public articles may be used to answer customers; internal ones are for the team

originwritten | gap | importrequired

written by a person or a key, gap from a knowledge gap, import from a help centre

topicslist of stringrequired

The case types (by key) the article serves; empty is no topic

brand_idstring (uuid) or nullrequired

The brand it belongs to; null is every brand

source_urlstring or nullrequired

An imported page's address, else null

published_atstring (date-time) or nullrequired

When it was first published; null for a draft

created_atstring (date-time)required
updated_atstring (date-time)required
bodystringrequired

The text, as it is stored: Markdown or plain text

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such article

Body application/json: ErrorResponse

422 The request did not validate

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/articles/{article_id}/publish

Publish an article

Make a draft usable: the team can insert it, and the bot can answer from it if its audience is public. Publishing a published article changes nothing.

Parameters

NameInTypeAbout
article_idpathstring (uuid)required

The article's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6
Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

200 Successful Response

Body application/json: Article

{
  "audience": "public",
  "body": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "created_at": "2026-10-20T08:00:00Z",
  "excerpt": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "origin": "written",
  "published_at": "2026-10-20T08:15:00Z",
  "status": "published",
  "title": "Returfrist for fliser",
  "topics": [
    "return"
  ],
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required

The article's id

titlestringrequired

The title

excerptstringrequired

The body's first 200 characters

languagestringrequired

The language of the text, such as nb or en

statusdraft | publishedrequired

draft is the team's alone; published can be used

audienceinternal | publicrequired

public articles may be used to answer customers; internal ones are for the team

originwritten | gap | importrequired

written by a person or a key, gap from a knowledge gap, import from a help centre

topicslist of stringrequired

The case types (by key) the article serves; empty is no topic

brand_idstring (uuid) or nullrequired

The brand it belongs to; null is every brand

source_urlstring or nullrequired

An imported page's address, else null

published_atstring (date-time) or nullrequired

When it was first published; null for a draft

created_atstring (date-time)required
updated_atstring (date-time)required
bodystringrequired

The text, as it is stored: Markdown or plain text

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such article

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/articles/{article_id}/unpublish

Unpublish an article

Take an article out of use: it becomes a draft again, and neither the team's search nor the bot finds it. Nothing is deleted, and publishing it again brings it back.

Parameters

NameInTypeAbout
article_idpathstring (uuid)required

The article's id


e.g. 0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6
Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

200 Successful Response

Body application/json: Article

{
  "audience": "public",
  "body": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "created_at": "2026-10-20T08:00:00Z",
  "excerpt": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "origin": "written",
  "published_at": "2026-10-20T08:15:00Z",
  "status": "published",
  "title": "Returfrist for fliser",
  "topics": [
    "return"
  ],
  "updated_at": "2026-10-20T08:15:00Z"
}
FieldTypeAbout
idstring (uuid)required

The article's id

titlestringrequired

The title

excerptstringrequired

The body's first 200 characters

languagestringrequired

The language of the text, such as nb or en

statusdraft | publishedrequired

draft is the team's alone; published can be used

audienceinternal | publicrequired

public articles may be used to answer customers; internal ones are for the team

originwritten | gap | importrequired

written by a person or a key, gap from a knowledge gap, import from a help centre

topicslist of stringrequired

The case types (by key) the article serves; empty is no topic

brand_idstring (uuid) or nullrequired

The brand it belongs to; null is every brand

source_urlstring or nullrequired

An imported page's address, else null

published_atstring (date-time) or nullrequired

When it was first published; null for a draft

created_atstring (date-time)required
updated_atstring (date-time)required
bodystringrequired

The text, as it is stored: Markdown or plain text

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope

Body application/json: ErrorResponse

404 No such article

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

Webhooks

GET /v1/webhook-endpoints

List webhook endpoints

The endpoints this key could have made: all of the workspace's when the key sees every group, otherwise those that hear only groups the key sees.

200 Successful Response

Body application/json: WebhookEndpointList

{
  "items": [
    {
      "all_groups": false,
      "created_at": "2026-11-09T12:00:00Z",
      "enabled": true,
      "events": [
        "case.created",
        "case.updated",
        "case.message.created"
      ],
      "group_ids": [
        "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
      ],
      "id": "0192f3a4-1111-7e2d-a3f4-5b6c7d8e9f01",
      "last_success_at": "2026-11-10T08:20:42Z",
      "name": "Returns page",
      "url": "https://returns.example.com/hooks/nolmo"
    }
  ]
}
FieldTypeAbout
itemslist of WebhookEndpointrequired

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `webhooks:manage`, or the groups

Body application/json: ErrorResponse

404 No such endpoint, or one the key could not have made

Body application/json: ErrorResponse

409 The endpoint is off, or the workspace is at its limit

Body application/json: ErrorResponse

422 Not accepted, e.g. `webhook_url_refused` (see `details.reason`)

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/webhook-endpoints

Make a webhook endpoint

Nolmo sends the chosen events to url as signed POSTs (see the webhooks section for the body and the signature). The address must be https and public; it is checked now and on every send. The answer holds the signing secret: it is shown only here.

An endpoint hears only the cases a key with its groups could read. A key can give only the groups it sees itself; all_groups needs a key that sees every group.

Parameters

NameInTypeAbout
Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

Request body application/json: WebhookEndpointCreate

{
  "all_groups": false,
  "events": [
    "case.created",
    "case.updated",
    "case.message.created"
  ],
  "group_ids": [
    "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
  ],
  "name": "Returns page",
  "url": "https://returns.example.com/hooks/nolmo"
}
FieldTypeAbout
namestringrequired

What the receiver is, for the list

urlstringrequired

https:// only, to a public address; no redirects are followed

eventslist of case.created | case.updated | case.message.created | case.note.created
all_groupsboolean
group_idslist of string (uuid)

201 Successful Response

Body application/json: WebhookEndpointCreated

{
  "all_groups": false,
  "created_at": "2026-11-09T12:00:00Z",
  "enabled": true,
  "events": [
    "case.created",
    "case.updated",
    "case.message.created"
  ],
  "group_ids": [
    "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
  ],
  "id": "0192f3a4-1111-7e2d-a3f4-5b6c7d8e9f01",
  "last_success_at": "2026-11-10T08:20:42Z",
  "name": "Returns page",
  "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw9o7cXx1b0hE=",
  "url": "https://returns.example.com/hooks/nolmo"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
urlstringrequired
eventslist of case.created | case.updated | case.message.created | case.note.createdrequired
enabledbooleanrequired
disabled_reasonfailing | manual or nullrequired

failing: it did not answer for three days; manual: someone turned it off

disabled_atstring (date-time) or nullrequired
all_groupsbooleanrequired

Hears every case, whatever its group

group_idslist of string (uuid)required

The groups it hears; empty with all_groups

previous_secret_untilstring (date-time) or nullrequired

While set, the secret that was rotated out still signs beside the new one, until this time

failing_sincestring (date-time) or nullrequired

Since when no send has been answered with a 2xx

last_success_atstring (date-time) or nullrequired
created_atstring (date-time)required
secretstringrequired

Signs every send (whsec_…). Shown here and never again; keep it where the receiver can read it

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `webhooks:manage`, or the groups

Body application/json: ErrorResponse

404 No such endpoint, or one the key could not have made

Body application/json: ErrorResponse

409 The endpoint is off, or the workspace is at its limit

Body application/json: ErrorResponse

422 Not accepted, e.g. `webhook_url_refused` (see `details.reason`)

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/webhook-endpoints/{endpoint_id}

Read a webhook endpoint

The endpoint's address, events, groups and state. The secret is never returned.

Parameters

NameInTypeAbout
endpoint_idpathstring (uuid)required

The webhook endpoint's id

200 Successful Response

Body application/json: WebhookEndpoint

{
  "all_groups": false,
  "created_at": "2026-11-09T12:00:00Z",
  "enabled": true,
  "events": [
    "case.created",
    "case.updated",
    "case.message.created"
  ],
  "group_ids": [
    "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
  ],
  "id": "0192f3a4-1111-7e2d-a3f4-5b6c7d8e9f01",
  "last_success_at": "2026-11-10T08:20:42Z",
  "name": "Returns page",
  "url": "https://returns.example.com/hooks/nolmo"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
urlstringrequired
eventslist of case.created | case.updated | case.message.created | case.note.createdrequired
enabledbooleanrequired
disabled_reasonfailing | manual or nullrequired

failing: it did not answer for three days; manual: someone turned it off

disabled_atstring (date-time) or nullrequired
all_groupsbooleanrequired

Hears every case, whatever its group

group_idslist of string (uuid)required

The groups it hears; empty with all_groups

previous_secret_untilstring (date-time) or nullrequired

While set, the secret that was rotated out still signs beside the new one, until this time

failing_sincestring (date-time) or nullrequired

Since when no send has been answered with a 2xx

last_success_atstring (date-time) or nullrequired
created_atstring (date-time)required

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `webhooks:manage`, or the groups

Body application/json: ErrorResponse

404 No such endpoint, or one the key could not have made

Body application/json: ErrorResponse

409 The endpoint is off, or the workspace is at its limit

Body application/json: ErrorResponse

422 Not accepted, e.g. `webhook_url_refused` (see `details.reason`)

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

PATCH /v1/webhook-endpoints/{endpoint_id}

Change a webhook endpoint

Only what is sent changes. enabled: true turns a stopped endpoint on again and sends what waited, as long as it is within 24 hours.

Parameters

NameInTypeAbout
endpoint_idpathstring (uuid)required

The webhook endpoint's id

Request body application/json: WebhookEndpointUpdate

{
  "enabled": false
}
FieldTypeAbout
namestring or null
urlstring or null
eventslist of case.created | case.updated | case.message.created | case.note.created or null
enabledboolean or null
all_groupsboolean or null
group_idslist of string (uuid) or null

200 Successful Response

Body application/json: WebhookEndpoint

{
  "all_groups": false,
  "created_at": "2026-11-09T12:00:00Z",
  "enabled": true,
  "events": [
    "case.created",
    "case.updated",
    "case.message.created"
  ],
  "group_ids": [
    "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
  ],
  "id": "0192f3a4-1111-7e2d-a3f4-5b6c7d8e9f01",
  "last_success_at": "2026-11-10T08:20:42Z",
  "name": "Returns page",
  "url": "https://returns.example.com/hooks/nolmo"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
urlstringrequired
eventslist of case.created | case.updated | case.message.created | case.note.createdrequired
enabledbooleanrequired
disabled_reasonfailing | manual or nullrequired

failing: it did not answer for three days; manual: someone turned it off

disabled_atstring (date-time) or nullrequired
all_groupsbooleanrequired

Hears every case, whatever its group

group_idslist of string (uuid)required

The groups it hears; empty with all_groups

previous_secret_untilstring (date-time) or nullrequired

While set, the secret that was rotated out still signs beside the new one, until this time

failing_sincestring (date-time) or nullrequired

Since when no send has been answered with a 2xx

last_success_atstring (date-time) or nullrequired
created_atstring (date-time)required

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `webhooks:manage`, or the groups

Body application/json: ErrorResponse

404 No such endpoint, or one the key could not have made

Body application/json: ErrorResponse

409 The endpoint is off, or the workspace is at its limit

Body application/json: ErrorResponse

422 Not accepted, e.g. `webhook_url_refused` (see `details.reason`)

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

DELETE /v1/webhook-endpoints/{endpoint_id}

Delete a webhook endpoint

The endpoint and its delivery log are gone, and nothing more is sent to it.

Parameters

NameInTypeAbout
endpoint_idpathstring (uuid)required

The webhook endpoint's id

204 Successful Response

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `webhooks:manage`, or the groups

Body application/json: ErrorResponse

404 No such endpoint, or one the key could not have made

Body application/json: ErrorResponse

409 The endpoint is off, or the workspace is at its limit

Body application/json: ErrorResponse

422 Not accepted, e.g. `webhook_url_refused` (see `details.reason`)

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/webhook-endpoints/{endpoint_id}/rotate-secret

Rotate an endpoint's secret

A new secret, shown only in this answer. The old one keeps signing for 24 hours beside it (webhook-signature then holds both), so the receiver can change over without a gap.

Parameters

NameInTypeAbout
endpoint_idpathstring (uuid)required

The webhook endpoint's id

Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

200 Successful Response

Body application/json: WebhookEndpointCreated

{
  "all_groups": false,
  "created_at": "2026-11-09T12:00:00Z",
  "enabled": true,
  "events": [
    "case.created",
    "case.updated",
    "case.message.created"
  ],
  "group_ids": [
    "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
  ],
  "id": "0192f3a4-1111-7e2d-a3f4-5b6c7d8e9f01",
  "last_success_at": "2026-11-10T08:20:42Z",
  "name": "Returns page",
  "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw9o7cXx1b0hE=",
  "url": "https://returns.example.com/hooks/nolmo"
}
FieldTypeAbout
idstring (uuid)required
namestringrequired
urlstringrequired
eventslist of case.created | case.updated | case.message.created | case.note.createdrequired
enabledbooleanrequired
disabled_reasonfailing | manual or nullrequired

failing: it did not answer for three days; manual: someone turned it off

disabled_atstring (date-time) or nullrequired
all_groupsbooleanrequired

Hears every case, whatever its group

group_idslist of string (uuid)required

The groups it hears; empty with all_groups

previous_secret_untilstring (date-time) or nullrequired

While set, the secret that was rotated out still signs beside the new one, until this time

failing_sincestring (date-time) or nullrequired

Since when no send has been answered with a 2xx

last_success_atstring (date-time) or nullrequired
created_atstring (date-time)required
secretstringrequired

Signs every send (whsec_…). Shown here and never again; keep it where the receiver can read it

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `webhooks:manage`, or the groups

Body application/json: ErrorResponse

404 No such endpoint, or one the key could not have made

Body application/json: ErrorResponse

409 The endpoint is off, or the workspace is at its limit

Body application/json: ErrorResponse

422 Not accepted, e.g. `webhook_url_refused` (see `details.reason`)

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/webhook-endpoints/{endpoint_id}/deliveries

The delivery log

What was sent to the endpoint in the last 30 days, newest first: the event, how many attempts, the receiver's last status code. Page with cursor.

Parameters

NameInTypeAbout
endpoint_idpathstring (uuid)required

The webhook endpoint's id

cursorquerystring or null

next_cursor of the previous page

limitqueryinteger

How many deliveries to return

statusquerypending | delivered | failed or null

Only deliveries in this state

200 Successful Response

Body application/json: WebhookDeliveryList

{
  "items": [
    {
      "attempts": 1,
      "case_number": 1042,
      "delivered_at": "2026-11-10T08:20:42Z",
      "event": "case.message.created",
      "id": "0192f3a4-2222-7e2d-a3f4-5b6c7d8e9f01",
      "last_attempt_at": "2026-11-10T08:20:42Z",
      "occurred_at": "2026-11-10T08:20:41Z",
      "response_code": 200,
      "status": "delivered"
    }
  ]
}
FieldTypeAbout
itemslist of WebhookDeliveryrequired
next_cursorstring or nullrequired

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `webhooks:manage`, or the groups

Body application/json: ErrorResponse

404 No such endpoint, or one the key could not have made

Body application/json: ErrorResponse

409 The endpoint is off, or the workspace is at its limit

Body application/json: ErrorResponse

422 Not accepted, e.g. `webhook_url_refused` (see `details.reason`)

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/webhook-endpoints/{endpoint_id}/deliveries/{delivery_id}/resend

Send one event again

The same event with the same webhook-id, as a new series of attempts. The receiver dedupes by that id.

Parameters

NameInTypeAbout
endpoint_idpathstring (uuid)required

The webhook endpoint's id

delivery_idpathstring (uuid)required

The delivery's id, as the receiver got it in webhook-id

Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

200 Successful Response

Body application/json: WebhookDelivery

{
  "attempts": 1,
  "case_number": 1042,
  "delivered_at": "2026-11-10T08:20:42Z",
  "event": "case.message.created",
  "id": "0192f3a4-2222-7e2d-a3f4-5b6c7d8e9f01",
  "last_attempt_at": "2026-11-10T08:20:42Z",
  "occurred_at": "2026-11-10T08:20:41Z",
  "response_code": 200,
  "status": "delivered"
}
FieldTypeAbout
idstring (uuid)required

The event's id, as the receiver got it in webhook-id

eventcase.created | case.updated | case.message.created | case.note.createdrequired
case_numberintegerrequired
statuspending | delivered | failedrequired

pending: to be sent or being retried; delivered: answered 2xx; failed: given up after 24 hours

attemptsintegerrequired
response_codeinteger or nullrequired

The receiver's last status code, when it answered

errorstring or nullrequired

Why the last attempt failed when there was no answer: timeout, unreachable, address_not_public, …

occurred_atstring (date-time)required
last_attempt_atstring (date-time) or nullrequired
next_attempt_atstring (date-time) or nullrequired
delivered_atstring (date-time) or nullrequired

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `webhooks:manage`, or the groups

Body application/json: ErrorResponse

404 No such endpoint, or one the key could not have made

Body application/json: ErrorResponse

409 The endpoint is off, or the workspace is at its limit

Body application/json: ErrorResponse

422 Not accepted, e.g. `webhook_url_refused` (see `details.reason`)

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

POST /v1/webhook-endpoints/{endpoint_id}/replay

Send everything since a time again

Every event in the log since since, delivered or not, again and in order, each with its own webhook-id. For a receiver that was down for longer than the 24 hours events are retried. Only the last 30 days are kept.

Parameters

NameInTypeAbout
endpoint_idpathstring (uuid)required

The webhook endpoint's id

Idempotency-Keyheaderstring or null

A value you make up, such as a UUID, for this one request. Send the same key again, after a timeout or a crash, and you get the first answer back (Idempotent-Replayed: true) and nothing is written twice. The same key with another body is 422 idempotency_key_reused. Kept for 24 hours


e.g. 7c9e6679-7425-40de-944b-e07fc1f90ae7

Request body application/json: Replay

{
  "since": "2026-11-10T08:00:00Z"
}
FieldTypeAbout
sincestring (date-time)required

200 Successful Response

Body application/json: Replayed

{
  "queued": 12
}
FieldTypeAbout
queuedintegerrequired

How many events will be sent again

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `webhooks:manage`, or the groups

Body application/json: ErrorResponse

404 No such endpoint, or one the key could not have made

Body application/json: ErrorResponse

409 The endpoint is off, or the workspace is at its limit

Body application/json: ErrorResponse

422 Not accepted, e.g. `webhook_url_refused` (see `details.reason`)

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

Lookups

GET /v1/groups

List groups

The workspace's groups, by name. A key limited to some groups sees those only. Use an id as group_id when creating a case or searching.

200 Successful Response

Body application/json: GroupList

{
  "items": [
    {
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "name": "Support"
    }
  ]
}
FieldTypeAbout
itemslist of Grouprequired

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `cases:read`

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/agents

List the team

The people who work in the workspace: an id, a first name and the groups they are in. No surname, email or phone number is shared. Use an id as assignee_id when searching.

A key limited to some groups sees the people who are in one of them, and only which of the key's groups they are in.

200 Successful Response

Body application/json: AgentList

{
  "items": [
    {
      "first_name": "Astrid",
      "group_ids": [
        "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6"
      ],
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6"
    }
  ]
}
FieldTypeAbout
itemslist of Agentrequired

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `cases:read`

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/case-types

List case types

The workspace's case types: what type takes when you create a case, and what type filters on in a search. Only the ones in use.

200 Successful Response

Body application/json: CaseTypeList

{
  "items": [
    {
      "key": "return",
      "name": "Return"
    }
  ]
}
FieldTypeAbout
itemslist of CaseTyperequired

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `cases:read`

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/case-fields

List case fields

The workspace's own fields on a case: the key to use in custom_fields, what it is called, its kind, and for a select its choices.

200 Successful Response

Body application/json: CaseFieldList

{
  "items": [
    {
      "key": "order_no",
      "kind": "text",
      "label": "Order number",
      "options": []
    },
    {
      "key": "carrier",
      "kind": "select",
      "label": "Carrier",
      "options": [
        {
          "label": "Posten",
          "value": "posten"
        },
        {
          "label": "DHL",
          "value": "dhl"
        }
      ]
    }
  ]
}
FieldTypeAbout
itemslist of CaseFieldrequired

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `cases:read`

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

GET /v1/tags

List tags

The tags on the cases the key sees, with how many cases have each, the most used first. A key limited to some groups counts its own cases only.

200 Successful Response

Body application/json: TagList

{
  "items": [
    {
      "count": 12,
      "tag": "delivery"
    }
  ]
}
FieldTypeAbout
itemslist of Tagrequired

401 No key, or a key that is wrong, revoked or expired

Body application/json: ErrorResponse

403 The key lacks the scope `cases:read`

Body application/json: ErrorResponse

429 Over the limit; see `Retry-After`

Body application/json: ErrorResponse

Webhook events

POST case.created

case.created

A case was made.

Nolmo sends a POST with a JSON body and these headers:

Answer with any 2xx within ten seconds. Anything else, or no answer, is retried with a growing pause for 24 hours; events of one case arrive in order. An endpoint that answers nothing for three days is turned off and the workspace's admins are told. Redirects are not followed.

import base64, hashlib, hmac, time

def verify(secret: str, headers: dict, body: bytes, tolerance: int = 300) -> bool:
    """True when `body` was signed with `secret` (the whsec_ value) and is fresh."""
    msg_id = headers["webhook-id"]
    timestamp = headers["webhook-timestamp"]
    if abs(time.time() - int(timestamp)) > tolerance:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{timestamp}.".encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    return any(
        hmac.compare_digest(expected, part.removeprefix("v1,"))
        for part in headers["webhook-signature"].split()
        if part.startswith("v1,")
    )

Request body application/json: CaseCreatedEvent

{
  "created_at": "2026-11-10T08:15:00Z",
  "data": {
    "case": {
      "assignee": "Astrid",
      "channel": "email",
      "contact": {
        "email": "kari@example.com",
        "id": "7c2e4a1b-9d3f-4f6a-8b5e-1a2b3c4d5e6f",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-10T08:15:00Z",
      "id": "5d0b8f0e-2a4c-7d3e-9c1a-3b6f2e8a1c42",
      "number": 1042,
      "priority": "high",
      "status": "open",
      "subject": "Cracked tile in the delivery",
      "tags": [
        "delivery",
        "tiles"
      ],
      "type": "complaint",
      "updated_at": "2026-11-10T08:20:41Z"
    }
  },
  "id": "0192f3a4-3333-7e2d-a3f4-5b6c7d8e9f01",
  "type": "case.created"
}
FieldTypeAbout
idstring (uuid)required

This event for this endpoint, the same on every retry and every replay: the receiver's key to dedupe by

created_atstring (date-time)required

When it happened, not when it was sent

typecase.created
dataCaseDatarequired

200 Successful Response

Body application/json: any

POST case.updated

case.updated

A case's status, assignee, priority or type changed (one event per change).

Nolmo sends a POST with a JSON body and these headers:

Answer with any 2xx within ten seconds. Anything else, or no answer, is retried with a growing pause for 24 hours; events of one case arrive in order. An endpoint that answers nothing for three days is turned off and the workspace's admins are told. Redirects are not followed.

import base64, hashlib, hmac, time

def verify(secret: str, headers: dict, body: bytes, tolerance: int = 300) -> bool:
    """True when `body` was signed with `secret` (the whsec_ value) and is fresh."""
    msg_id = headers["webhook-id"]
    timestamp = headers["webhook-timestamp"]
    if abs(time.time() - int(timestamp)) > tolerance:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{timestamp}.".encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    return any(
        hmac.compare_digest(expected, part.removeprefix("v1,"))
        for part in headers["webhook-signature"].split()
        if part.startswith("v1,")
    )

Request body application/json: CaseUpdatedEvent

{
  "created_at": "2026-11-10T08:18:00Z",
  "data": {
    "case": {
      "assignee": "Astrid",
      "channel": "email",
      "contact": {
        "email": "kari@example.com",
        "id": "7c2e4a1b-9d3f-4f6a-8b5e-1a2b3c4d5e6f",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-10T08:15:00Z",
      "id": "5d0b8f0e-2a4c-7d3e-9c1a-3b6f2e8a1c42",
      "number": 1042,
      "priority": "high",
      "status": "open",
      "subject": "Cracked tile in the delivery",
      "tags": [
        "delivery",
        "tiles"
      ],
      "type": "complaint",
      "updated_at": "2026-11-10T08:20:41Z"
    },
    "changes": [
      {
        "field": "assignee",
        "to": "Astrid"
      }
    ]
  },
  "id": "0192f3a4-4444-7e2d-a3f4-5b6c7d8e9f01",
  "type": "case.updated"
}
FieldTypeAbout
idstring (uuid)required

This event for this endpoint, the same on every retry and every replay: the receiver's key to dedupe by

created_atstring (date-time)required

When it happened, not when it was sent

typecase.updated
dataCaseUpdatedDatarequired

200 Successful Response

Body application/json: any

POST case.message.created

case.message.created

A reply the customer sees was added to a case.

Nolmo sends a POST with a JSON body and these headers:

Answer with any 2xx within ten seconds. Anything else, or no answer, is retried with a growing pause for 24 hours; events of one case arrive in order. An endpoint that answers nothing for three days is turned off and the workspace's admins are told. Redirects are not followed.

import base64, hashlib, hmac, time

def verify(secret: str, headers: dict, body: bytes, tolerance: int = 300) -> bool:
    """True when `body` was signed with `secret` (the whsec_ value) and is fresh."""
    msg_id = headers["webhook-id"]
    timestamp = headers["webhook-timestamp"]
    if abs(time.time() - int(timestamp)) > tolerance:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{timestamp}.".encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    return any(
        hmac.compare_digest(expected, part.removeprefix("v1,"))
        for part in headers["webhook-signature"].split()
        if part.startswith("v1,")
    )

Request body application/json: CaseMessageCreatedEvent

{
  "created_at": "2026-11-10T08:20:41Z",
  "data": {
    "case": {
      "assignee": "Astrid",
      "channel": "email",
      "contact": {
        "email": "kari@example.com",
        "id": "7c2e4a1b-9d3f-4f6a-8b5e-1a2b3c4d5e6f",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-10T08:15:00Z",
      "id": "5d0b8f0e-2a4c-7d3e-9c1a-3b6f2e8a1c42",
      "number": 1042,
      "priority": "high",
      "status": "open",
      "subject": "Cracked tile in the delivery",
      "tags": [
        "delivery",
        "tiles"
      ],
      "type": "complaint",
      "updated_at": "2026-11-10T08:20:41Z"
    },
    "message": {
      "author_kind": "customer",
      "created_at": "2026-11-10T08:20:41Z",
      "id": "0192f3a4-7b1c-7e2d-a3f4-5b6c7d8e9f01",
      "kind": "public",
      "text": "Hi, three of the tiles in the pallet were cracked."
    }
  },
  "id": "0192f3a4-5555-7e2d-a3f4-5b6c7d8e9f01",
  "type": "case.message.created"
}
FieldTypeAbout
idstring (uuid)required

This event for this endpoint, the same on every retry and every replay: the receiver's key to dedupe by

created_atstring (date-time)required

When it happened, not when it was sent

typecase.message.created
dataMessageDatarequired

200 Successful Response

Body application/json: any

POST case.note.created

case.note.created

An internal note was added to a case. Only sent to an endpoint that asks for it.

Nolmo sends a POST with a JSON body and these headers:

Answer with any 2xx within ten seconds. Anything else, or no answer, is retried with a growing pause for 24 hours; events of one case arrive in order. An endpoint that answers nothing for three days is turned off and the workspace's admins are told. Redirects are not followed.

import base64, hashlib, hmac, time

def verify(secret: str, headers: dict, body: bytes, tolerance: int = 300) -> bool:
    """True when `body` was signed with `secret` (the whsec_ value) and is fresh."""
    msg_id = headers["webhook-id"]
    timestamp = headers["webhook-timestamp"]
    if abs(time.time() - int(timestamp)) > tolerance:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{timestamp}.".encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    return any(
        hmac.compare_digest(expected, part.removeprefix("v1,"))
        for part in headers["webhook-signature"].split()
        if part.startswith("v1,")
    )

Request body application/json: CaseNoteCreatedEvent

{
  "created_at": "2026-11-10T08:22:00Z",
  "data": {
    "case": {
      "assignee": "Astrid",
      "channel": "email",
      "contact": {
        "email": "kari@example.com",
        "id": "7c2e4a1b-9d3f-4f6a-8b5e-1a2b3c4d5e6f",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-10T08:15:00Z",
      "id": "5d0b8f0e-2a4c-7d3e-9c1a-3b6f2e8a1c42",
      "number": 1042,
      "priority": "high",
      "status": "open",
      "subject": "Cracked tile in the delivery",
      "tags": [
        "delivery",
        "tiles"
      ],
      "type": "complaint",
      "updated_at": "2026-11-10T08:20:41Z"
    },
    "message": {
      "author_kind": "team",
      "created_at": "2026-11-10T08:20:41Z",
      "id": "0192f3a4-7b1c-7e2d-a3f4-5b6c7d8e9f01",
      "kind": "internal",
      "text": "Replacement tiles ordered."
    }
  },
  "id": "0192f3a4-6666-7e2d-a3f4-5b6c7d8e9f01",
  "type": "case.note.created"
}
FieldTypeAbout
idstring (uuid)required

This event for this endpoint, the same on every retry and every replay: the receiver's key to dedupe by

created_atstring (date-time)required

When it happened, not when it was sent

typecase.note.created
dataMessageDatarequired

200 Successful Response

Body application/json: any

Schemas

Agent

FieldTypeAbout
idstring (uuid)required

Use it as assignee_id when searching

first_namestringrequired

The first name only: no surname, email or phone number is shared

group_idslist of string (uuid)required

The groups the person is in. A key limited to some groups sees those of them only

{
  "first_name": "Astrid",
  "group_ids": [
    "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6"
  ],
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6"
}

AgentList

FieldTypeAbout
itemslist of Agentrequired
{
  "items": [
    {
      "first_name": "Astrid",
      "group_ids": [
        "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6"
      ],
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6"
    }
  ]
}

Article

One article, with its text.

FieldTypeAbout
idstring (uuid)required

The article's id

titlestringrequired

The title

excerptstringrequired

The body's first 200 characters

languagestringrequired

The language of the text, such as nb or en

statusdraft | publishedrequired

draft is the team's alone; published can be used

audienceinternal | publicrequired

public articles may be used to answer customers; internal ones are for the team

originwritten | gap | importrequired

written by a person or a key, gap from a knowledge gap, import from a help centre

topicslist of stringrequired

The case types (by key) the article serves; empty is no topic

brand_idstring (uuid) or nullrequired

The brand it belongs to; null is every brand

source_urlstring or nullrequired

An imported page's address, else null

published_atstring (date-time) or nullrequired

When it was first published; null for a draft

created_atstring (date-time)required
updated_atstring (date-time)required
bodystringrequired

The text, as it is stored: Markdown or plain text

{
  "audience": "public",
  "body": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "created_at": "2026-10-20T08:00:00Z",
  "excerpt": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "origin": "written",
  "published_at": "2026-10-20T08:15:00Z",
  "status": "published",
  "title": "Returfrist for fliser",
  "topics": [
    "return"
  ],
  "updated_at": "2026-10-20T08:15:00Z"
}

ArticleIn

A new article. It is a draft until you publish it, and an internal one until you say public.

FieldTypeAbout
titlestringrequired

The title

bodystringrequired

The text, Markdown or plain. If it holds HTML tags, scripts, styles and anything else a page may not carry are removed before it is stored

languagestring or null

The language of the text. Left out, the workspace's default language

brand_idstring (uuid) or null

A brand of the workspace; null is every brand

audienceinternal | public

public lets the bot use it once published

statusdraft | published

published makes it usable at once

topicslist of string

Case types, by key, the article serves

{
  "audience": "public",
  "body": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
  "language": "nb",
  "status": "published",
  "title": "Returfrist for fliser",
  "topics": [
    "return"
  ]
}

ArticleList

A page of articles, newest first.

FieldTypeAbout
itemslist of ArticleSummaryrequired
next_cursorstring or null

Send as after for the next page; null on the last

{
  "items": [
    {
      "audience": "public",
      "created_at": "2026-10-20T08:00:00Z",
      "excerpt": "Returfristen er 30 dager. Send varene uåpnet i retur sammen med kvitteringen.",
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "language": "nb",
      "origin": "written",
      "published_at": "2026-10-20T08:15:00Z",
      "status": "published",
      "title": "Returfrist for fliser",
      "topics": [
        "return"
      ],
      "updated_at": "2026-10-20T08:15:00Z"
    }
  ]
}

ArticlePatch

Only what is sent changes. brand_id: null clears the brand; topics replaces the list whole. Publishing and unpublishing have their own routes.

FieldTypeAbout
titlestring or null

A new title

bodystring or null

A new text; HTML is cleaned as on create

languagestring or null

A new language

brand_idstring (uuid) or null

A brand of the workspace, or null for every brand

audienceinternal | public or null

public or internal

topicslist of string or null

Replaces the case types the article serves

{
  "audience": "public",
  "body": "Returfristen er 60 dager."
}

ArticleSummary

An article without its body, as the list shows it.

FieldTypeAbout
idstring (uuid)required

The article's id

titlestringrequired

The title

excerptstringrequired

The body's first 200 characters

languagestringrequired

The language of the text, such as nb or en

statusdraft | publishedrequired

draft is the team's alone; published can be used

audienceinternal | publicrequired

public articles may be used to answer customers; internal ones are for the team

originwritten | gap | importrequired

written by a person or a key, gap from a knowledge gap, import from a help centre

topicslist of stringrequired

The case types (by key) the article serves; empty is no topic

brand_idstring (uuid) or nullrequired

The brand it belongs to; null is every brand

source_urlstring or nullrequired

An imported page's address, else null

published_atstring (date-time) or nullrequired

When it was first published; null for a draft

created_atstring (date-time)required
updated_atstring (date-time)required

Attachment

FieldTypeAbout
idstringrequired

Use it in attachments of a message

namestringrequired
content_typestringrequired
sizeintegerrequired

Bytes

Author

FieldTypeAbout
typecustomer | team | api | airequired

customer: the person the case is about. team: someone in the workspace, by first name. api: a key, by its name. ai: the workspace's AI

namestring or nullrequired

Body_upload_file_v1_uploads_post

FieldTypeAbout
filestringrequired

Case

A case, as far as a key may see it.

FieldTypeAbout
numberintegerrequired

The case's number in the workspace; the id in the paths

subjectstringrequired
statusnew | open | pending | hold | solved | closedrequired

new, open, pending, hold, solved or closed

prioritylow | normal | high | urgentrequired
typestring or nullrequired

The case type's key

tagslist of stringrequired
custom_fieldsobjectrequired
external_refstring or nullrequired

Your own id for the case, if it has one

sourcestring or nullrequired

The source the external_ref belongs to

channelstringrequired

How the case arrived: api for one made here

group_idstring (uuid) or nullrequired
brand_idstring (uuid) or nullrequired
assignee_first_namestring or nullrequired

The first name of the person who has the case

contactContactrequired
created_atstring (date-time)required
updated_atstring (date-time)required
solved_atstring (date-time) or nullrequired
last_message_atstring (date-time) or nullrequired
versionintegerrequired

Send it back in version of a PATCH

{
  "channel": "api",
  "contact": {
    "email": "kari@example.no",
    "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
    "language": "nb",
    "name": "Kari Kunde"
  },
  "created_at": "2026-11-09T09:00:00Z",
  "custom_fields": {},
  "external_ref": "order-12345",
  "group_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "last_message_at": "2026-11-09T09:00:00Z",
  "number": 1042,
  "priority": "normal",
  "source": "Returns page",
  "status": "new",
  "subject": "Cracked tiles in the delivery",
  "tags": [
    "delivery"
  ],
  "updated_at": "2026-11-09T09:00:00Z",
  "version": 1
}

CaseCounts

How many cases a person or a company has, counting only the cases the key may see.

FieldTypeAbout
casesintegerrequired

Cases, spam left out

openintegerrequired

Of those, the ones still with the team: new, open, pending or on hold

last_contact_atstring (date-time) or nullrequired

When they last wrote or were written to

CaseCreatedEvent

A case was made, by a person, a customer's message or another system.

FieldTypeAbout
idstring (uuid)required

This event for this endpoint, the same on every retry and every replay: the receiver's key to dedupe by

created_atstring (date-time)required

When it happened, not when it was sent

typecase.created
dataCaseDatarequired
{
  "created_at": "2026-11-10T08:15:00Z",
  "data": {
    "case": {
      "assignee": "Astrid",
      "channel": "email",
      "contact": {
        "email": "kari@example.com",
        "id": "7c2e4a1b-9d3f-4f6a-8b5e-1a2b3c4d5e6f",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-10T08:15:00Z",
      "id": "5d0b8f0e-2a4c-7d3e-9c1a-3b6f2e8a1c42",
      "number": 1042,
      "priority": "high",
      "status": "open",
      "subject": "Cracked tile in the delivery",
      "tags": [
        "delivery",
        "tiles"
      ],
      "type": "complaint",
      "updated_at": "2026-11-10T08:20:41Z"
    }
  },
  "id": "0192f3a4-3333-7e2d-a3f4-5b6c7d8e9f01",
  "type": "case.created"
}

CaseData

FieldTypeAbout
casePublicCaserequired

CaseEvent

FieldTypeAbout
idstring (uuid)required
kindcreated | customer_replied | team_replied | status_changed | reopened | assigned | priority_changed | type_changedrequired

created; customer_replied and team_replied (message_id says which message); status_changed, reopened, priority_changed and type_changed (from and to); assigned (from and to are first names, null for nobody). More kinds may be added: ignore the ones you do not know

actorEventActorrequired
fromstring or null

The value before, where there is one

tostring or null

The value after, where there is one

message_idstring (uuid) or null

The message, for a reply

atstring (date-time)required
{
  "actor": {
    "name": "Astrid",
    "type": "team"
  },
  "at": "2026-11-09T11:02:00Z",
  "from": "open",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "kind": "status_changed",
  "to": "solved"
}

CaseEventList

FieldTypeAbout
itemslist of CaseEventrequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

{
  "items": [
    {
      "actor": {
        "name": "Astrid",
        "type": "team"
      },
      "at": "2026-11-09T11:02:00Z",
      "from": "open",
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "kind": "status_changed",
      "to": "solved"
    }
  ]
}

CaseField

FieldTypeAbout
keystringrequired

The key in a case's custom_fields

labelstringrequired
kindtext | number | select | date | checkboxrequired

text, number, select, date or checkbox

optionslist of FieldOptionrequired

The choices of a select; empty for the other kinds

{
  "key": "order_no",
  "kind": "text",
  "label": "Order number",
  "options": []
}

CaseFieldList

FieldTypeAbout
itemslist of CaseFieldrequired
{
  "items": [
    {
      "key": "order_no",
      "kind": "text",
      "label": "Order number",
      "options": []
    },
    {
      "key": "carrier",
      "kind": "select",
      "label": "Carrier",
      "options": [
        {
          "label": "Posten",
          "value": "posten"
        },
        {
          "label": "DHL",
          "value": "dhl"
        }
      ]
    }
  ]
}

CaseFile

FieldTypeAbout
idstringrequired

Download it with GET /v1/attachments/{id}

namestringrequired
content_typestringrequired
sizeintegerrequired

Bytes

message_idstring (uuid)required

The message it hangs on

onmessage | noterequired

message: on a public message. note: on an internal note, listed only when asked for

created_atstring (date-time)required

When the message was written

{
  "content_type": "image/jpeg",
  "created_at": "2026-11-09T10:30:00Z",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "message_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "name": "damaged-tiles.jpg",
  "on": "message",
  "size": 183204
}

CaseFileList

FieldTypeAbout
itemslist of CaseFilerequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

{
  "items": [
    {
      "content_type": "image/jpeg",
      "created_at": "2026-11-09T10:30:00Z",
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "message_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "name": "damaged-tiles.jpg",
      "on": "message",
      "size": 183204
    }
  ]
}

CaseIn

A new case: a person, a subject and what they wrote. It is routed by the workspace's triggers, gets its clocks and is assessed, exactly as a mail would.

FieldTypeAbout
contactContactInrequired
subjectstringrequired

The case's title

messageFirstMessageInrequired
prioritylow | normal | high | urgent

low, normal, high or urgent

typestring or null

One of the workspace's case types, by its key

tagslist of string

Labels to put on the case

brand_idstring (uuid) or null

The brand or store the case belongs to

group_idstring (uuid) or null

The group the case goes to. It must be one of the key's groups (GET /v1/me); left out, the workspace's rules choose

custom_fieldsobject

Values for the workspace's case fields, by the field's key

external_refstring or null

Your own id for the case. A second create with the same id from the same source is refused with 409 external_ref_taken and the number of the case that has it

sourcestring or null

What you call your system, such as Contact form. external_ref is unique within it. Left out, it is the key's name

{
  "contact": {
    "email": "kari@example.no",
    "name": "Kari Kunde"
  },
  "external_ref": "order-12345",
  "message": {
    "body_text": "Three of the tiles in the pack were cracked. Order 12345."
  },
  "priority": "normal",
  "source": "Returns page",
  "subject": "Cracked tiles in the delivery",
  "tags": [
    "delivery"
  ]
}

CaseList

FieldTypeAbout
itemslist of Caserequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

{
  "items": [
    {
      "channel": "api",
      "contact": {
        "email": "kari@example.no",
        "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
        "language": "nb",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-09T09:00:00Z",
      "custom_fields": {},
      "external_ref": "order-12345",
      "group_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "last_message_at": "2026-11-09T09:00:00Z",
      "number": 1042,
      "priority": "normal",
      "source": "Returns page",
      "status": "new",
      "subject": "Cracked tiles in the delivery",
      "tags": [
        "delivery"
      ],
      "updated_at": "2026-11-09T09:00:00Z",
      "version": 1
    }
  ]
}

CaseMessageCreatedEvent

A reply the customer sees was added to a case: theirs, the team's or the AI's. Internal notes are case.note.created.

FieldTypeAbout
idstring (uuid)required

This event for this endpoint, the same on every retry and every replay: the receiver's key to dedupe by

created_atstring (date-time)required

When it happened, not when it was sent

typecase.message.created
dataMessageDatarequired
{
  "created_at": "2026-11-10T08:20:41Z",
  "data": {
    "case": {
      "assignee": "Astrid",
      "channel": "email",
      "contact": {
        "email": "kari@example.com",
        "id": "7c2e4a1b-9d3f-4f6a-8b5e-1a2b3c4d5e6f",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-10T08:15:00Z",
      "id": "5d0b8f0e-2a4c-7d3e-9c1a-3b6f2e8a1c42",
      "number": 1042,
      "priority": "high",
      "status": "open",
      "subject": "Cracked tile in the delivery",
      "tags": [
        "delivery",
        "tiles"
      ],
      "type": "complaint",
      "updated_at": "2026-11-10T08:20:41Z"
    },
    "message": {
      "author_kind": "customer",
      "created_at": "2026-11-10T08:20:41Z",
      "id": "0192f3a4-7b1c-7e2d-a3f4-5b6c7d8e9f01",
      "kind": "public",
      "text": "Hi, three of the tiles in the pallet were cracked."
    }
  },
  "id": "0192f3a4-5555-7e2d-a3f4-5b6c7d8e9f01",
  "type": "case.message.created"
}

CaseNoteCreatedEvent

An internal note was added to a case. Opt-in: an endpoint hears it only when it asks for it.

FieldTypeAbout
idstring (uuid)required

This event for this endpoint, the same on every retry and every replay: the receiver's key to dedupe by

created_atstring (date-time)required

When it happened, not when it was sent

typecase.note.created
dataMessageDatarequired
{
  "created_at": "2026-11-10T08:22:00Z",
  "data": {
    "case": {
      "assignee": "Astrid",
      "channel": "email",
      "contact": {
        "email": "kari@example.com",
        "id": "7c2e4a1b-9d3f-4f6a-8b5e-1a2b3c4d5e6f",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-10T08:15:00Z",
      "id": "5d0b8f0e-2a4c-7d3e-9c1a-3b6f2e8a1c42",
      "number": 1042,
      "priority": "high",
      "status": "open",
      "subject": "Cracked tile in the delivery",
      "tags": [
        "delivery",
        "tiles"
      ],
      "type": "complaint",
      "updated_at": "2026-11-10T08:20:41Z"
    },
    "message": {
      "author_kind": "team",
      "created_at": "2026-11-10T08:20:41Z",
      "id": "0192f3a4-7b1c-7e2d-a3f4-5b6c7d8e9f01",
      "kind": "internal",
      "text": "Replacement tiles ordered."
    }
  },
  "id": "0192f3a4-6666-7e2d-a3f4-5b6c7d8e9f01",
  "type": "case.note.created"
}

CasePatchIn

What a caller may change on a case. Status and assignment stay the team's: sending them is a 422.

FieldTypeAbout
tagslist of string or null

Replaces the case's tags

prioritylow | normal | high | urgent or null

low, normal, high or urgent

custom_fieldsobject or null

Values for the case fields, merged key by key

external_refstring or null

Your own id for the case; null takes it away

versioninteger or null

The version you read. When the case has moved on the answer is 409 case_changed

{
  "priority": "high",
  "tags": [
    "delivery",
    "refund"
  ],
  "version": 3
}

CaseType

FieldTypeAbout
keystringrequired

What type takes on a case, and what type filters on

namestringrequired
{
  "key": "return",
  "name": "Return"
}

CaseTypeList

FieldTypeAbout
itemslist of CaseTyperequired
{
  "items": [
    {
      "key": "return",
      "name": "Return"
    }
  ]
}

CaseUpdatedData

FieldTypeAbout
casePublicCaserequired
changeslist of Changerequired

What this event is about; one event per change

CaseUpdatedEvent

A case's status, assignee, priority or type changed. data.case is the case as it is when the event is sent; data.changes says what this event is about.

FieldTypeAbout
idstring (uuid)required

This event for this endpoint, the same on every retry and every replay: the receiver's key to dedupe by

created_atstring (date-time)required

When it happened, not when it was sent

typecase.updated
dataCaseUpdatedDatarequired
{
  "created_at": "2026-11-10T08:18:00Z",
  "data": {
    "case": {
      "assignee": "Astrid",
      "channel": "email",
      "contact": {
        "email": "kari@example.com",
        "id": "7c2e4a1b-9d3f-4f6a-8b5e-1a2b3c4d5e6f",
        "name": "Kari Kunde"
      },
      "created_at": "2026-11-10T08:15:00Z",
      "id": "5d0b8f0e-2a4c-7d3e-9c1a-3b6f2e8a1c42",
      "number": 1042,
      "priority": "high",
      "status": "open",
      "subject": "Cracked tile in the delivery",
      "tags": [
        "delivery",
        "tiles"
      ],
      "type": "complaint",
      "updated_at": "2026-11-10T08:20:41Z"
    },
    "changes": [
      {
        "field": "assignee",
        "to": "Astrid"
      }
    ]
  },
  "id": "0192f3a4-4444-7e2d-a3f4-5b6c7d8e9f01",
  "type": "case.updated"
}

Change

One thing that changed on a case. from and to are the status, the priority or the type's key, or the first name of the assignee.

FieldTypeAbout
fieldstatus | assignee | priority | typerequired
fromstring or nullrequired
tostring or nullrequired

Contact

FieldTypeAbout
idstring (uuid)required
namestringrequired
emailstring or nullrequired
phonestring or nullrequired
languagestring or nullrequired

nb or en, when known

ContactIn

The person the case is about, found or made. Give at least one of email, phone and external_ids: the person is found by the email, then the phone number, then an id in another system (exact matches only), and made when none is known. A person who is found is not changed.

FieldTypeAbout
namestringrequired

Their name, used when they are new

emailstring (email) or null

Their email address

phonestring or null

Their phone number

languagestring or null

Their language, nb or en, used when they are new

external_idsmap of string

Their id in your own systems, by the system's name: {"shop": "C-1042"}

{
  "email": "kari@example.no",
  "language": "nb",
  "name": "Kari Kunde",
  "phone": "+47 900 00 000"
}

Customer

A customer: a person who writes to the workspace.

FieldTypeAbout
idstring (uuid)required
namestringrequired
emailstring or nullrequired

Lowercased. Unique in the workspace

phonestring or nullrequired

Digits with a leading +

languagestring or nullrequired

nb or en, when known

customer_nostring or nullrequired

Their customer number

member_nostring or nullrequired

Their member number

external_idsmap of stringrequired

Their ids in your own systems, by the system's name: {"shop": "C-1042"}

organization_idstring (uuid) or nullrequired

The company they belong to

organization_namestring or nullrequired
case_countsCaseCountsrequired
created_atstring (date-time)required
updated_atstring (date-time)required
{
  "case_counts": {
    "cases": 3,
    "last_contact_at": "2026-10-20T08:15:00Z",
    "open": 1
  },
  "created_at": "2026-09-02T10:00:00Z",
  "customer_no": "10442",
  "email": "kari@example.no",
  "external_ids": {
    "shop": "C-1042"
  },
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "language": "nb",
  "name": "Kari Kunde",
  "organization_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "organization_name": "Fliser AS",
  "phone": "+4790000000",
  "updated_at": "2026-10-20T08:15:00Z"
}

CustomerIn

A new contact. A person whose email, phone number or id in another system is already known is not made twice: the answer is 409 contact_exists with the existing contact's id in details.contact_id.

FieldTypeAbout
namestringrequired

Their name

emailstring (email) or null

Their email address

phonestring or null

Their phone number

languagestring or null

nb or en

customer_nostring or null
member_nostring or null
organization_idstring (uuid) or null

The company they belong to. Left out, a company with their email's domain is used

external_idsmap of string

Their ids in your own systems, by the system's name: {"shop": "C-1042"}

{
  "email": "kari@example.no",
  "external_ids": {
    "shop": "C-1042"
  },
  "language": "nb",
  "name": "Kari Kunde",
  "phone": "+47 900 00 000"
}

CustomerList

FieldTypeAbout
itemslist of Customerrequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

{
  "items": [
    {
      "case_counts": {
        "cases": 3,
        "last_contact_at": "2026-10-20T08:15:00Z",
        "open": 1
      },
      "created_at": "2026-09-02T10:00:00Z",
      "customer_no": "10442",
      "email": "kari@example.no",
      "external_ids": {
        "shop": "C-1042"
      },
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "language": "nb",
      "name": "Kari Kunde",
      "organization_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
      "organization_name": "Fliser AS",
      "phone": "+4790000000",
      "updated_at": "2026-10-20T08:15:00Z"
    }
  ]
}

CustomerPatchIn

What to change on a contact. Fields left out are not touched; null empties an optional field.

FieldTypeAbout
namestring or null

Never null

emailstring (email) or null

Another email address; null takes it away

phonestring or null
languagestring or null
customer_nostring or null
member_nostring or null
organization_idstring (uuid) or null

null takes them out of their company

external_idsmap of string or null

Merged key by key: a system's id is set, or forgotten with null. Systems you leave out are kept

{
  "external_ids": {
    "shop": "C-2000"
  },
  "organization_id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "phone": "+47 911 22 333"
}

ErrorBody

FieldTypeAbout
codestringrequired
messagestringrequired
detailsobject or null

ErrorResponse

Declare on operations that can fail: responses={404: {"model": ErrorResponse}}.

FieldTypeAbout
errorErrorBodyrequired

EventActor

FieldTypeAbout
typecustomer | team | api | ai | automation | systemrequired

customer: the person the case is about. team: someone in the workspace, by first name. api: a key, by its name. ai, automation and system are Nolmo itself

namestring or nullrequired

FieldOption

FieldTypeAbout
valuestringrequired
labelstringrequired

FirstMessageIn

What the customer wrote.

FieldTypeAbout
body_textstringrequired

The words, as plain text

body_htmlstring or null

The same as HTML, if you have it. It is cleaned before it is stored: scripts, styles and anything else a mail may not carry are removed

attachmentslist of string

Ids from POST /v1/uploads; only your own, not yet used ones

Group

FieldTypeAbout
idstring (uuid)required
namestringrequired
{
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "name": "Support"
}

GroupList

FieldTypeAbout
itemslist of Grouprequired
{
  "items": [
    {
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "name": "Support"
    }
  ]
}

Me

Who is calling, so an integration can check its setup.

FieldTypeAbout
keyMeKeyrequired
tenantMeTenantrequired
scopeslist of cases:read | cases:write | cases:reply | contacts:read | contacts:write | webhooks:manage | knowledge:read | knowledge:writerequired

What the key may do

all_groupsbooleanrequired

The key sees every group's cases

groupslist of MeGrouprequired

The groups it sees; empty with all_groups

{
  "all_groups": false,
  "groups": [
    {
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f8",
      "name": "Support"
    }
  ],
  "key": {
    "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
    "name": "Contact form",
    "prefix": "nolmo_live_k3J9fQ2a"
  },
  "scopes": [
    "cases:read",
    "cases:write"
  ],
  "tenant": {
    "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f7",
    "name": "Furu & Lin",
    "slug": "furu-lin"
  }
}

MeGroup

FieldTypeAbout
idstring (uuid)required
namestringrequired

MeKey

FieldTypeAbout
idstring (uuid)required
namestringrequired
prefixstringrequired

nolmo_live_ and the first eight characters of the key

expires_atstring (date-time) or nullrequired

When the key stops working; null for never

MeTenant

FieldTypeAbout
idstring (uuid)required
namestringrequired
slugstringrequired

Message

FieldTypeAbout
idstring (uuid)required
kindcustomer | note | replyrequired

customer, reply (public, from the team, a key or the AI) or note (internal; only when asked for)

authorAuthorrequired
body_textstringrequired
body_htmlstring or nullrequired
attachmentslist of Attachmentrequired
deliveryqueued | sent | failed or nullrequired

For a reply that leaves by email: where it is. Null for everything else

created_atstring (date-time)required
{
  "attachments": [],
  "author": {
    "name": "Astrid",
    "type": "team"
  },
  "body_text": "Hi Kari, we are sending new tiles today.",
  "created_at": "2026-11-09T10:30:00Z",
  "delivery": "sent",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "kind": "reply"
}

MessageData

FieldTypeAbout
casePublicCaserequired
messagePublicMessagerequired

MessageIn

Something to add to a case.

FieldTypeAbout
kindcustomer | note | replyrequired

customer: a message from the person the case is about; it reopens a solved case and is refused on a closed one. note: internal, for the team, authored by the key. reply: a public answer to the customer, sent through the case's channel as a team reply is; it needs the scope cases:reply

body_textstringrequired
body_htmlstring or null

Cleaned before it is stored, as in a new case

attachmentslist of string

Ids from POST /v1/uploads; only your own, not yet used ones

{
  "body_text": "Any news on this? I still have not heard anything.",
  "kind": "customer"
}

MessageList

FieldTypeAbout
itemslist of Messagerequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

{
  "items": [
    {
      "attachments": [],
      "author": {
        "name": "Astrid",
        "type": "team"
      },
      "body_text": "Hi Kari, we are sending new tiles today.",
      "created_at": "2026-11-09T10:30:00Z",
      "delivery": "sent",
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
      "kind": "reply"
    }
  ]
}

Organization

A company the workspace serves.

FieldTypeAbout
idstring (uuid)required
namestringrequired
domainslist of stringrequired

Email domains whose people belong here, lowercased

external_idsmap of stringrequired

Its ids in your own systems, by the system's name

peopleintegerrequired

How many contacts belong to it

case_countsCaseCountsrequired
created_atstring (date-time)required
updated_atstring (date-time)required
{
  "case_counts": {
    "cases": 9,
    "last_contact_at": "2026-10-20T08:15:00Z",
    "open": 2
  },
  "created_at": "2026-09-02T10:00:00Z",
  "domains": [
    "fliser.no"
  ],
  "external_ids": {
    "crm": "ORG-77"
  },
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
  "name": "Fliser AS",
  "people": 4,
  "updated_at": "2026-10-20T08:15:00Z"
}

OrganizationIn

A new company. One with the same name or the same id in another system is not made twice: the answer is 409 organization_exists with the existing company's id in details.organization_id.

FieldTypeAbout
namestringrequired
domainslist of string

Email domains; a new contact with an address there joins the company

external_idsmap of string

Its ids in your own systems, by the system's name

{
  "domains": [
    "fliser.no"
  ],
  "external_ids": {
    "crm": "ORG-77"
  },
  "name": "Fliser AS"
}

OrganizationList

FieldTypeAbout
itemslist of Organizationrequired
next_cursorstring or nullrequired

Send it as after for the next page; null on the last

{
  "items": [
    {
      "case_counts": {
        "cases": 9,
        "last_contact_at": "2026-10-20T08:15:00Z",
        "open": 2
      },
      "created_at": "2026-09-02T10:00:00Z",
      "domains": [
        "fliser.no"
      ],
      "external_ids": {
        "crm": "ORG-77"
      },
      "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f9",
      "name": "Fliser AS",
      "people": 4,
      "updated_at": "2026-10-20T08:15:00Z"
    }
  ]
}

OrganizationPatchIn

What to change on a company. Fields left out are not touched.

FieldTypeAbout
namestring or null

Never null

domainslist of string or null

Replaces the domains

external_idsmap of string or null

Merged key by key: set, or forgotten with null

{
  "domains": [
    "fliser.no",
    "fliser.com"
  ],
  "external_ids": {}
}

PublicCase

A case as /v1 shows it.

FieldTypeAbout
idstring (uuid)required
numberintegerrequired

The case's number in the workspace, the one people see and type

subjectstringrequired
statusnew | open | pending | hold | solved | closedrequired
prioritylow | normal | high | urgentrequired
typestring or nullrequired

The case type's key, when the case has one

tagslist of stringrequired
assigneestring or nullrequired

First name of whoever has the case; null when nobody does

created_atstring (date-time)required
updated_atstring (date-time)required
contactPublicContactrequired
channelstring or nullrequired

How the case came in: email, chat, api, messenger, …

PublicContact

FieldTypeAbout
idstring (uuid)required
namestringrequired
emailstring or nullrequired

The contact's address, when they have one

PublicMessage

A message on a case as /v1 shows it.

FieldTypeAbout
idstring (uuid)required
kindpublic | internalrequired

public is a reply the customer sees; internal is a note for the team

textstringrequired
author_kindcustomer | team | ai | systemrequired
created_atstring (date-time)required

Replay

Send every event since a time again, in order, as new attempts with the same ids.

FieldTypeAbout
sincestring (date-time)required
{
  "since": "2026-11-10T08:00:00Z"
}

Replayed

FieldTypeAbout
queuedintegerrequired

How many events will be sent again

{
  "queued": 12
}

Tag

FieldTypeAbout
tagstringrequired
countintegerrequired

How many of the cases the key sees have it

{
  "count": 12,
  "tag": "delivery"
}

TagList

FieldTypeAbout
itemslist of Tagrequired
{
  "items": [
    {
      "count": 12,
      "tag": "delivery"
    }
  ]
}

Upload

A file waiting to be put on a message.

FieldTypeAbout
idstringrequired

Name it in attachments of a case or a message

namestringrequired
content_typestringrequired
sizeintegerrequired

Bytes

{
  "content_type": "image/jpeg",
  "id": "0192f3c4-6a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "name": "damaged-tiles.jpg",
  "size": 183204
}

WebhookDelivery

One event for one endpoint, as the log shows it (kept 30 days).

FieldTypeAbout
idstring (uuid)required

The event's id, as the receiver got it in webhook-id

eventcase.created | case.updated | case.message.created | case.note.createdrequired
case_numberintegerrequired
statuspending | delivered | failedrequired

pending: to be sent or being retried; delivered: answered 2xx; failed: given up after 24 hours

attemptsintegerrequired
response_codeinteger or nullrequired

The receiver's last status code, when it answered

errorstring or nullrequired

Why the last attempt failed when there was no answer: timeout, unreachable, address_not_public, …

occurred_atstring (date-time)required
last_attempt_atstring (date-time) or nullrequired
next_attempt_atstring (date-time) or nullrequired
delivered_atstring (date-time) or nullrequired
{
  "attempts": 1,
  "case_number": 1042,
  "delivered_at": "2026-11-10T08:20:42Z",
  "event": "case.message.created",
  "id": "0192f3a4-2222-7e2d-a3f4-5b6c7d8e9f01",
  "last_attempt_at": "2026-11-10T08:20:42Z",
  "occurred_at": "2026-11-10T08:20:41Z",
  "response_code": 200,
  "status": "delivered"
}

WebhookDeliveryList

FieldTypeAbout
itemslist of WebhookDeliveryrequired
next_cursorstring or nullrequired
{
  "items": [
    {
      "attempts": 1,
      "case_number": 1042,
      "delivered_at": "2026-11-10T08:20:42Z",
      "event": "case.message.created",
      "id": "0192f3a4-2222-7e2d-a3f4-5b6c7d8e9f01",
      "last_attempt_at": "2026-11-10T08:20:42Z",
      "occurred_at": "2026-11-10T08:20:41Z",
      "response_code": 200,
      "status": "delivered"
    }
  ]
}

WebhookEndpoint

Where events are sent. The secret is not here: it is shown once, when the endpoint is made or its secret rotated.

FieldTypeAbout
idstring (uuid)required
namestringrequired
urlstringrequired
eventslist of case.created | case.updated | case.message.created | case.note.createdrequired
enabledbooleanrequired
disabled_reasonfailing | manual or nullrequired

failing: it did not answer for three days; manual: someone turned it off

disabled_atstring (date-time) or nullrequired
all_groupsbooleanrequired

Hears every case, whatever its group

group_idslist of string (uuid)required

The groups it hears; empty with all_groups

previous_secret_untilstring (date-time) or nullrequired

While set, the secret that was rotated out still signs beside the new one, until this time

failing_sincestring (date-time) or nullrequired

Since when no send has been answered with a 2xx

last_success_atstring (date-time) or nullrequired
created_atstring (date-time)required
{
  "all_groups": false,
  "created_at": "2026-11-09T12:00:00Z",
  "enabled": true,
  "events": [
    "case.created",
    "case.updated",
    "case.message.created"
  ],
  "group_ids": [
    "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
  ],
  "id": "0192f3a4-1111-7e2d-a3f4-5b6c7d8e9f01",
  "last_success_at": "2026-11-10T08:20:42Z",
  "name": "Returns page",
  "url": "https://returns.example.com/hooks/nolmo"
}

WebhookEndpointCreate

FieldTypeAbout
namestringrequired

What the receiver is, for the list

urlstringrequired

https:// only, to a public address; no redirects are followed

eventslist of case.created | case.updated | case.message.created | case.note.created
all_groupsboolean
group_idslist of string (uuid)
{
  "all_groups": false,
  "events": [
    "case.created",
    "case.updated",
    "case.message.created"
  ],
  "group_ids": [
    "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
  ],
  "name": "Returns page",
  "url": "https://returns.example.com/hooks/nolmo"
}

WebhookEndpointCreated

FieldTypeAbout
idstring (uuid)required
namestringrequired
urlstringrequired
eventslist of case.created | case.updated | case.message.created | case.note.createdrequired
enabledbooleanrequired
disabled_reasonfailing | manual or nullrequired

failing: it did not answer for three days; manual: someone turned it off

disabled_atstring (date-time) or nullrequired
all_groupsbooleanrequired

Hears every case, whatever its group

group_idslist of string (uuid)required

The groups it hears; empty with all_groups

previous_secret_untilstring (date-time) or nullrequired

While set, the secret that was rotated out still signs beside the new one, until this time

failing_sincestring (date-time) or nullrequired

Since when no send has been answered with a 2xx

last_success_atstring (date-time) or nullrequired
created_atstring (date-time)required
secretstringrequired

Signs every send (whsec_…). Shown here and never again; keep it where the receiver can read it

{
  "all_groups": false,
  "created_at": "2026-11-09T12:00:00Z",
  "enabled": true,
  "events": [
    "case.created",
    "case.updated",
    "case.message.created"
  ],
  "group_ids": [
    "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
  ],
  "id": "0192f3a4-1111-7e2d-a3f4-5b6c7d8e9f01",
  "last_success_at": "2026-11-10T08:20:42Z",
  "name": "Returns page",
  "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw9o7cXx1b0hE=",
  "url": "https://returns.example.com/hooks/nolmo"
}

WebhookEndpointList

FieldTypeAbout
itemslist of WebhookEndpointrequired
{
  "items": [
    {
      "all_groups": false,
      "created_at": "2026-11-09T12:00:00Z",
      "enabled": true,
      "events": [
        "case.created",
        "case.updated",
        "case.message.created"
      ],
      "group_ids": [
        "0b1c1f0e-8f6e-4d8e-9a47-0c8f5d0e6a11"
      ],
      "id": "0192f3a4-1111-7e2d-a3f4-5b6c7d8e9f01",
      "last_success_at": "2026-11-10T08:20:42Z",
      "name": "Returns page",
      "url": "https://returns.example.com/hooks/nolmo"
    }
  ]
}

WebhookEndpointUpdate

Only what is sent changes. Turning an endpoint on clears its failing mark.

FieldTypeAbout
namestring or null
urlstring or null
eventslist of case.created | case.updated | case.message.created | case.note.created or null
enabledboolean or null
all_groupsboolean or null
group_idslist of string (uuid) or null
{
  "enabled": false
}

Changelog