Create an org agent
The agents.create operation: POST /api/v1/agents in the REST API and the agents_create tool in the MCP server.
/api/v1/agentsCreates an agent: a machine identity for the organization's own AI agents and automations. It holds one
role at a scope (the organization, one legal entity or one brand), like a member, and gets access tokens
with OAuth 2.1 client_credentials at /oauth/token.
authMethod: "client_secret_basic"(default): the response contains the client secret incredentials.clientSecret. It is shown only once; only a hash is stored.authMethod: "private_key_jwt": give your public keys injwks; the agent signs a client assertion instead of sending a secret.
scopes are the permission scopes its tokens may carry (default: everything the role can use at that scope).
An agent is test mode only until livemodeAccess is turned on, whatever its role. It never gets more than
you have yourself at its scope and mode (agent_delegation_not_allowed), never org_owner, and never decides
approval requests. limits (requests per second, mutations per day, amounts per financial operation) and
toolSets (the MCP tool sets it sees: workflows, registry or operation areas such as tasks; none
means all of them) are applied by the runtime.
dpopBoundAccessTokens: true makes every token sender-constrained: the token endpoint refuses a request
without a DPoP proof (invalid_grant), so a leaked token is useless without the agent's private key.
At a glance
- REST:
POST /api/v1/agents - MCP tool:
agents_create - Operation ID:
agents.create - Scope: Organization
- Permission:
agents:admin - Risk tier:
security - Dry run: Supported (send
Ownrecord-Dry-Run: true) - Idempotency:
Idempotency-Keyrequired - Modes: Test and live
- Authentication: API key with the
agents:adminscope - Fresh sign-in: In the console, a sign-in within the last 15 minutes
- Since: M10
Request
Body
A JSON object (Content-Type: application/json):
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | |
description | string | No | |
contact | string | No | |
vendor | string | No | |
role | one of org_admin, billing_admin, finance, tax_manager, support, developer, viewer | Yes | |
scope | object | No | |
livemodeAccess | boolean | No | |
scopes | array of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document) | No | |
authMethod | one of client_secret_basic, private_key_jwt | No | |
jwks | object | No | |
jwks.keys | array of object | Yes | |
jwks.keys[].kty | one of RSA, EC, OKP | Yes | |
jwks.keys[].kid | string | No | |
jwks.keys[].use | "sig" | No | |
jwks.keys[].alg | string | No | |
dpopBoundAccessTokens | boolean | No | Require DPoP for every token: a token request without a DPoP proof is refused (RFC 9449). |
limits | object | No | |
limits.rps | integer (1 to 10000) or null | No | |
limits.mutationsPerDay | integer (0 to 10000000) or null | No | |
limits.amountLimits | array of object | No | |
limits.amountLimits[].operationId | string | Yes | |
limits.amountLimits[].perCall | money or null | No | |
limits.amountLimits[].perDay | money or null | No | |
toolSets | array of string | No |
Response
201 Created with a JSON object:
| Field | Type | Description |
|---|---|---|
agent | object | |
agent.id | agt_… ID | |
agent.object | "agent" | |
agent.orgId | org_… ID | |
agent.name | string | |
agent.description | string or null | |
agent.contact | string or null | |
agent.vendor | string or null | |
agent.ownerType | one of org, app | |
agent.appInstallationId | ains_… ID or null | |
agent.role | one of org_admin, billing_admin, finance, tax_manager, support, developer, viewer | |
agent.scope | object | |
agent.livemodeAccess | boolean | |
agent.limits | object | |
agent.limits.rps | integer or null | |
agent.limits.mutationsPerDay | integer or null | |
agent.limits.amountLimits | array of object | |
agent.limits.amountLimits[].operationId | string | |
agent.limits.amountLimits[].perCall | money or null | |
agent.limits.amountLimits[].perDay | money or null | |
agent.toolSets | array of string | |
agent.status | one of active, paused, revoked | |
agent.clientId | ocl_… ID | |
agent.authMethod | one of client_secret_basic, client_secret_post, private_key_jwt | |
agent.secretPrefix | string or null | |
agent.dpopBoundAccessTokens | boolean | |
agent.scopes | array of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document) | |
agent.createdBy | string | |
agent.createdAt | string | |
agent.updatedAt | string | |
agent.pausedAt | string or null | |
agent.revokedAt | string or null | |
credentials | object | |
credentials.clientId | ocl_… ID | |
credentials.clientSecret | string or null | |
credentials.tokenEndpoint | string | |
credentials.authMethod | one of none, client_secret_basic, client_secret_post, private_key_jwt |
Examples
A bookkeeping agent with a client secret
POST /api/v1/agents
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"name": "Bookkeeper",
"description": "Prepares and files the quarterly OSS return",
"contact": "ops@acme.example",
"vendor": "In-house, Claude via the MCP server",
"role": "finance",
"scopes": [
"filings:read",
"filings:write",
"tasks:read",
"tasks:write"
],
"limits": {
"amountLimits": [
{
"operationId": "refunds.create",
"perDay": {
"amount": "200000000",
"currency": "EUR"
}
}
]
}
}
Response (201):
{
"agent": {
"id": "agt_01m4ttwd3veh9apbewxheq5xh8",
"object": "agent",
"orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
"name": "Bookkeeper",
"description": "Prepares and files the quarterly OSS return",
"contact": "ops@acme.example",
"vendor": "In-house, Claude via the MCP server",
"ownerType": "org",
"appInstallationId": null,
"role": "finance",
"scope": {
"type": "org"
},
"livemodeAccess": false,
"limits": {
"rps": null,
"mutationsPerDay": null,
"amountLimits": [
{
"operationId": "refunds.create",
"perCall": null,
"perDay": {
"amount": "200000000",
"currency": "EUR"
}
}
]
},
"toolSets": [],
"status": "active",
"clientId": "ocl_01m4ttwd3veh9apbewxheq5xh9",
"authMethod": "client_secret_basic",
"secretPrefix": "or_cs_Exampl",
"dpopBoundAccessTokens": false,
"scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
"createdBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
"createdAt": "2026-10-25T09:00:00.000Z",
"updatedAt": "2026-10-25T09:00:00.000Z",
"pausedAt": null,
"revokedAt": null
},
"credentials": {
"clientId": "ocl_01m4ttwd3veh9apbewxheq5xh9",
"clientSecret": "or_cs_ExampleOnlyNotARealSecret0000000000000000",
"tokenEndpoint": "https://ownrecord.io/oauth/token",
"authMethod": "client_secret_basic"
}
}
An agent for one brand with a private key JWT
POST /api/v1/agents
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"name": "Support triage",
"role": "support",
"scope": {
"type": "brand",
"brandId": "brd_01m4ttwd3veh9apbewxheq5xh5"
},
"authMethod": "private_key_jwt",
"jwks": {
"keys": [
{
"kty": "EC",
"crv": "P-256",
"kid": "triage-1",
"x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
}
]
}
}
Response (201):
{
"agent": {
"id": "agt_01m4ttwd3veh9apbewxheq5xh8",
"object": "agent",
"orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
"name": "Support triage",
"description": null,
"contact": null,
"vendor": null,
"ownerType": "org",
"appInstallationId": null,
"role": "support",
"scope": {
"type": "brand",
"brandId": "brd_01m4ttwd3veh9apbewxheq5xh5"
},
"livemodeAccess": false,
"limits": {
"rps": null,
"mutationsPerDay": null,
"amountLimits": []
},
"toolSets": [],
"status": "active",
"clientId": "ocl_01m4ttwd3veh9apbewxheq5xh9",
"authMethod": "private_key_jwt",
"secretPrefix": null,
"dpopBoundAccessTokens": false,
"scopes": ["customers:read", "customers:write"],
"createdBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
"createdAt": "2026-10-25T09:00:00.000Z",
"updatedAt": "2026-10-25T09:00:00.000Z",
"pausedAt": null,
"revokedAt": null
},
"credentials": {
"clientId": "ocl_01m4ttwd3veh9apbewxheq5xh9",
"clientSecret": null,
"tokenEndpoint": "https://ownrecord.io/oauth/token",
"authMethod": "private_key_jwt"
}
}
Errors
| Code | HTTP status | When | What to do |
|---|---|---|---|
validation_failed | 400 | A field is invalid, e.g. a scope the role can't use, or private_key_jwt without public keys. | Fix the fields listed in details.issues and send the request again. |
agent_role_invalid | 400 | The role is org_owner, or isn't allowed at the scope's type. | Choose a role other than org_owner, at a scope the role allows (org, entity or brand; see the role table in the docs). |
agent_delegation_not_allowed | 403 | The agent would get a permission (or live access) you don't have at its scope. | Choose a narrower role, scope or token scopes, turn off live mode for the agent, or ask an organization owner to do it. |
resource_not_found | 404 | The scope's entity or brand doesn't exist in the organization. | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode. |
permission_denied | 403 | You can't manage agents, or you aren't a person or an org management key. | Ask an organization owner or admin for a role or scope that allows this action. |
fresh_auth_required | 403 | Your last sign-in is older than 15 minutes. | Sign in again, then repeat the action within 15 minutes. |
It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.