Skip to main content
Back to blog

Teams API integration guide — route to groups from your app, not just the dashboard

The Teams API puts the same roster your inbox assigns to behind three calls — create a team, page its members, toggle routing availability. When team-scoped routing earns its keep, and how to integrate it programmatically.

Orbit Editorial Team

A team on Devotel Orbit is a named group of users that inbound routing hands conversations to, instead of leaving them in an unassigned bucket. Until now most teams were created by clicking Voice → Teams in the dashboard and letting routing rules point at the result. This guide is for the second posture: teams created and maintained from your own code — and the same worked request/response samples the Teams endpoint reference now publishes inline.

The API surface is small on purpose. Three calls cover the full lifecycle: POST /api/v1/teams creates the group, GET /api/v1/teams/{id}/members pages its roster, and PATCH /api/v1/teams/{id} renames it or flips it in and out of routing. The full guide, including member add/remove and a 200-teams-per-organization cap, lives in the Teams guide; this post is about when that surface is worth integrating and how the pieces combine.

When team-scoped routing matters

Assigning a conversation to one named user works until the assignment outlives the person: they go on leave, move teams, or leave the company, and every rule pointing at them routes to a ghost. A team decouples the rule from the person — the rule targets the group, and the group resolves to whoever is actually staffed at assignment time.

Three patterns make that resolution pay:

Shared inbox with a triage group. Inbound email or WhatsApp lands in a general queue, and a rule evaluation assigns the thread to "Tier 1 Support" rather than to a person. The picker reads the team's active members and hands the thread to the least-loaded one. New hires join the roster and receive assignments immediately; departures stop receiving them the moment their membership row is removed — no rule edits needed either way.

Round-robin fairness. The pick is not random. The ordering is fewest currently-open conversations first, tie-broken by the oldest last_assigned_at on the member row (a member never picked yet wins), so assignments spread across the pool instead of piling on whoever happened to reply last. Each member row carries that timestamp — visible in the members list response — so the fairness mechanism is auditable from your own tooling rather than a black box.

Time-based assignment. Routing rules AND-combine conditions, and one condition is business hours: in-window against your schedule, out-of-window, holidays. Pair that with two teams — "Weekday coverage" and "On-call" — and an in-window rule assigns to the staffed pool while an out-of-window rule assigns to the on-call group. The teams do not change; the rule's time condition flips which group receives. The full condition and action matrix is in the omnichannel queue routing guide.

Two properties of the same surface finish the picture. Setting a team's active flag to false pulls it out of assign_team resolution without deleting the roster — the safe way to pause a group during reorganization. And if a rule targets a team with no active members, the assignment refuses to mis-assign: the conversation stays unassigned and an internal note records why, so a skeleton crew never silently breaks your routing.

The worked examples

The Teams endpoint reference now leads with copy-pastable samples for the three calls an integration makes first, each shown as cURL and Node fetch with the full { data, meta } response envelope. Here is the summary of what those calls are and what to do with their responses.

Create the team. POST /api/v1/teams with a name (unique per organization, up to 120 characters), an optional description, and active. It returns 201 with the full row — { id, name, description, active, created_at, updated_at } — so capture data.id for every subsequent call. Names are unique within the workspace: a duplicate returns 409 CONFLICT with the message naming the taken name. The correct branch is to pick a new name or PATCH the existing team; never blind-retry a 409.

Page the members. GET /api/v1/teams/{id}/members lists the roster oldest-first with keyset pagination. limit defaults to 25 and caps at 100; when meta.pagination.has_more is true, pass meta.pagination.cursor back as the cursor query parameter for the next page. total counts the whole roster and flags total_capped: true at the 1,000 ceiling so a UI can render "1,000+" — paging still walks every member via the cursor. Each member row carries active and last_assigned_at: members set inactive stay listed but are skipped by assignment, and the timestamp is what round-robin fairness reads.

Toggle routing availability. PATCH /api/v1/teams/{id} accepts any subset of name, description, and active; unset fields are untouched. Flipping active: false pulls the team out of routing-rule resolution without deleting anything, and true resumes it — the sequence for planned pauses (holiday schedules, reorganization) that keeps the roster and every rule pointing at it intact.

For errors, branch on the class: 401 means rotate the key, 403 means the key lacks the team:invite scope team writes require, 422 means fix the payload against error.details.field, 429 means retry after error.details.retry_after, 404 on update means the id is stale — re-list and re-branch. The endpoint page ships each class as a full { error, meta } envelope with a retry-versus-surface matrix.

Two integration postures

There are two credible ways to drive this API, and they differ in who owns the running sequence.

Dashboard-adjacent, assistant-driven. Devotel Orbit's MCP server is read-write: connect Claude, Cursor, or another MCP client and the assistant can execute real calls against your account from a prompt — "create a team called Seasonal overflow, add these three users, and point the holiday routing rule at it." For the occasional reorganization that is the cheapest path: no code to own, and the assistant works against the same API the dashboard shows. The trade-off is that the sequence lives in the prompt, not in version control.

A dedicated integration sequence. When team lifecycle becomes part of a provisioning pipeline — every new customer workspace gets a fixed team layout, or membership syncs nightly from your directory — encode the calls. The sequence is the samples above: create each team, add members by user id, wire the assign_team routing rule, page members to verify, and flip active only once the roster agrees with the source of truth. This posture puts the sequence in your repo, reviewable alongside the rest of your infrastructure, and it degrades predictably: a 409 on create means the workspace already has the team, so PATCH it; a 429 means wait the stated window.

Pick the first for operational one-offs, the second the moment the same sequence runs more than occasionally. Both go through the identical endpoints and permissions — reads admit any organization member, writes require the team:invite grant that built-in roles give owners and admins — so the choice is about where the sequence lives, not about capability.

Frequently asked questions

Does the Teams API change how outbound calls or messages are placed?

No. Teams govern inbound assignment only. Team definitions, member rosters, and the rules that target them live in your organization's settings and take effect on inbound items; outbound voice and messaging keep their existing delivery path, and none of these controls alter it.

How large can a team get?

The members endpoint paginates at 100 rows per page and the total count caps at 1,000 with total_capped: true. Practically, teams work best as assignment pools; when a roster needs hundreds of users, the resolution order (fewest open conversations, then oldest last_assigned_at) keeps the pick cheap at any size.

Is creating a team idempotent?

No — duplicate names return 409 CONFLICT by design. For provisioning pipelines, the idempotent pattern is: try the create, treat the 409 whose message names your intended name as "already exists," and PATCH the existing row to converge it.

What happens when a routing rule points at a deleted or empty team?

The conversation is left unassigned and an internal note on the thread says the team assignment could not resolve, rather than mis-assigning to a fallback user. Reactivate the team, staff it, or re-point the rule and the next inbound item routes normally.

Can the same user belong to more than one team?

Yes. Membership rows are additive across teams — removing a user from one team does not touch their membership anywhere else, and assignment within each team only weighs that team's own last_assigned_at markers.

Where it fits

Teams are one assignment surface in the omnichannel inbox. The walked-through guide — member add/remove, the 200-team cap, the safe-behavior fallthrough, the dashboard screens, and audit-log entries — is the Teams guide. The routing engine that consumes the roster is covered end to end in omnichannel queue routing, the worked endpoint samples carry the request/response envelopes, and this site's shared omnichannel inbox explainer frames the model the teams plug into. The MCP route for prompt-driven operation is described in the MCP server overview.

Teams API integration guide — route to groups from your app, not just the dashboard — Orbit by Devotel