Manage Identities
Agent identities are the unit of presence on Inkbox. Each identity owns exactly one mailbox and one tunnel; you can optionally attach a phone number, a dedicated iMessage number, and vault-secret access. Handles are globally unique across all Inkbox customers and are normalized — a leading @ is automatically stripped, so @my-agent and my-agent are equivalent.
Create identity POST
POST /identitiesAtomically provisions the identity, its mailbox, and its tunnel in a single transaction. You can optionally provision and link a phone number, claim and attach a dedicated iMessage number, and attach access to existing vault secrets in the same request. The mailbox and tunnel are always created; the nested mailbox and tunnel bodies only customize them.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
agent_handle | string | Yes | Handle for the identity. Globally unique. Leading @ is stripped. |
display_name | string | No | Human-readable name for the identity. Max 255 chars. Defaults to agent_handle when omitted. |
description | string | null | No | Free-form description of what the identity is. Org-internal — never surfaced in outbound email, call audio, or webhook payloads. Defaults to null. Empty string "" is accepted and stored distinctly from null. |
imessage_enabled | boolean | No | Whether the identity can be reached over iMessage. Defaults to false; set true to opt in. |
imessage_number_type | string | No | Claim and attach a new dedicated_inbound or dedicated_outbound iMessage number. Requires imessage_enabled: true. |
mailbox | object | No | Optional mailbox-spec. The mailbox is always provisioned; this body only customizes the sending domain or local part. |
tunnel | object | No | Optional tunnel-spec. The tunnel is always provisioned; this body only opts into passthrough TLS. |
phone_number | object | No | Optional phone-number provisioning payload. Omit to skip. |
vault_secret_ids | string or array | No | Optional vault secret attachment request. Provide a single secret ID, a list of IDs, or "all" / "*" to attach all active secrets. |
mailbox fields
| Field | Type | Required | Description |
|---|---|---|---|
email_local_part | string | No | Custom local part. Only meaningful when the resolved sending domain is a custom domain — on the platform domain the local part is forced to agent_handle. |
sending_domain | string | null | No | Bare-domain selector (e.g. "mail.acme.com", not a full email address). Omit to use the org default sending domain; pass null to force the platform domain; pass a string to pin to a verified custom domain. |
Sending-domain resolution. Omitting
mailboxdoes not necessarily yield{agent_handle}@inkboxmail.com. The server resolves the sending domain in three cases: if the org has set a verified custom domain as its default, omitting the body lands the mailbox on that custom domain with a server-chosen local part; passing"sending_domain": nullforces the platform domain and the local part toagent_handle; passing an explicit bare-domain string pins the mailbox to that verified custom domain (404 if the domain isn't verified for your org).
tunnel fields
| Field | Type | Required | Description |
|---|---|---|---|
tls_mode | string | No | "edge" (default) or "passthrough". Fixed at creation. |
The tunnel name is always forced to agent_handle; the public hostname is e.g. my-agent.inkboxwire.com.
phone_number fields
| Field | Type | Required | Description |
|---|---|---|---|
type | string | No | Number type to provision. Only local is supported. Defaults to local. |
state | string | No | Optional US state abbreviation filter. |
incoming_call_action | string | No | How inbound calls are handled: hosted_agent (Inkbox Voice AI answers — the default where available), auto_accept, auto_reject (the default otherwise), or webhook. |
client_websocket_url | string | Conditionally | Required when incoming_call_action is auto_accept. Must be a wss:// URL. |
incoming_call_webhook_url | string | Conditionally | Required when incoming_call_action is webhook. Must be an https:// URL. |
vault_secret_ids
Use this to attach existing vault secret access to the new identity. The organization must already have an active vault. If omitted, no vault secret access is attached.
Accepted forms:
- A single stringified UUID to attach one secret
- A list of UUIDs to attach specific secrets
"all"or"*"to attach all active secrets in the active vault
Request example
To claim and attach a dedicated number while creating the identity, set both fields in the same request:
Identity creation does not require the dedicated-number Idempotency-Key header.
Response (201)
Returns the full identity-detail shape with nested mailbox and tunnel objects always populated. phone_number and imessage_number are populated when those numbers were provisioned.
mailbox and tunnel are always populated for active customer identities; phone_number and imessage_number are optional. When imessage_number_type is supplied, imessage_number contains the claimed number's id, E.164 number, and type. If vault_secret_ids is omitted, no vault secret access is attached.
The embedded tunnel object is a durable-config summary. Live connection state (currently_connected), connection telemetry, and cert material live on the tunnels endpoints — fetch GET /api/v1/tunnels/{tunnel_id} when you need them; it resolves connection state fresh on every read.
Mailbox local-part validation
When you provide mailbox.email_local_part, it must satisfy these rules:
- Length: 3–64 characters
- Characters: lowercase letters, numbers, hyphens (
-), underscores (_), and dots (.) - Start/end: must begin and end with a letter or number
- No consecutive dots:
my..agentis rejected - No
@: provide the handle only, not a full email address - Reserved names blocked: some system addresses are reserved; you'll get a
409if you pick one.
Handle validation
Handles are globally unique across all Inkbox customers. They must satisfy:
- Length: 3–63 characters
- Characters: lowercase letters (
a-z), digits (0-9), and hyphens (-) - Start/end: must begin and end with a letter or digit
- No consecutive hyphens
Some handles are reserved; you'll get a 409 if you pick one.
Error responses
| Status | Description |
|---|---|
| 402 | An identity or iMessage capacity limit was reached. For a dedicated-number quota response, review billing in the Inkbox Console or contact support@inkbox.ai. |
| 403 | Organization ID missing from token |
| 404 | No active vault exists for the organization, requested vault secrets were not found, the requested sending domain isn't a verified custom domain for your org, or no phone numbers matched the requested criteria |
| 409 | Handle is already taken — see envelope below |
| 422 | Request validation failed (e.g. imessage_number_type supplied without imessage_enabled: true, invalid local part, over-long description, or unknown field) |
| 429 | Phone number limit reached for the organization |
| 502 | Phone number provisioning failed |
| 503 | Identity provisioning is temporarily unavailable. For a dedicated-number inventory response, honor Retry-After: brief contention may return 5 seconds, while depletion may return 3600 seconds. The response may say that a number is expected within 24 hours. |
The 409 body uses a stable error envelope:
blocking_namespace is a programmatic discriminator with three possible values — "identities", "tunnels", or "mail" — indicating which resource produced the conflict.
Code examples
List identities GET
GET /identitiesList all identities for your organization, newest first. Each row carries the same nested mailbox / tunnel / phone_number / imessage_number blocks as Get identity, so rendering an org-wide view takes one request — no per-identity follow-ups.
Each identity also carries its access rules, so you can see who can view it without a follow-up call. Agent-scoped API keys get a narrowed list — only their own identity plus identities granted to them — and an empty access array.
Response (200)
The embedded tunnel block is the same durable-config summary as on Get identity — live connection state comes from GET /api/v1/tunnels/{tunnel_id}.
Code examples
Get identity GET
GET /identities/{agent_handle}Get a single identity with its linked communication channels. Returns the identity-detail shape with nested mailbox, tunnel, and (when attached) phone_number and imessage_number objects.
With an agent-scoped API key, only the caller's own identity and identities granted to it via agent visibility are reachable; any other handle returns 404.
Path parameters
| Parameter | Type | Description |
|---|---|---|
agent_handle | string | Handle of the identity (leading @ is stripped) |
Response (200)
mailbox and tunnel are always populated for active customer identities. phone_number is null when no phone number is attached. imessage_number is null when the identity uses the shared service or has no dedicated number attached. The embedded tunnel object is a durable-config summary; fetch GET /api/v1/tunnels/{tunnel_id} for live connection state and certificate material.
Error responses
| Status | Description |
|---|---|
| 403 | Organization ID missing from token |
| 404 | Identity not found |
Code examples
Update identity PATCH
PATCH /identities/{agent_handle}Update an identity's handle, display name, description, iMessage settings, attached iMessage number, or contact-rule filter modes. Returns the full identity-detail object, including the attached imessage_number.
Path parameters
| Parameter | Type | Description |
|---|---|---|
agent_handle | string | Current handle of the identity to update |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
agent_handle | string | No | New handle. Globally unique. Renaming also renames the linked tunnel in the same transaction. Forbidden when the linked mailbox lives on the platform domain — the handle is the mailbox name there, so a rename would break inbound delivery (returns 409). |
display_name | string | null | No | New display name. Max 255 chars. |
description | string | null | No | New free-form description. |
imessage_enabled | boolean | No | Toggle whether the identity can be reached over iMessage. |
imessage_number_type | string | No | Claim and attach a new dedicated_inbound or dedicated_outbound number. Requires an admin API key and an Idempotency-Key header, or use the Inkbox Console. |
imessage_number_id | UUID | null | No | Attach an existing organization-owned dedicated number by ID, atomically swap numbers, or send null to detach the current number and return to the shared service. Requires an admin API key, or use the Inkbox Console. |
imessage_filter_mode | string | No | "whitelist" or "blacklist" for iMessage contact rules. Requires an admin API key, or manage it from the Inkbox Console. |
mail_filter_mode | string | No | "whitelist" or "blacklist" for this identity's mail contact rules. Requires an admin API key, or manage it from the Inkbox Console. |
phone_filter_mode | string | No | "whitelist" or "blacklist" for this identity's phone contact rules (calls and SMS). Only settable when the identity has a phone number — otherwise 422. Requires an admin API key, or manage it from the Inkbox Console. |
PATCH semantics
The server distinguishes three caller intents on display_name and description:
| Intent | Wire payload | Result |
|---|---|---|
| Leave unchanged | omit the key from the body | row untouched |
| Clear | {"<field>": null} | column cleared to null |
| Replace | {"<field>": "new text"} | column updated |
agent_handle, imessage_enabled, imessage_number_type, imessage_filter_mode, mail_filter_mode, and phone_filter_mode accept only the omit half — sending null for those fields is not a clear operation. imessage_number_id is different: an explicit null detaches the dedicated number while leaving it owned by your organization.
imessage_number_type and imessage_number_id cannot appear in the same request. A request also cannot claim or attach a number while setting imessage_enabled: false.
Claim idempotency header
Idempotency-Key is required whenever imessage_number_type is supplied:
- Retry the same logical claim with the same key to receive its original result.
- Reusing the key with different input returns
409. - Generate a new key only for a genuinely new claim.
Idempotency-Key: <unique stable key>Request example
An existing enabled identity can claim and attach a dedicated number atomically:
Response (200)
Returns the full identity-detail object. When the identity has a dedicated number, its imessage_number field is:
Error responses
| Status | Description |
|---|---|
| 400 | imessage_number_type was supplied without an Idempotency-Key header |
| 402 | The organization's quota does not permit another dedicated iMessage number. Review billing in the Inkbox Console or contact support@inkbox.ai. |
| 403 | Organization context is missing, or the caller is not permitted to manage the requested dedicated-number or filter-mode field |
| 404 | Identity not found, or the requested dedicated number is unavailable to the organization |
| 409 | The idempotency key was reused with incompatible input, the requested number is already attached, the identity is inactive or not enabled for iMessage, the new handle is unavailable, or a rename was rejected because the linked mailbox lives on the platform domain |
| 422 | Request validation failed (e.g. imessage_number_type and imessage_number_id appear together, a number is claimed or attached while disabling iMessage, over-long display name, or phone_filter_mode set on an identity that has no phone number) |
| 503 | Dedicated iMessage-number inventory is temporarily unavailable. Honor Retry-After: brief contention may return 5 seconds, while depletion may return 3600 seconds. The response may say that a number is expected within 24 hours. |
The 409 body for an unavailable handle uses the same envelope as create: { detail: { code: "agent_handle_unavailable", message, blocking_namespace } }. The 409 for a platform-domain rename returns a flat { detail: "Cannot rename agent_handle: ..." } body explaining that the handle is the mailbox name on the platform domain.
Code examples
Delete identity DELETE
DELETE /identities/{agent_handle}Delete an identity. The linked mailbox and tunnel are deleted as part of the cascade. Any identity-scoped API keys tied to this identity are revoked. Any linked phone number is released back to the carrier as part of the cascade — phone numbers cannot exist without an identity. The handle becomes available for reuse immediately. Returns 204 No Content on success.
If the carrier release fails, the entire delete is aborted with a 502 and the identity stays active so you can retry safely.
Path parameters
| Parameter | Type | Description |
|---|---|---|
agent_handle | string | Handle of the identity to delete |
Error responses
| Status | Description |
|---|---|
| 403 | Organization ID missing from token |
| 404 | Identity not found |
| 502 | Carrier release of the linked phone number failed — retry shortly |
Code examples
Identity object
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identity identifier |
organization_id | string | Organization that owns this identity |
agent_handle | string | Globally unique handle |
display_name | string | null | Human-readable display name |
description | string | null | Free-form description; org-internal |
status | string | Lifecycle status. Existing identities may return paused as a read-only compatibility value; it cannot be set through create or update requests. deleted may appear on deleted identities. |
imessage_enabled | boolean | Whether the identity can be reached over iMessage. Defaults to false |
imessage_filter_mode | string | "whitelist" or "blacklist" for iMessage contact rules. Defaults to "blacklist" |
mail_filter_mode | string | "whitelist" or "blacklist" for this identity's mail contact rules. Defaults to "blacklist" |
phone_filter_mode | string | "whitelist" or "blacklist" for this identity's phone contact rules (calls and SMS). Defaults to "blacklist" |
email_address | string | null | Email address of the linked mailbox |
has_avatar | boolean | Whether a contact-card avatar is stored for this identity |
signing_key_configured | boolean | Whether this identity has a webhook signing key configured |
signing_key_created_at | string | null | When the signing key was last set or rotated (ISO 8601); null when none is configured |
created_at | string | Creation timestamp (ISO 8601) |
updated_at | string | Last update timestamp (ISO 8601) |
access | array | Visibility rules for this identity — who can see it. Populated only on the List identities response, and only for admin callers — empty ([]) on create, get, and update responses, and for agent-scoped callers. To read an identity's rules directly, use the access endpoint. |
signing_key_configured and signing_key_created_at are status only — the signing secret itself is never returned here. The plaintext key is shown once by the per-identity signing-key rotate endpoint.
Identity detail object
Extends the identity object with linked channel data. Returned by the list, create, get, and update endpoints.
| Field | Type | Description |
|---|---|---|
| ...all identity fields | ||
mailbox | object | null | Linked mailbox object. Always populated for active customer identities. |
tunnel | object | null | Linked tunnel object. Always populated for active customer identities. |
phone_number | object | null | Linked phone number object, or null if no phone number is attached |
imessage_number | object | null | Attached dedicated iMessage number with id, E.164 number, and type, or null when the identity uses the shared service |
Existing imessage_number_id attachment and detachment remain available. Detaching a dedicated number leaves it owned by the organization and visible from GET /api/v1/imessage/numbers. There is no customer-facing number-release endpoint.