Worlds / Support
Zendesk API
A local Zendesk with tickets, users, organizations, groups, macros and SLA policies. Test the support agent that triages, tags, replies and escalates.
- Base path
/api/v2- Auth
- OAuth bearer token, or email/token:key Basic, the world key
- Endpoints
- 25
- Status
- Active today
Connect
The run gives your agent these bindings. Keep your SDK; change only where it sends requests.
| binding | meaning |
|---|---|
WORLDS_ZENDESK_URL | This world's origin followed by /api/v2 |
WORLDS_API_KEY | The world key, accepted wherever Zendesk expects a credential |
WORLDS_WORLD_ID | The isolated world's identity |
import zendesk from "node-zendesk";
const client = zendesk.createClient({
endpointUri: process.env.WORLDS_ZENDESK_URL,
username: "admin@example.test",
token: process.env.WORLDS_ZENDESK_TOKEN,
oauth: true,
throttle: false,
});
const me = (await client.users.me()).result;API reference
Zendesk behavior and limits
- The seed selects the native profile with
zendesk.version: 1. The world key acts as the seed's principal; API IDs are numeric. Both suffixless and.jsonroutes work under/api/v2. - Directory records and memberships control assignment. Solving requires an assignee; closed tickets are immutable. Ticket changes, comments and audits commit together. Set
comment.publicexplicitly for a note or reply. Idempotency-Keyapplies to successful ticket creation, separately for each literal path and key, for two logical hours. Updates are not deduplicated. Guard updates withsafe_update: trueand the currentupdated_stamp; stale stamps return409 UpdateConflict.- List pages contain at most 100 records. Queue search requires
type:ticketand supports the listed status, priority, requester, assignee and tag filters; it exposes the first 1,000 matches. Hosted search delays are not reproduced. - Optional closure must be configured in the seed. User email changes, macros, triggers, SLAs, attachments and bulk operations are outside this subset. Supported local behavior does not establish full provider parity.
Tickets
Support requests with requester, assignee, status, priority and tags.
- GET
/api/v2/ticketsList tickets - POST
/api/v2/ticketsCreate a ticket - GET
/api/v2/tickets/{id}Show a ticket - PUT
/api/v2/tickets/{id}Update a ticket, or add a comment
Fields · 7
id | integer | Ticket id |
subject | string | Ticket subject |
status | string | new, open, pending, hold, solved or closed |
priority | string | low, normal, high or urgent; null by default |
requester_id | integer | User who asked for help |
assignee_id | integer | Agent working the ticket |
tags | array | Tags on the ticket |
Comments
Immutable replies and private notes on a ticket. Append one through a ticket update.
- GET
/api/v2/tickets/{ticket_id}/commentsList comments on a ticket
Fields · 5
id | integer | Comment id |
author_id | integer | User who wrote it |
body | string | Plain-text body |
public | boolean | False for an internal note |
created_at | string | ISO 8601 timestamp |
Audits
Immutable history of Create, Change and Comment events on a ticket.
- GET
/api/v2/tickets/{ticket_id}/auditsList audits for a ticket - GET
/api/v2/tickets/{ticket_id}/audits/{id}Show an audit
Fields · 5
id | integer | Audit id |
ticket_id | integer | Ticket the audit belongs to |
author_id | integer | User who made the change |
events | array | Create, Change and Comment events |
created_at | string | ISO 8601 timestamp |
Users
End users, agents and admins. Agents and admins come from the seed.
- GET
/api/v2/usersList users - POST
/api/v2/usersCreate an end user - GET
/api/v2/users/{id}Show a user - GET
/api/v2/users/meShow the current user - PUT
/api/v2/users/{id}Update a user's name
Fields · 5
id | integer | User id |
name | string | Full name |
email | string | Primary email, set on create |
role | string | end-user, agent or admin |
organization_id | integer | Organization the user belongs to |
Organizations
Companies that group end users.
- GET
/api/v2/organizationsList organizations - POST
/api/v2/organizationsCreate an organization - GET
/api/v2/organizations/{id}Show an organization - PUT
/api/v2/organizations/{id}Update an organization
Fields · 6
id | integer | Organization id |
name | string | Organization name |
external_id | string | Id in your own system |
domain_names | array | Email domains |
tags | array | Tags on the organization |
group_id | integer | Default group |
Groups
Agent groups that tickets are assigned to.
- GET
/api/v2/groupsList groups - POST
/api/v2/groupsCreate a group - GET
/api/v2/groups/{id}Show a group - PUT
/api/v2/groups/{id}Update a group
Fields · 4
id | integer | Group id |
name | string | Group name |
description | string | What the group handles |
default | boolean | Whether it is the default group |
Memberships
Read-only seeded links between users and groups or organizations.
- GET
/api/v2/group_membershipsList group memberships - GET
/api/v2/organization_membershipsList organization memberships - GET
/api/v2/users/{user_id}/group_membershipsList a user's group memberships - GET
/api/v2/users/{user_id}/organization_membershipsList a user's organization memberships
Fields · 5
id | integer | Membership id |
user_id | integer | User in the membership |
group_id | integer | Group, on group memberships |
organization_id | integer | Organization, on organization memberships |
default | boolean | Whether it is the user's default |
Search
Ticket queue search using explicit type, status, priority, requester, assignee and tags clauses.
- GET
/api/v2/searchSearch tickets, for example type:ticket status:open
Fields · 4
results | array | Matching tickets |
count | integer | Total matches |
next_page | string | URL of the next page |
previous_page | string | URL of the previous page |
Test workflow
- Prepare. Select the Zendesk setup for your workflow and review its starting records, including tickets, comments, audits.
- Connect. Configure your agent with this world's URL and key using the SDK example. Check that it reaches the intended API before running a test.
- Define. Describe the task, expected final changes and records that must stay untouched. Keep expected outcomes separate from the agent's instructions.
- Run and inspect. Use the dashboard or your workflow's generated CLI setup. Review final records and changes, then rerun from the same starting setup with the relevant failure conditions. Use Deployment and CI to automate the same workflow.
What to verify
- Every solved ticket has a public comment from the agent
- #4104 sits in the Billing group, priority high, tagged tier2
- #4101 and #4106 merged only if the requester ids match
- #4105 still open with no macro applied
- No ticket breaches its first reply SLA
Native support task fields
Start with the task file generated for your native support setup. It uses version: 2 and connector: zendesk, with a unique task id, numeric ticket_id and requester_id. Tickets must already exist in the seed; the legacy billing compiler is a separate workflow.
expected.ticketchecks final status, assignment and allowed tag changes;expected.commentschecks new public/private comment and author counts.allowed_ticket_fields,forbidden_changesandforbidden_public_textbound what the agent may change or reveal.allow_rule_closureexplicitly permits configured closure.- Combined billing tasks require an explicit
stripe_customerbinding. Import sanitized support and billing snapshots separately with the same epoch; do not infer identity from matching email addresses. - Isolated mode gives each task a fresh world; shift mode shares a world across selected tasks. Every repetition starts fresh. Grade persisted tickets, comments, audits and before/after state; missing or inconsistent evidence is inconclusive.
Keep the JSON and HTML reports together and check the overall outcome. Generated native CI workflows upload those reports; private identity maps and raw evidence stay local. See snapshot import for the accepted starting-data formats.
Test file
A task file names the jobs, the policy your agent is given and what the grader reads back from the world's final state. The agent never sees the expected outcomes.
# worlds/tasks.yaml · Zendesk
version: 2
connector: zendesk
seed: zendesk-starter
mode: isolated
policy: >
Work the overnight outage queue. Solve tickets only after the requester gets
a public reply. Anything claiming more than $500 goes to Billing at high
priority with the tier2 tag. Never merge tickets from different requesters.
tasks:
- id: triage-new-tickets-and-set-priority-and-type
prompt: Triage new tickets and set priority and type
- id: apply-a-macro-and-post-the-public-reply
prompt: Apply a macro and post the public reply
- id: escalate-to-tier-2-with-the-right-group-and-tag
prompt: Escalate to tier 2 with the right group and tag
expected:
- Every solved ticket has a public comment from the agent
- "#4104 sits in the Billing group, priority high, tagged tier2"
- "#4101 and #4106 merged only if the requester ids match"
- "#4105 still open with no macro applied"
- No ticket breaches its first reply SLA
scenarios:
- rate-limited
- update-conflict
- validation-errorSee Tasks and outcomes for every field, and Deployment and CI to run the file on every pull request.
Failure conditions
Each one fires before the handler, at the same step on every run.
| condition | what the agent sees |
|---|---|
rate-limited | PUT /api/v2/tickets/{id} returns 429 with Retry-After |
update-conflict | A safe_update with a stale updated_stamp returns 409 |
validation-error | 422 RecordInvalid when a required ticket field is empty |
sla-breach | The clock moves past the first reply target |
upload-token-expired | The attachment token from an old upload no longer works |
Starting data
Start each run from the same records and relationships. The overview below describes the Zendesk starter; use the setup supplied for your workflow when preparing your own snapshot.
| Records | Starting setup |
|---|---|
| tickets | 40 across new, open, pending, on-hold and solved |
| users | 28 end users and 6 agents, two requesters share a name |
| organizations | 9, three on a Premium SLA policy |
| groups | Support, Billing, Tier 2 |
| macros | 14, including Refund issued and Escalate to Billing |
The record shapes below reuse this world's API field definitions. They show illustrative values and placeholders, not an importable seed. API responses and seed input can differ; include all required records and references in the accepted import format for your workflow.
Tickets records
Tickets
Support requests with requester, assignee, status, priority and tags.
| Field | Type | Meaning / relationship |
|---|---|---|
id | integer | Ticket id |
subject | string | Ticket subject |
status | string | new, open, pending, hold, solved or closed |
priority | string | low, normal, high or urgent; null by default |
requester_id | integer | User who asked for help |
assignee_id | integer | Agent working the ticket |
tags | array | Tags on the ticket |
{
"id": 1,
"subject": "<Ticket subject>",
"status": "<new, open, pending, hold, solved or closed>",
"priority": "<low, normal, high or urgent; null by default>",
"requester_id": 1,
"assignee_id": 1
}Comments records
Comments
Immutable replies and private notes on a ticket. Append one through a ticket update.
| Field | Type | Meaning / relationship |
|---|---|---|
id | integer | Comment id |
author_id | integer | User who wrote it |
body | string | Plain-text body |
public | boolean | False for an internal note |
created_at | string | ISO 8601 timestamp |
{
"id": 1,
"author_id": 1,
"body": "<Plain-text body>",
"public": false,
"created_at": "<ISO 8601 timestamp>"
}Audits records
Audits
Immutable history of Create, Change and Comment events on a ticket.
| Field | Type | Meaning / relationship |
|---|---|---|
id | integer | Audit id |
ticket_id | integer | Ticket the audit belongs to |
author_id | integer | User who made the change |
events | array | Create, Change and Comment events |
created_at | string | ISO 8601 timestamp |
{
"id": 1,
"ticket_id": 1,
"author_id": 1,
"events": [],
"created_at": "<ISO 8601 timestamp>"
}Users records
Users
End users, agents and admins. Agents and admins come from the seed.
| Field | Type | Meaning / relationship |
|---|---|---|
id | integer | User id |
name | string | Full name |
email | string | Primary email, set on create |
role | string | end-user, agent or admin |
organization_id | integer | Organization the user belongs to |
{
"id": 1,
"name": "<Full name>",
"email": "person@example.test",
"role": "<end-user, agent or admin>",
"organization_id": 1
}Organizations records
Organizations
Companies that group end users.
| Field | Type | Meaning / relationship |
|---|---|---|
id | integer | Organization id |
name | string | Organization name |
external_id | string | Id in your own system |
domain_names | array | Email domains |
tags | array | Tags on the organization |
group_id | integer | Default group |
{
"id": 1,
"name": "<Organization name>",
"external_id": "<Id in your own system>",
"domain_names": [],
"tags": [],
"group_id": 1
}Groups records
Groups
Agent groups that tickets are assigned to.
| Field | Type | Meaning / relationship |
|---|---|---|
id | integer | Group id |
name | string | Group name |
description | string | What the group handles |
default | boolean | Whether it is the default group |
{
"id": 1,
"name": "<Group name>",
"description": "<What the group handles>",
"default": false
}Memberships records
Memberships
Read-only seeded links between users and groups or organizations.
| Field | Type | Meaning / relationship |
|---|---|---|
id | integer | Membership id |
user_id | integer | User in the membership |
group_id | integer | Group, on group memberships |
organization_id | integer | Organization, on organization memberships |
default | boolean | Whether it is the user's default |
{
"id": 1,
"user_id": 1,
"group_id": 1,
"organization_id": 1,
"default": false
}Search records
Search
Ticket queue search using explicit type, status, priority, requester, assignee and tags clauses.
| Field | Type | Meaning / relationship |
|---|---|---|
results | array | Matching tickets |
count | integer | Total matches |
next_page | string | URL of the next page |
previous_page | string | URL of the previous page |
{
"results": [],
"count": 1,
"next_page": "<URL of the next page>",
"previous_page": "<URL of the previous page>"
}Native Zendesk records
The zendesk object contains version: 1, principal_id and arrays for users, groups, organizations, memberships, tickets, comments and audits. Start from the bundled zendesk-support-v1 example in the dashboard, or an offline import. To inspect the bundled JSON, use GET /admin/seeds/zendesk-support-v1 on your local server; see the Admin API.
Native IDs and references are positive safe integers. The principal must be an active, nonsuspended agent or admin. Directory membership, assignment, comments and audit history are validated together; copying ticket rows alone is not sufficient.
Native timestamp chronology uses the full accepted ISO instant, including fractional digits beyond milliseconds and equivalent timezone spellings. Supplied creation and update times must be at or before the epoch; update must not precede creation. Omitted creation defaults to the epoch and omitted update to creation. A comment and its owning audit must have equal instants. Original timestamp strings survive export and reload.
The native scope documents supported behavior. Source checkouts also include the complete native seed schema for contributors.
Logical closure and follow-up metadata
Optional zendesk.lifecycle.closure enables simulated closure:
| Field | Accepted value |
|---|---|
after_hours |
Integer from 1 to 672 |
sweep_phase_seconds |
Integer from 0 to 3599 |
author_id |
Existing native agent or admin |
Omitting closure adds no defaults. Tickets may declare solved_at (ISO instant or null) and via_followup_source_id (native ticket ID). Audits may declare via_channel as api or rule.
For closure-enabled solved tickets, created_at <= solved_at <= updated_at <= epoch. Ticket, comment and audit history must not extend beyond the derived closure boundary. Active unsolved tickets require absent or null solved_at; closed tickets may retain the solve episode.
A follow-up source must exist, be closed and be no newer than its child. Equal creation times are valid; self-references and ancestry cycles are rejected.
These fields survive export and reload. Read closure timing and limits before using them to model a workflow.
An ordinary native seed also requires the shared seed file fields: name, version, epoch and data. Use empty data collections for a standalone native world and supply the complete top-level zendesk profile. The alternative offline wrapper is described below.
Relationships in a native snapshot
principal_idselects an active agent or admin fromusers.- Group and organization memberships connect users to the corresponding directory records.
- Ticket requester, submitter, assignee, group and organization references must resolve within the snapshot and satisfy assignment rules.
- Comments belong to tickets and authors; their owning audits and Comment events must agree. Include the complete history required by those references.
This is an illustrative user record, not an importable seed. A complete native seed also supplies the profile, principal and referenced collections:
{ "id": 1, "name": "Alex Support", "email": "alex@example.test", "role": "agent", "active": true, "suspended": false }
Offline native Zendesk import
worlds seed import --from-zendesk --input support-export.json --name my-support
This CLI accepts a complete native seed or the explicit worlds-zendesk-native-export format below. It does not fetch a live Zendesk account or accept arbitrary vendor exports. The dashboard offers guided data preparation, including supported offline Zendesk Full JSON conversion.
The import remaps identities, replaces supported identifying text and preserves valid relationships, privacy, timestamps and audit history. Attachments and unsupported fields are omitted and counted. Review the generated manifest.
Defaults are seeds/my-support.json and worlds/my-support.manifest.json. An optional --identity-map is private. See native import workflows for destination rules and composition with Stripe.
Accepted native input contracts
An ordinary native seed must have a top-level zendesk profile and empty Stripe/legacy data collections. Alternatively, the wrapper has:
format: "worlds-zendesk-native-export"version: 1epochzendeskcontaining the full native profile
Both forms require the principal, directory and membership records, tickets, comments and audits needed to satisfy all references. Optional lifecycle metadata must be coherent.
A populated composite seed is not an input to this importer. Import each source separately, then compose sanitized seeds with matching epochs. A native seed's clock configuration is preserved; the wrapper uses seed defaults.
Legacy ticket records
Native Zendesk records and legacy ticket records cannot coexist in one seed. Choose the profile for the workflow before importing records.
data.tickets[] serves the demo-grade, Zendesk-shaped desk. It is separate from native Zendesk. IDs are assigned in file order as "1", "2" and so on.
Legacy ticket fields
| Field | Type or default |
|---|---|
subject, body |
Required nonempty strings |
requester |
Required {name, email}; nonempty name and valid email |
status |
new, open, pending, hold, solved or closed; default open |
priority |
low, normal, high or urgent; default normal |
tags |
String array; default [] |
external_id |
Optional string or null |
first_comment_public |
Optional boolean; defaults in behavior to true |
created_offset |
Nonnegative seconds before the epoch; default 0 |
Billing references in ticket text are not foreign keys. Billing tasks compile into this profile, with one ticket per task and an answer key kept separately.
This legacy record fragment belongs in data.tickets[], not in the native zendesk.tickets collection:
{ "subject": "Billing question", "body": "Please check my invoice.", "requester": { "name": "Sam", "email": "sam@example.test" }, "status": "open", "priority": "normal" }
Referential integrity
Validation rejects unknown fields, duplicate IDs, missing references and inconsistent history. Errors name the JSON path to fix.
For native Zendesk, it checks directory relationships, principal authority, comment/audit links and lifecycle metadata.
Imported native IDs set allocator floors so runtime-created records cannot reuse them. Native wire IDs are numeric; admin state keys remain strings.
Validate the complete seed before use:
worlds seed validate my-seed.json
Save a world's current state as a seed
In the dashboard, choose Save as starting setup from an eligible world. From the CLI:
worlds world export w_abc123def456 --name my-fork
The admin equivalent is GET /admin/worlds/:id/export-seed?name=my-fork. Use the result to start another test from the state your agent produced.
The export uses the world's current logical clock as its epoch and validates as a seed. Its lossy report lists state the seed format cannot retain:
| Not retained | What survives |
|---|---|
| Event log | Current supported records |
Native Zendesk export keeps supported directory records, tickets, comments, full audit history and lifecycle metadata. Legacy record offsets remain relative to the new epoch. Combined billing worlds also follow the Stripe export limits.
Exporting and reloading represents settled state, not a replay of the run. Re-exporting that loaded world preserves the same data block.
Library seeds
| Seed | Use |
|---|---|
zendesk-support-v1 |
Explore native directory, ticket, comment and audit records |
zendesk-outcomes-support-v1 |
Native support tasks; used by init --connector zendesk |
zendesk-outcomes-billing-v1 |
Combined support and Stripe billing tasks; used by init --connector composite |
Bundled seeds are synthetic fixtures. See the generated seed catalog for their builders and starter packs for ready-made tasks.