Case Management¶
Note
AI Context
Complexity: Medium
Cost: Free – case management has no per-operation charge.
Async: No. All
/service_agents/contact_cases*endpoints are synchronous.
A Case is a lightweight, per-channel session header that groups the interactions with a single remote party (a phone number, an email address, …) into a start/end unit an agent can pick up, work, and close. Where a Contact is a durable identity record and Interactions are the raw event log, a Case is the working unit an agent-facing console builds its queue/inbox UI around: “here is everything related to this customer’s current issue, and who owns it.”
With the Case API you can:
List and inspect the cases open for your customer
Assign a case to a specific agent, or close it once resolved
Attach or detach the case’s resolved Contact
Attach internal, agent-only notes that never leak into customer-facing webhooks or responses
Cases are created automatically by the platform (e.g. by a Flow action or an AI tool when a new call/conversation arrives for a peer with no open Case) – there is no POST /service_agents/contact_cases endpoint. The Agent Console surface only reads, assigns, annotates, and closes cases.
How Cases Work¶
Case Lifecycle
+------------------------------------------------------------------+
| Case Lifecycle |
+------------------------------------------------------------------+
New interaction arrives
for a peer with no open Case
|
v
+-------------+ assign +-------------+
| open |----------------->| open |
| (unowned) | | (owned by |
+------+------+ | agent) |
| +------+------+
| close | close
v v
+-------------+ +-------------+
| closed |<-----------------| closed |
+------+------+ +-------------+
|
| re-contact (new interaction for same peer)
v
+-------------+
| new open |
| Case, chained via previous_case_id
+-------------+
Status is either
openorclosed. Cases startopenand unowned.Owner (
owner_type/owner_id) identifies which agent is working the case. Assignment does not change status – an assigned case staysopenuntil explicitly closed.Closing a case is permanent for that Case instance; a subsequent interaction from the same peer creates a new Case with
previous_case_idpointing at the just-closed one, so the console can show re-contact history.The owner is never cleared by closing a case –
owner_idremains a durable record of who last worked it, even afterstatusbecomesclosed.
Endpoints¶
Method |
Path |
Description |
|---|---|---|
GET |
|
List cases for the caller’s customer (paginated, no server-side owner filter – filter client-side). Optional |
GET |
|
Get a single case by ID. Any agent of the customer may view any case. |
PUT |
|
Attach (non-empty |
POST |
|
Assign the case to an owner agent ( |
POST |
|
Clear the case’s owner. Only the current owning agent may unassign themselves. |
POST |
|
Close the case. |
GET |
|
List notes on the case, in creation order. |
POST |
|
Create a note on the case, authored by the caller. |
DELETE |
|
Delete a note. Only the note’s own author may delete it. |
GET |
|
List notes across all cases of a contact, newest first (paginated). |
See Structures for the Case and CaseNote field reference.
Listing and Viewing Cases¶
List cases
$ curl -X GET 'https://api.voipbin.net/v1.0/service_agents/contact_cases?page_size=50&token=<token>'
List cases for a specific contact
$ curl -X GET 'https://api.voipbin.net/v1.0/service_agents/contact_cases?contact_id=<contact-id>&page_size=50&token=<token>'
List the notes of a contact across all its cases
$ curl -X GET 'https://api.voipbin.net/v1.0/service_agents/contacts/<contact-id>/case_notes?page_size=50&token=<token>'
Returns the notes written on every case currently attributed to the contact, newest first, as a single paginated list. Each note carries the case_id of the case it was written on. Pass the returned next_page_token as page_token to fetch the next page. If a case is later attributed to a different contact, its notes follow the case. A contact that does not exist or belongs to another customer returns 404.
List cases bound to a specific call
reference_id matches the internal resource ID that the case’s reference_type points at (for reference_type call, the call ID). Combined with contact_id the filters are ANDed. A call that is bound to no case returns an empty result array. An empty reference_id value applies no filter.
$ curl -X GET 'https://api.voipbin.net/v1.0/service_agents/contact_cases?reference_id=<call-id>&page_size=50&token=<token>'
Get a case
$ curl -X GET 'https://api.voipbin.net/v1.0/service_agents/contact_cases/<case-id>?token=<token>'
Note
AI Implementation Hint
GET /service_agents/contact_cases returns every open and closed case for the customer with no server-side status/owner_id filter – console UIs (e.g. an agent’s “My Cases” view) are expected to filter the result client-side. There is no status=open or owner_id=<my-agent-id> query parameter on this endpoint today. A contact_id filter IS supported (added VOIP-1563) – use it to fetch a specific contact’s case history server-side instead of filtering the full customer-wide result client-side. A reference_id filter is also supported – use it to find the case a given call is bound to.
Assigning and Closing¶
Assignment and closing are separate operations: assigning a case to an agent does not close it, and closing a case does not require it to have an owner.
Assign a case to an agent
$ curl -X POST 'https://api.voipbin.net/v1.0/service_agents/contact_cases/<case-id>/assign?token=<token>' \
--header 'Content-Type: application/json' \
--data '{
"owner_id": "<agent-id>"
}'
owner_id must reference an existing agent of the same customer as the caller. A cross-tenant or nonexistent agent ID and a nonexistent case ID both return the same 404 – the API deliberately does not reveal which one failed.
Unassign a case
$ curl -X POST 'https://api.voipbin.net/v1.0/service_agents/contact_cases/<case-id>/unassign?token=<token>'
Only the current owning agent may unassign themselves; there is no admin/manager bypass on this surface. Attempting to unassign a case owned by a different agent, or an already-unowned case, returns 403 Forbidden – not 404, since any agent of the customer may already view the case via GET. Unassigning a closed case returns 409 Conflict, matching assign’s behavior.
Close a case
$ curl -X POST 'https://api.voipbin.net/v1.0/service_agents/contact_cases/<case-id>/close?token=<token>' \
--header 'Content-Type: application/json' \
--data '{
"disposition": "resolved"
}'
The request body is optional. disposition records the outcome of the case and must be one of resolved, unresolved, follow_up_needed, spam or other; any other value returns 400 Bad Request. Omit the body (or the field) to close the case without recording one.
If the case was already closed, the stored disposition is kept and the response returns the stored value, which can differ from the requested one.
closed_by_type/closed_by_id are always derived from the caller’s own authenticated agent identity – the request body cannot set them, so a case can never be closed “as” another agent.
Attach or detach the case’s Contact
$ curl -X PUT 'https://api.voipbin.net/v1.0/service_agents/contact_cases/<case-id>?token=<token>' \
--header 'Content-Type: application/json' \
--data '{
"contact_id": "<contact-id>"
}'
Send an empty string for contact_id to detach:
$ curl -X PUT 'https://api.voipbin.net/v1.0/service_agents/contact_cases/<case-id>?token=<token>' \
--header 'Content-Type: application/json' \
--data '{
"contact_id": ""
}'
Every attach/detach is recorded as a case_contact_attributed/case_contact_detached audit event, queryable via bin-timeline-manager.
Case Notes¶
Notes are an internal, agent-facing annotation on a Case – free-text scratchpad entries like “called back, no answer” that help agents hand a case off to each other. Notes are physically and transport-isolated from customer-facing data: they never appear in any customer webhook, in GET /contact_cases (the admin/manager surface), or in any other customer-visible response.
List notes
$ curl -X GET 'https://api.voipbin.net/v1.0/service_agents/contact_cases/<case-id>/notes?token=<token>'
Create a note
$ curl -X POST 'https://api.voipbin.net/v1.0/service_agents/contact_cases/<case-id>/notes?token=<token>' \
--header 'Content-Type: application/json' \
--data '{
"text": "Called the customer back, no answer."
}'
The note’s author_type/author_id are always derived server-side from the caller’s authenticated agent identity – there is no way to author a note as another agent or as the system through this endpoint.
Delete a note
$ curl -X DELETE 'https://api.voipbin.net/v1.0/service_agents/contact_cases/<case-id>/notes/<note-id>?token=<token>'
Note
AI Implementation Hint
An agent may only delete a note it authored itself. Attempting to delete a note authored by another agent, or a system-authored note (author_id is null), returns 403 PermissionDenied – not 404, so the caller can distinguish “not my note” from “note doesn’t exist”.
Relationship to the Admin/Manager Case API¶
The same underlying Case resource is also exposed at the top level (/contact_cases, gated by admin/manager permission) – see Case Overview. That surface additionally supports POST /contact_cases/{id}/continue (reopen a closed case) and sending an outbound conversation message tied to the case, neither of which exists under /service_agents/contact_cases. POST /service_agents/contact_cases/{id}/assign (owner assignment) and POST /service_agents/contact_cases/{id}/unassign (owner removal) also have top-level equivalents, POST /contact_cases/{id}/assign (VOIP-1514) and POST /contact_cases/{id}/unassign (VOIP-1515), gated by admin/manager permission (or the owning agent, for unassign) instead of PermissionAll.