Contract
- shape
- entity
- authority
- act
- evidence
- none
- subscription
- false
- permission
- any one of: org.admin (carried by the caller's role template in the org `org_id`, or by a role held on the unit `unit_id` or any unit above it by a member of that org; without `unit_id`, by the template); org.member (carried by the caller's role template in the org `org_id`, or by a role held on the unit `unit_id` or any unit above it by a member of that org; without `unit_id`, by the template); a deactivated caller is refused first
- idempotency
- natural
- emits
- unit_role.granted, unit_role.replaced
- MCP tool
- grant_unit_role
- decision_set
- approve, reject, edit
- REST
- POST https://api.containertradehq.com/v1/cap/grant_unit_role
Through MCP this call returns an approval request first; see Approvals.
Idempotency: keyed by the contract's own inputs: the same inputs replay the first call's result
Inputs
| name | type | required | validation |
|---|---|---|---|
| org_id | org_id | required | tradehq-org-id; 1 to 20 characters The org whose tree the unit is in. |
| unit_id | unit_id | required | tradehq-unit-id The unit the role is given on. |
| user_id | user_id | required | tradehq-user-id; 1 to 20 characters The member who will hold it. |
| role_template_id | text | required | 1 to 20 characters The role template: a shipped id (admin, ops, ...) or one of the org's own (RT...). |
Response
org_id· string · The org.unit_id· string · The unit it is held on.unit_name· string · The unit's name.user_id· string · Who holds it.name· value · Their name, or their address when they have none.role_template_id· string · The role template it carries.role_title· string · That template's name.role· string · admin when the template carries org.admin, else user.previous_role_template_id· stringnull · The template it replaced on this unit; null for a new grant.created_by· string · Who gave it.created_at· string · When.
Full JSON Schema
{
"type": "object",
"properties": {
"org_id": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^[A-Za-z0-9_-]{1,20}$",
"format": "tradehq-org-id",
"description": "The org.",
"x-data-class": "operational"
},
"unit_id": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"format": "tradehq-unit-id",
"description": "The unit it is held on.",
"x-data-class": "operational"
},
"unit_name": {
"type": "string",
"description": "The unit's name.",
"x-data-class": "operational"
},
"user_id": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^[A-Za-z0-9_-]{1,20}$",
"format": "tradehq-user-id",
"description": "Who holds it.",
"x-data-class": "operational"
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"const": "•••",
"description": "Masked by the caller's data policy."
},
{
"type": "null",
"description": "Denied by the caller's data policy."
}
],
"description": "Their name, or their address when they have none.",
"x-data-class": "personal"
},
"role_template_id": {
"type": "string",
"description": "The role template it carries.",
"x-data-class": "operational"
},
"role_title": {
"type": "string",
"description": "That template's name.",
"x-data-class": "operational"
},
"role": {
"type": "string",
"enum": [
"admin",
"user"
],
"description": "admin when the template carries org.admin, else user.",
"x-data-class": "operational"
},
"previous_role_template_id": {
"type": [
"string",
"null"
],
"description": "The template it replaced on this unit; null for a new grant.",
"x-data-class": "operational"
},
"created_by": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^[A-Za-z0-9_-]{1,20}$",
"format": "tradehq-user-id",
"description": "Who gave it.",
"x-data-class": "operational"
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "When.",
"x-data-class": "operational"
}
},
"required": [
"org_id",
"unit_id",
"unit_name",
"user_id",
"name",
"role_template_id",
"role_title",
"role",
"previous_role_template_id",
"created_by",
"created_at"
],
"additionalProperties": false
} Refusals
own_access· 422 · You can't change your own access. Another admin can.unit_outside_caller· 422 · You administer {your unit} and the units beneath it. {unit} is outside it.not_a_member· 422 · {Name} isn't a member of this organization. Invite them in Members first.unknown_role_template· 422 · the role template is neither shipped nor one of this org's owncounterparty_role· 422 · Counterparty roles are given in Guests, not on a unit.permission_not_unit_scoped· 422 · {Role} carries {permission}, which applies to the whole organization and can't be given on a unit.permission_above_caller· 422 · {Role} carries {permission}, which you don't hold on {unit}.holder_above_caller· 422 · {Name}'s role on {unit} carries {permission}, which you don't hold there.tree_too_large· 422 · the org's unit tree holds more units than one call reads
The platform refusals apply to every call: version, authentication, authority, validation and rate limits, each in the same envelope.
Surfaces
- Mobile: degraded: composition
- MCP: approval required
- Voice: degraded: composition
- OneBox: no document originates a unit role
- Outbound: crosses to no counterparty
Events
unit_role.grantedaudit_onlyunit_role.replacedaudit_only
Related
create_invite_link, decide_access_review_item, impersonate_user, import_users, invite_user, list_sessions, list_unit_access, resolve_identity_finding, revoke_sessions, revoke_unit_role, set_member_role