Create a role in a tenant.
const url = 'https://shiftagent.example.com/tenants/example/roles';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"name":"csr","description":"Customer service representative","skill_access":{"mode":"selected","skill_ids":["skl_01hzx8dispatch","skl_01hzx8invoice"]}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://shiftagent.example.com/tenants/example/roles \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "name": "csr", "description": "Customer service representative", "skill_access": { "mode": "selected", "skill_ids": [ "skl_01hzx8dispatch", "skl_01hzx8invoice" ] } }'Creates a tenant-scoped role. A role is an access profile:
an optional repository_id override (when null, the role
resolves to the tenant’s default repository) plus a skill_access
narrowing ({"mode": "all"} or {"mode": "selected", "skill_ids": [...]} — IDs must belong to the role’s effective repository).
name is unique per tenant — this is load-bearing for replay-safe
provisioning: a crashed cold path that retries gets a deterministic
409 name-conflict with conflicting_resource_id, fetches that
role, and continues.
Cold-path step 3: create the role with skills picked from
listRepositorySkills.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Internal tenant ID.
Header Parameters
Section titled “Header Parameters ”Optional idempotency key (any unique string, e.g. a UUID; max 255 chars). Responses are cached 24h per (key principal, operation, key); replays return the original status and body with Idempotency-Replayed: true. Reusing a key with a different payload responds 409 idempotency-key-conflict.
Request Body required
Section titled “Request Body required ”Body for createRole.
object
Tenant-unique name (409 name-conflict if taken).
Human-readable purpose.
Optional repository override (must be a registered repository); omit to resolve to the tenant default.
Grant every skill of the role’s effective repository.
object
Access-mode discriminator.
Grant only the listed skills of the role’s effective repository.
object
Access-mode discriminator.
Skills to grant — each must belong to the role’s effective repository (422 validation-error otherwise).
Free-form string key–value map for host/adapter bookkeeping (e.g. a host-side reference ID). Max 50 keys; values max 500 chars. Replaced wholesale when provided in updates.
object
Examples
Cold-path role on the tenant default repository
{ "name": "csr", "description": "Customer service representative", "skill_access": { "mode": "selected", "skill_ids": [ "skl_01hzx8dispatch", "skl_01hzx8invoice" ] }}Role pinning a repository override
{ "name": "dispatcher", "description": "Dispatch desk — dedicated tooling repository", "repository_id": "rep_01hzx8dispatchtools", "skill_access": { "mode": "all" }}Responses
Section titled “ Responses ”Role created.
Tenant-scoped access profile: optional repository override + skill narrowing. Effective repository = repository_id ?? tenant default; effective skills = effective repository’s skills ∩ skill_access.
object
Unique per tenant — load-bearing for replay-safe provisioning (deterministic 409 + conflicting_resource_id recovery).
Human-readable purpose.
Repository override; null falls through to the tenant default repository.
Grant every skill of the role’s effective repository.
object
Access-mode discriminator.
Grant only the listed skills of the role’s effective repository.
object
Access-mode discriminator.
Skills to grant — each must belong to the role’s effective repository (422 validation-error otherwise).
RFC 3339 / ISO 8601 timestamp, UTC.
RFC 3339 / ISO 8601 timestamp, UTC.
Examples
CSR role with selected skills
{ "object": "role", "id": "rol_01hzx8csr001", "tenant_id": "tnt_01hzx8acme001", "name": "csr", "description": "Customer service representative", "repository_id": null, "skill_access": { "mode": "selected", "skill_ids": [ "skl_01hzx8dispatch", "skl_01hzx8invoice" ] }, "created_at": "2026-07-02T09:35:00Z", "updated_at": "2026-07-02T09:35:00Z"}Missing or invalid credentials — no bearer token, an unknown/revoked sk_int_ key, or an expired platform JWT.
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Missing or invalid bearer token
{ "type": "https://shiftagent.example.com/problems/insufficient-scope", "title": "Unauthorized", "status": 401, "detail": "Provide a valid sk_int_ service key or platform JWT.", "request_id": "req_01hzx8auth001"}Not found — the resource does not exist, was deprovisioned, or lies outside the integration key’s subtree (indistinguishable by design).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Unknown resource
{ "type": "https://shiftagent.example.com/problems/not-found", "title": "Not found", "status": 404, "detail": "No tenant with external_id acme:tenant:999999.", "request_id": "req_01hzx8nf001"}Conflict — name-conflict / external-id-conflict (unique name or external ID taken; conflicting_resource_id names the holder — fetch it and continue), resource-in-use (guarded delete refused), cross-tenant (referenced resource belongs to another tenant), conversation-archived (write to an archived conversation), approval-expired (approval already resolved or expired), or idempotency-key-conflict (same key, different payload).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Named sub-resource already exists — recoverable
{ "type": "https://shiftagent.example.com/problems/name-conflict", "title": "Name conflict", "status": 409, "detail": "A role named \"csr\" already exists in this tenant.", "conflicting_resource_id": "rol_01hzx8csr001", "request_id": "req_01hzx8conf01"}Guarded delete refused
{ "type": "https://shiftagent.example.com/problems/resource-in-use", "title": "Resource in use", "status": 409, "detail": "Repository is attached to 1 tenant and pinned by 2 roles.", "conflicting_resource_id": "tnt_01hzx8acme001", "request_id": "req_01hzx8used01"}Unprocessable — validation-error (schema/semantic validation failed; errors[] lists JSON-pointer details) or role-required (user has multiple roles and no role_id was given).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Field-level validation failure
{ "type": "https://shiftagent.example.com/problems/validation-error", "title": "Validation error", "status": 422, "detail": "One or more fields failed validation.", "errors": [ { "pointer": "/skill_access/skill_ids/0", "message": "skl_01hzx8unknown does not belong to the effective repository." } ], "request_id": "req_01hzx8val001"}Ambiguous role at conversation creation
{ "type": "https://shiftagent.example.com/problems/role-required", "title": "Role required", "status": 422, "detail": "User usr_01hzx8jane001 holds 2 roles; pass role_id explicitly.", "request_id": "req_01hzx8role01"}