Documentation

Everything you need to put an AI agent to work inside otorize: the concepts, the first steps, the API and a complete n8n example.

How it works

An AI agent, whether an n8n workflow, a Python bot or an assistant, works in a room alongside people. It can talk freely, but when it wants to do something real (send an invoice, block an IP, change a record) it does not do it on its own: it proposes it to otorize.

otorize checks the proposal against your organization’s rules and picks the path: it goes through, it asks a person, or it stops. The agent gets the decision and acts only if it is approved. Every step goes on the record, which cannot be changed.

otorize does not run actions for you and has no access to your systems: it decides whether the agent may go ahead. Execution stays in your own tools.

Concepts

Organization

Your space on otorize: people, agents, rooms and rules. Each organization’s data is encrypted with a key of its own.

Room

A permanent place of work, such as “Accounts” or “IT-ops”. Every member has a role: viewer reads, operator talks and proposes actions, approver decides on actions, admin also manages roles. Whoever creates a room becomes its approver. Agents can only be viewers or operators: they never approve.

Case

A piece of work with a start and an end, such as “Onboarding Bianchi” or “Year-end close”. It comes with its own room; when you close it the room becomes read-only and everything stays on the record. Policies can apply to a single case.

Agents and keys

Each agent is a member with its own identity and API keys (otorize_ak_…). A key is shown once: otorize keeps only its fingerprint. You can restrict which IP addresses it connects from and revoke a key at any time.

Action catalog

The actions agents may propose. Each has a key (invoice.send), a parameter schema, a risk class and the sentence approvers read. That sentence is built from the parameters, not from what the agent writes, so nobody can slip instructions into a request.

{
  "type": "object",
  "required": ["number", "customer", "amount", "currency"],
  "additionalProperties": false,
  "properties": {
    "number":   { "type": "string", "maxLength": 20 },
    "customer": { "type": "string", "maxLength": 100 },
    "amount":   { "type": "number", "minimum": 0 },
    "currency": { "enum": ["EUR"] }
  }
}

Policies and autonomy

A policy says how much autonomy an action has, for the whole organization, a room, a case or a single agent. The most specific wins: agent, then room, then case, then unit, then organization. With no policy, a person decides.

ModeWhat happens
manualEvery action waits for a person.
supervisedThe action goes ahead at once; a supervisor can veto it within a time window.
arbiterThe arbiter checks the parameters against the rules: within them it goes through, otherwise it asks a person or blocks.
autopilotNo waiting; volume limits and the record still apply.
confidenceGoes through if the agent’s stated confidence is above the threshold, otherwise asks a person.

Critical-risk actions always go to a person, whatever mode you pick. When in doubt otorize does not authorize: a missing parameter or an error ends up in front of a person.

Lists and limits

Rules can use lists (trusted customers, company IPs that must never be blocked) and sum or count limits over time, such as “at most €10,000 a day per agent”.

The record

Every event in every room is stored in order, chained to the previous one by a cryptographic fingerprint. From the room you can check at any time that the chain is intact.

First steps

  1. Create a room, for example “Accounts”. You become its approver.
  2. Add the action the agent will propose to the catalog. To try it out there is a ready-made “send an invoice” example.
  3. In Agents, create the agent and generate a key. Copy it right away into the agent’s secret store.
  4. Add the agent to the room as operator, with the Members button in the room.
  5. Send the first proposal (see below) and approve it from the room or from the link in the email.

Connecting an agent

The agent talks to otorize over HTTPS, with its key in the Authorization header. Base address: https://api.otorize.app.

Proposing an action

curl -X POST "https://api.otorize.app/v1/rooms/ROOM_ID/proposals?wait=25" \
  -H "Authorization: Bearer $OTORIZE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ordine-4471" \
  -d '{"action":"invoice.send","params":{"number":"FT-1","customer":"Rossi Srl","amount":1240,"currency":"EUR"}}'

With wait=25 the call waits up to 25 seconds for the decision and returns it. Without wait it returns at once: the agent can poll GET /v1/proposals/{id} or receive a webhook.

{
  "proposal": {
    "id": "b53e479b-0582-487f-b24a-01e668112f54",
    "action": "invoice.send",
    "params": { "number": "FT-1", "customer": "Rossi Srl", "amount": 1240, "currency": "EUR" },
    "summary": "Invia la fattura FT-1 a Rossi Srl per 1240 EUR",
    "status": "approved",
    "decision_clause": "human.approved 1/1",
    "expires_at": "2026-10-19T20:29:41.893Z",
    "veto_until": null
  },
  "replayed": false
}
FieldDescription
actionThe key of an action in the catalog.
paramsThe parameters, validated against the action’s schema.
summaryOptional: the agent’s own explanation, shown separately. It does not replace the sentence built from the parameters.
confidenceOptional, 0 to 1: used by the confidence mode.
contextOptional: context data kept with the proposal.
expires_in_secondsOptional: time to decide, from 60 seconds to 7 days (default 1 day).
Idempotency-KeyOptional header: repeating the same call with the same key does not create a second proposal.

Proposal states

StateMeaning
pendingWaiting for a person.
approvedApproved: the agent may act.
rejectedRejected by a person.
blockedStopped by the rules.
expiredNobody decided in time.
executed / failedThe agent reported the outcome.

Reporting the outcome

After acting, the agent reports how it went. Only the agent that proposed can do it, and only once: a second call gets 409.

curl -X POST "https://api.otorize.app/v1/proposals/PROPOSAL_ID/result" \
  -H "Authorization: Bearer $OTORIZE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"executed","result":{"sdi_id":"90231577"}}'

Messages in the room

An agent can also write in the room with POST /v1/rooms/{id}/messages and a text field, for instance to explain what it is doing or ask people a question.

Webhooks

If you set a callback address for the agent (Agents, Connect tab), otorize sends it a webhook when a proposal is decided (proposal.decided) and when someone mentions it in the room (message.posted). It is the right way for decisions that take more than a few seconds.

The body is {"type": "...", "data": {...}}. The x-otorize-signature header carries a timestamp and an HMAC-SHA256 signature computed with the agent’s webhook secret. Always verify it and refuse messages older than five minutes.

import { createHmac, timingSafeEqual } from 'node:crypto';

// header: x-otorize-signature: t=<unix seconds>,v1=<hex>
export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false;
  const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

n8n example

In n8n, otorize is an HTTP Request node in the middle of the flow: the agent proposes, waits for the decision and carries on only if it is approved.

n8n invoicing workflow with the approval request to otorize
  1. In n8n, create a “Header Auth” credential named Authorization with value Bearer followed by the agent’s key.
  2. Import one of the workflows below (… menu > Import from File) and assign the credential to the “otorize: …” nodes.
  3. Replace ROOM_ID with the room id (it is in the room’s address) and create in the catalog the action the workflow uses.
  4. Run the workflow and approve from the room: n8n takes the right branch and reports the outcome to otorize.

In the examples, external services (AI, e-invoicing, firewall, notifications) have sample data pinned: you can run them straight away and swap in your own nodes when ready. The otorize calls are real.

Approving by email

When an action waits for a person, the room’s approvers get an email with the summary and a button. The link carries a single-use, personal code with an expiry, which never reaches server logs. Opening the link decides nothing: you have to press Approve or Reject.

Errors

Errors always look like {"error": {"code": "...", "message": "...", "request_id": "..."}}.

HTTPCodeWhat to do
401unauthorizedMissing, wrong or revoked key.
403org_pendingThe organization is not active yet.
403plan_limit_reachedYour plan does not allow it.
404not_foundThe room does not exist or the agent is not in it; or the action is not in the catalog.
409conflictAlready decided, outcome already reported, or an idempotency key reused.
422unprocessableThe parameters do not match the action’s schema: the details say which.
429rate_limited / daily_limit_reachedToo many requests or daily limit reached: retry after Retry-After.

Security in short

  • Messages, parameters and emails are encrypted with a separate key for each organization.
  • API keys are generated by otorize and stored only as a fingerprint.
  • Each agent can be restricted to specific IP addresses or networks.
  • Agent requests are rate limited, to contain mistakes and abuse.
  • Rules read only structured parameters, never the chat: an agent cannot talk them round.
  • The record is a fingerprint chain, verifiable by anyone with access to the room.

To report a security issue, write to [email protected].