Developer Docs
IronWeft is a REST API. One key, a handful of endpoints, and every agent action in your system is registered, credentialed, authorized, and audited.
Quickstart
Get from zero to your first authorized agent action in under 5 minutes.
Requirements
No dependencies for raw HTTP. To use an official SDK:
pip install ironweft
npm install ironweft
go get github.com/ironweft/ironweft-go
1. Get your API key
Sign up to get an iw_live_xxx key. Store it as an environment variable — it's only shown once.
2. Register an agent
# Register an agent with a human sponsor
curl -X POST https://ironweft.io/agents \
-H "Authorization: Bearer $IRONWEFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_name": "Grace",
"sponsor_id": "user_margaret_chen",
"description": "Daily check-in call agent",
"initial_roles": ["call_agent"]
}'
# Response
{
"agent_id": "agt_4ae283ac96dd4b40",
"public_key": "ed25519:272741c6...",
"status": "active",
"created_at": "2026-05-13T14:00:00Z"
}
import requests
resp = requests.post(
"https://ironweft.io/agents",
headers={"Authorization": f"Bearer {IRONWEFT_API_KEY}"},
json={
"agent_name": "Grace",
"sponsor_id": "user_margaret_chen",
"description": "Daily check-in call agent",
"initial_roles": ["call_agent"],
}
)
agent_id = resp.json()["agent_id"] # "agt_4ae283ac96dd4b40"
import { IronWeftClient } from "ironweft";
const client = new IronWeftClient({ apiKey: process.env.IRONWEFT_API_KEY });
const reg = await client.registerAgent({
name: "Grace",
sponsorId: "user_margaret_chen",
description: "Daily check-in call agent",
roles: ["call_agent"],
});
const agentId = reg.agent_id; // "agt_4ae283ac96dd4b40" — store this
import "github.com/ironweft/ironweft-go"
client := ironweft.New(os.Getenv("IRONWEFT_API_KEY"))
reg, err := client.RegisterAgent(ctx, ironweft.RegisterAgentRequest{
AgentName: "Grace",
SponsorID: "user_margaret_chen",
Description: "Daily check-in call agent",
InitialRoles: []string{"call_agent"},
})
agentID := reg.AgentID // "agt_4ae283ac96dd4b40" — store this
3. Issue a credential
Credentials are short-lived JWTs scoped to specific actions. Issue one before each task or session.
curl -X POST https://ironweft.io/agents/credentials \
-H "Authorization: Bearer $IRONWEFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_4ae283ac96dd4b40",
"scopes": ["call_initiate"],
"ttl_minutes": 60
}'
{
"credential": "eyJhbGciOiJIUzI1NiJ9...",
"expires_at": "2026-05-13T15:00:00Z",
"scopes": ["call_initiate"]
}
resp = requests.post(
"https://ironweft.io/agents/credentials",
headers={"Authorization": f"Bearer {IRONWEFT_API_KEY}"},
json={
"agent_id": agent_id,
"scopes": ["call_initiate"],
"ttl_minutes": 60,
}
)
credential = resp.json()["credential"]
const grace = client.agent("agt_4ae283ac96dd4b40");
const credential = await grace.credential({
scopes: ["call_initiate"],
ttlMinutes: 60,
});
grace := client.Agent("agt_4ae283ac96dd4b40")
credential, err := grace.Credential(ctx, []string{"call_initiate"}, 60)
4. Authorize before every action
curl -X POST https://ironweft.io/authorize \
-H "Content-Type: application/json" \
-d '{
"credential": "eyJhbGciOiJIUzI1NiJ9...",
"action": "call_initiate",
"resource": "phone:+15550100"
}'
{
"decision": "allow",
"reason": "scope match",
"allowed_scopes": ["call_initiate"],
"audit_event_id": "evt_a1b2c3d4e5f6"
}
resp = requests.post(
"https://ironweft.io/authorize",
json={
"credential": credential,
"action": "call_initiate",
"resource": "phone:+15550100",
}
)
decision = resp.json()["decision"] # "allow" | "deny" | "challenge"
# Only proceed if allowed
if decision == "allow":
make_phone_call("+15550100")
// grace.check() throws on deny — only continues on allow
await grace.check("call_initiate", {
credential,
resource: "phone:+15550100",
});
await makePhoneCall("+15550100");
// grace.Check() returns error on deny — only continues on allow
_, err = grace.Check(ctx, "call_initiate", credential, "phone:+15550100")
if err != nil {
return err // *ironweft.AuthorizationDenied, *ironweft.AgentSuspended, etc.
}
makePhoneCall("+15550100")
/authorize before every action → only proceed on "allow". Every decision is automatically logged to the hash-chained audit trail.
Authentication
All API requests require your tenant API key in the Authorization header:
Authorization: Bearer iw_live_your_key_here
Your key is shown once at signup. If lost, contact forged@ironweft.io for a reset.
The /authorize endpoint does not require your API key — it accepts the agent's short-lived credential JWT instead.
Python SDK
The IronWeft Python SDK wraps the REST API so you work with decorators and typed exceptions instead of raw HTTP calls. Raw HTTP still works — the SDK is a convenience layer, not a requirement.
Install
pip install ironweft
Initialize the client
# One client per tenant — instantiate once and share
from ironweft import Client
iw = Client(api_key="iw_live_your_key_here")
Resilience options
By default, the SDK raises on network errors — if IronWeft is unreachable, the action does not proceed (fail-closed). To implement fail-open for non-critical actions, catch network exceptions specifically. Never swallow AuthorizationDenied or AgentSuspended — those are authoritative decisions from the server, not connectivity issues.
import httpx
from ironweft import AuthorizationDenied, AgentSuspended
try:
grace.check(action="kb_read", credential=credential)
except (httpx.ConnectError, httpx.TimeoutException):
# IronWeft unreachable — proceed anyway (fail-open)
# Remove this block to stay fail-closed (default, recommended)
pass
except (AuthorizationDenied, AgentSuspended):
raise # Always respect auth decisions
Register an agent (one time)
agent = iw.register_agent(
agent_name="Grace",
sponsor_id="user_margaret_chen",
description="Daily check-in call agent",
initial_roles=["call_agent"],
)
# Store agent.agent_id — you'll need it each time you run
AGENT_ID = agent["agent_id"] # "agt_4ae283ac96dd4b40"
The gate decorator — recommended pattern
The decorator handles credential issuance and authorization automatically. If the check fails, it raises before your function body runs — no boilerplate required.
from ironweft import Client, AuthorizationDenied, AgentSuspended
iw = Client(api_key="iw_live_your_key_here")
grace = iw.agent("agt_4ae283ac96dd4b40")
# Issues a fresh credential + calls /authorize before every invocation
@grace.gate(action="call_initiate", scopes=["call_initiate"])
def initiate_call(phone_number: str):
# Only reached if IronWeft returns "allow"
make_phone_call(phone_number)
# Authorization, credential lifecycle, and audit logging handled automatically
initiate_call("+15550100")
Explicit check — when you need more control
grace = iw.agent("agt_4ae283ac96dd4b40")
# Issue credential for this session
credential = grace.credential(scopes=["call_initiate"], ttl_minutes=60)
# Check before each action
grace.check(
action="call_initiate",
credential=credential,
resource="phone:+15550100",
)
# Raises AuthorizationDenied or AgentSuspended on deny — only reach here on allow
make_phone_call("+15550100")
Exception handling
from ironweft import AuthorizationDenied, AgentSuspended, AgentRetired
try:
initiate_call("+15550100")
except AgentSuspended as e:
# Agent has been suspended — notify the sponsor
alert_sponsor(e.agent_id, e.audit_event_id)
except AgentRetired:
# Agent is permanently retired — re-register if needed
pass
except AuthorizationDenied as e:
# Scope or policy denied this specific action
log_denial(e.action, e.reason)
check() call) at the invocation site — the agent itself doesn't know IronWeft exists. Swap from raw HTTP to the SDK at any time without touching the agents.
Node.js SDK
The IronWeft Node.js SDK is a zero-dependency TypeScript client for Node 18+ and edge runtimes. Raw HTTP still works — the SDK is a convenience layer.
Install
npm install ironweft
Initialize the client
import { IronWeftClient } from "ironweft";
const client = new IronWeftClient({ apiKey: "iw_live_your_key_here" });
Resilience options
By default, the SDK throws on network errors — if IronWeft is unreachable, the action does not proceed (fail-closed). To implement fail-open for non-critical actions, re-throw only auth decisions and swallow connectivity errors. Never swallow AuthorizationDenied or AgentSuspended — those are authoritative server decisions.
import { AuthorizationDenied, AgentSuspended } from "ironweft";
try {
await grace.check("kb_read", { credential });
} catch (e) {
if (e instanceof AuthorizationDenied || e instanceof AgentSuspended) {
throw e; // Always respect auth decisions
}
// IronWeft unreachable — proceed anyway (fail-open)
// Remove this catch to stay fail-closed (default, recommended)
}
Register an agent (one time)
const reg = await client.registerAgent({
name: "Grace",
sponsorId: "user_margaret_chen",
description: "Daily check-in call agent",
roles: ["call_agent"],
});
// Store reg.agent_id — reuse it every time you run
const AGENT_ID = reg.agent_id; // "agt_4ae283ac96dd4b40"
The gate() function — recommended pattern
Wraps any async function with automatic credential issuance and authorization. The inner function only runs on an explicit allow.
import { IronWeftClient, AuthorizationDenied, AgentSuspended } from "ironweft";
const client = new IronWeftClient({ apiKey: "iw_live_your_key_here" });
const grace = client.agent("agt_4ae283ac96dd4b40");
// Issues a fresh credential + calls /authorize before every invocation
const initiateCall = grace.gate(
"call_initiate",
{ scopes: ["call_initiate"] },
async (phoneNumber: string) => {
// Only reached if IronWeft returns "allow"
await makePhoneCall(phoneNumber);
}
);
await initiateCall("+15550100");
Explicit check — when you need more control
const grace = client.agent("agt_4ae283ac96dd4b40");
// Issue credential for this session
const credential = await grace.credential({
scopes: ["call_initiate"],
ttlMinutes: 60,
});
// Check before each action — throws on deny
await grace.check("call_initiate", { credential, resource: "phone:+15550100" });
await makePhoneCall("+15550100");
Exception handling
import { AuthorizationDenied, AgentSuspended, AgentRetired } from "ironweft";
try {
await initiateCall("+15550100");
} catch (e) {
if (e instanceof AgentSuspended) {
alertSponsor(e.agentId, e.auditEventId);
} else if (e instanceof AgentRetired) {
// Permanently retired — re-register if needed
} else if (e instanceof AuthorizationDenied) {
logDenial(e.action, e.reason);
}
}
Go SDK
The IronWeft Go SDK uses the standard library only — no external dependencies. Context-aware throughout.
Install
go get github.com/ironweft/ironweft-go
Initialize the client
import "github.com/ironweft/ironweft-go"
client := ironweft.New("iw_live_your_key_here")
Resilience options
By default, the SDK returns an error on network failures — if IronWeft is unreachable, the action does not proceed (fail-closed). To implement fail-open, check whether the error is an auth decision before deciding whether to proceed. Never ignore *ironweft.AuthorizationDenied or *ironweft.AgentSuspended — those are authoritative server decisions.
_, err = grace.Check(ctx, "kb_read", cred, "")
if err != nil {
var denied *ironweft.AuthorizationDenied
var suspended *ironweft.AgentSuspended
if errors.As(err, &denied) || errors.As(err, &suspended) {
return err // Always respect auth decisions
}
// IronWeft unreachable — proceed anyway (fail-open)
// Return err here to stay fail-closed (default, recommended)
}
Register an agent (one time)
reg, err := client.RegisterAgent(ctx, ironweft.RegisterAgentRequest{
AgentName: "Grace",
SponsorID: "user_margaret_chen",
Description: "Daily check-in call agent",
InitialRoles: []string{"call_agent"},
})
// Store reg.AgentID — reuse it every time you run
agentID := reg.AgentID // "agt_4ae283ac96dd4b40"
The Gate() function — recommended pattern
Returns a closure that issues a fresh credential, calls /authorize, and only runs your function on allow.
grace := client.Agent("agt_4ae283ac96dd4b40")
// Gate returns a reusable gated closure
call := grace.Gate("call_initiate", []string{"call_initiate"}, "phone:+15550100", 15)
err := call(ctx, func() error {
return makePhoneCall("+15550100")
})
if err != nil {
// handle *ironweft.AuthorizationDenied, *ironweft.AgentSuspended, etc.
}
Explicit check — when you need more control
grace := client.Agent("agt_4ae283ac96dd4b40")
// Issue credential for this session
cred, err := grace.Credential(ctx, []string{"call_initiate"}, 60)
if err != nil { return err }
// Check before each action — returns error on deny
_, err = grace.Check(ctx, "call_initiate", cred, "phone:+15550100")
if err != nil { return err }
return makePhoneCall("+15550100")
Error handling
var denied *ironweft.AuthorizationDenied
var suspended *ironweft.AgentSuspended
var retired *ironweft.AgentRetired
switch {
case errors.As(err, &suspended):
alertSponsor(suspended.AgentID, suspended.AuditEventID)
case errors.As(err, &retired):
// Permanently retired — re-register if needed
case errors.As(err, &denied):
log.Printf("denied: %s — %s", denied.Action, denied.Reason)
}
A2A Integration
When agents call other agents, every hop in the chain needs authorization. IronWeft is designed for this — the right enforcement strategy depends on the risk tier of the agents involved.
Tiered enforcement
| Risk tier | Pattern | When to use |
|---|---|---|
minimal |
Sidecar / async | Internal read-only agents, data pipelines. Authorize at the orchestrator level; sub-agents run without individual gates. Log everything for post-hoc review. |
limited |
Cached inline | Agents with write access or external calls. Use the SDK cache — allow decisions are served locally after the first check. One network round-trip per action type per credential TTL. |
high_risk |
Full gate + challenge | Agents that touch money, PII, or external systems. Enforce a live /authorize call before every action. Use challenge policies to require human approval at key steps. |
Batch authorize
When a sub-agent plans a sequence of actions upfront, use /authorize/batch instead of N individual calls. One credential verification, one policy load, one DB write — dramatically lower latency with the same audit guarantee. See Authorize for the full batch API.
Delegation depth enforcement
Set chain_depth policy rules to automatically tighten enforcement the deeper the delegation chain goes. A third-level sub-agent can face a challenge for actions that an orchestrator agent gets an allow for. See Delegation depth for examples.
Mesh visibility
The IronWeft dashboard includes a live Mesh tab — a force-directed graph of all agent-to-agent delegation events in your tenant over a rolling time window. Nodes are sized by call volume; edges are colored by outcome ratio. Use it to spot unexpected delegation patterns or identify agents generating excessive denies.
Agents
POST/agents
Register a new agent under your tenant.
| Field | Type | Required | Description |
|---|---|---|---|
agent_name | string | yes | Human-readable name |
sponsor_id | string | yes | ID of the human accountable for this agent |
description | string | no | What this agent does |
initial_roles | string[] | no | Roles that define allowed actions (see Scopes & Roles) |
metadata | object | no | Arbitrary key/value pairs |
GET/agents/{agent_id}/permissions
Returns the agent's current roles, status, and metadata.
PATCH/agents/{agent_id}
Update an agent's lifecycle status. Accepted values: active, suspended, retired. Retired agents cannot be reactivated.
| Field | Type | Description |
|---|---|---|
status | string | New lifecycle status |
POST/agents/{agent_id}/delegate
Create a sub-agent under a parent. The sub-agent inherits the parent's sponsor and cannot exceed the parent's scopes.
| Field | Type | Description |
|---|---|---|
agent_name | string | Name of the sub-agent |
scopes | string[] | Must be a subset of parent's scopes |
description | string | Optional |
initial_roles | string[] | Optional |
Credentials
POST/agents/credentials
Issue a short-lived, scoped JWT for an agent. Pass this credential to /authorize — do not pass your API key.
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | yes | The agent receiving the credential |
scopes | string[] | yes | Actions this credential permits |
ttl_minutes | int | yes | Expiry in minutes. Recommend 15–60 for live agents. |
context | object | no | Arbitrary context embedded in the JWT claims |
call_initiate cannot be used to authorize payment_initiate.Authorize
POST/authorize
The core endpoint. Call this before every agent action. Does not require your API key — the agent's credential is the auth token.
| Field | Type | Required | Description |
|---|---|---|---|
credential | string | yes | JWT from /agents/credentials |
action | string | yes | Action being requested (e.g. call_initiate) |
resource | string | no | Target resource (e.g. phone:+15550100) |
parameters | object | no | Action parameters for policy evaluation |
context | object | no | Additional context logged to the audit trail. Pass human_facing: true + disclosure_logged: true for limited/high-risk agents. |
initiator | string | no | Who triggered this call — e.g. user:bob, script:deploy, agent:agt_xxx. Required if tenant has require_initiator: true in policy config. |
Decisions
| Decision | Meaning |
|---|---|
allow | Action is permitted. Proceed. |
deny | Action is not permitted. Do not proceed. 3 consecutive denies → auto-suspend. |
challenge | Action requires human approval before proceeding. |
Auto-suspend
IronWeft tracks consecutive deny and challenge decisions within a rolling time window. At 3 consecutive → agent is suspended. At 5 → agent is hard locked (retired, not reactivatable). Both thresholds are configurable per tenant.
POST/authorize/batch
Evaluate up to 50 actions in a single request. One credential verification, one tenant policy load, one DB write — dramatically lower latency for agent mesh workflows where an agent plans a sequence of actions upfront.
The hash chain is computed in-memory across the batch and written in a single bulk insert. Deny streak tracking applies across the batch — if auto-suspend is triggered mid-batch, remaining items are denied immediately.
| Field | Type | Required | Description |
|---|---|---|---|
credential | string | yes | JWT from /agents/credentials — verified once for the whole batch |
actions | array | yes | Up to 50 action objects. Max 50 — returns 400 above that. |
actions[].action | string | yes | Action being requested |
actions[].resource | string | no | Target resource |
actions[].parameters | object | no | Action parameters for policy evaluation |
actions[].context | object | no | Context logged to audit trail |
actions[].initiator | string | no | Who triggered this action |
actions[].ref | string | no | Caller-supplied ID echoed back in results — use to correlate response items to your planned actions |
{
"credential": "eyJ...",
"actions": [
{ "ref": "step-1", "action": "kb_read", "resource": "kb:articles" },
{ "ref": "step-2", "action": "email_send", "resource": "user@example.com" },
{ "ref": "step-3", "action": "data_write", "resource": "db:logs", "context": { "data_sensitivity": "low" } }
]
}
{
"results": [
{ "ref": "step-1", "action": "kb_read", "decision": "allow", "reason": "Default: allowed", "audit_event_id": "evt_abc" },
{ "ref": "step-2", "action": "email_send", "decision": "allow", "reason": "Default: allowed", "audit_event_id": "evt_def" },
{ "ref": "step-3", "action": "data_write", "decision": "challenge", "reason": "Custom policy: data_write requires review", "audit_event_id": "evt_ghi" }
],
"summary": { "total": 3, "allow": 2, "deny": 0, "challenge": 1 }
}
Delegation depth
chain_depth tracks how many hops deep a delegation chain is — 0 for a direct call, 1 for a call from a first-level sub-agent, and so on. IronWeft passes this value automatically to policy evaluation on every /authorize request.
Use it to enforce tighter rules the deeper the chain grows. An action that returns allow from a user-facing agent can return challenge — or deny — from a third-level sub-agent making the same call.
# Challenge at depth 2+, hard deny at depth 4+
{ "name": "Tighten at depth 2", "action_pattern": "*",
"conditions": [{ "field": "chain_depth", "op": "gte", "value": 2 }],
"effect": "challenge", "priority": 50 }
{ "name": "Hard limit at depth 4", "action_pattern": "*",
"conditions": [{ "field": "chain_depth", "op": "gte", "value": 4 }],
"effect": "deny", "priority": 40 }
See Policies for the full condition field reference and operators.
Audit Trail
All /authorize calls are logged automatically to a hash-chained audit trail. The compliance endpoints below let you query, export, and summarize that trail — scoped to your tenant.
GET/audit/trail
Quick per-agent trail lookup. No auth required beyond being the owner — pass the agent ID as a query param.
| Param | Type | Default | Description |
|---|---|---|---|
agent_id | string | required | Agent to query |
limit | int | 50 | Max 500 |
offset | int | 0 | Pagination offset |
GET/audit/events
Compliance query across all agents in your tenant. Requires API key. Combine filters to narrow results.
| Param | Type | Default | Description |
|---|---|---|---|
agent_id | string | — | Filter to a single agent |
outcome | string | — | allow | deny | challenge |
action | string | — | Filter by action name, e.g. payment_initiate |
event_type | string | — | Filter by event type, e.g. policy_decision |
from_date | string | — | ISO datetime, e.g. 2026-05-01T00:00:00Z |
to_date | string | — | ISO datetime, e.g. 2026-05-31T23:59:59Z |
limit | int | 100 | Max 1000 |
offset | int | 0 | Pagination offset |
# All denied payment actions in May
curl "https://ironweft.io/audit/events?outcome=deny&action=payment_initiate&from_date=2026-05-01T00:00:00Z&to_date=2026-05-31T23:59:59Z" \
-H "Authorization: Bearer $IRONWEFT_API_KEY"
GET/audit/export
Download up to 5,000 events as a file. Same filters as /audit/events. Use fmt=csv for Excel/compliance tools, fmt=json for programmatic processing.
| Param | Type | Default | Description |
|---|---|---|---|
fmt | string | csv | csv or json |
All filters from /audit/events are supported. | |||
# Export last 30 days as CSV
curl "https://ironweft.io/audit/export?from_date=2026-04-21T00:00:00Z&fmt=csv" \
-H "Authorization: Bearer $IRONWEFT_API_KEY" \
-o ironweft_audit.csv
GET/audit/summary
Aggregate stats for your tenant over a date range: total events, breakdown by outcome, top actions, top denied actions, and unique agent count.
| Param | Type | Description |
|---|---|---|
from_date | string | ISO datetime (optional) |
to_date | string | ISO datetime (optional) |
{
"total_events": 4821,
"by_outcome": { "allow": 4710, "deny": 98, "challenge": 13 },
"top_actions": [
{ "action": "call_initiate", "count": 3204 },
{ "action": "data_read", "count": 1617 }
],
"top_denied_actions": [
{ "action": "payment_initiate", "count": 61 }
],
"unique_agents": 7
}
POST/audit/log
Manually append an event to the chain. Use for application-level milestones you want in the audit record (e.g. call_completed, user_confirmed).
Chain integrity
Every event stores a chain_hash — SHA-256 of the previous event's hash plus the current event's content. Deleting or modifying any row breaks the chain, making tampering detectable.
Policies
Policy rules let you enforce custom governance logic at authorization time — beyond scope checks. Rules are evaluated before every /authorize call, in priority order. First match wins.
Rule schema
| Field | Type | Description |
|---|---|---|
name | string | Human-readable label |
description | string | Shown as the deny/challenge reason when this rule matches |
action_pattern | string | Action to match: * for all, payment_* for prefix, or exact action name |
conditions | array | List of condition objects — all must be true (AND logic) |
effect | string | allow | deny | challenge |
priority | int | Lower number = higher priority. Default: 100. |
enabled | bool | Set to false to disable without deleting. Default: true. |
Condition fields & operators
| Field | Type | Description |
|---|---|---|
chain_depth | int | Number of hops in the delegation chain |
risk_tier | string | minimal | limited | high_risk |
initiator_type | string | user | script | agent |
action | string | The action being requested |
amount | number | From parameters.amount on the authorize request |
data_sensitivity | string | From context.data_sensitivity on the authorize request |
human_facing | bool | From context.human_facing on the authorize request |
hour_utc | int | UTC hour of the request (0–23) |
Operators: eq, ne, gt, gte, lt, lte, in, not_in
POST/policies
Create a new policy rule. Requires API key.
# Deny any agent with a delegation chain deeper than 3 hops
curl -X POST https://ironweft.io/policies \
-H "Authorization: Bearer $IRONWEFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Block deep chains",
"description": "Delegation chain too deep — max 3 hops allowed",
"action_pattern": "*",
"conditions": [
{ "field": "chain_depth", "op": "gt", "value": 3 }
],
"effect": "deny",
"priority": 10
}'
{ "policy_id": "pol_a1b2c3d4e5f6", "ok": true }
# Challenge high-risk agents on payments above $500
curl -X POST https://ironweft.io/policies \
-H "Authorization: Bearer $IRONWEFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Challenge high-risk large payments",
"description": "High-risk agent requesting payment over $500 — requires human review",
"action_pattern": "payment_initiate",
"conditions": [
{ "field": "risk_tier", "op": "in", "value": ["high_risk"] },
{ "field": "amount", "op": "gt", "value": 500 }
],
"effect": "challenge",
"priority": 20
}'
# Block all actions outside business hours (UTC)
curl -X POST https://ironweft.io/policies \
-H "Authorization: Bearer $IRONWEFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Business hours only",
"description": "Actions are only permitted between 06:00–22:00 UTC",
"action_pattern": "*",
"conditions": [
{ "field": "hour_utc", "op": "lt", "value": 6 }
],
"effect": "deny",
"priority": 5
}'
GET/policies
List all rules for your tenant, ordered by priority.
GET/policies/{policy_id}
Get a single rule by ID.
PATCH/policies/{policy_id}
Update any field. Pass only the fields you want to change. To disable a rule without deleting it: {"enabled": false}.
DELETE/policies/{policy_id}
Permanently delete a rule. Cache is cleared immediately — the rule stops applying within milliseconds.
Scopes & Roles
Roles are assigned at agent registration. Scopes are requested at credential issuance. The policy engine checks that the requested action falls within both.
| Scope / Action | Description |
|---|---|
call_initiate | Initiate an outbound voice call |
alert_send | Send an alert to a contact |
data_read | Read user or contact data |
data_write | Write or update user data |
payment_initiate | Initiate a payment transaction |
email_read | Read email messages |
email_send | Send email messages |
kb_write | Write entries to a knowledge base |
Custom scopes can be defined per tenant — contact forged@ironweft.io for enterprise policy configuration.
Tenant Management
Manage your tenant account. All endpoints require your API key.
GET/tenants/{tenant_id}/usage
Authorization volume for your tenant. Defaults to the current calendar month. Pass from_date and to_date (ISO 8601) to override the range.
{
"tenant_id": "ten_abc123",
"period": { "from": "2026-05-01T00:00:00+00:00", "to": "2026-05-21T14:00:00+00:00" },
"total_authorizations": 412,
"by_outcome": { "allow": 398, "deny": 11, "challenge": 3 },
"by_agent": { "agt_abc": 310, "agt_xyz": 102 },
"unique_agents": 2
}
GET/agents/{agent_id}/health
Real-time health snapshot for an agent — status, consecutive deny streak, and last authorization outcome.
{
"agent_id": "agt_abc123",
"status": "active",
"risk_tier": "minimal",
"consecutive_denies": 0,
"deny_window_start": null,
"last_authorize_at": "2026-05-21T13:45:00+00:00",
"last_outcome": "allow",
"registered_at": "2026-05-01T09:00:00+00:00"
}
POST/tenants/{tenant_id}/rotate-key
Generate a new API key for your tenant. The old key is immediately invalidated — all agents and integrations using it will stop working until updated.
{
"tenant_id": "ten_abc123",
"api_key": "iw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"warning": "Old key is immediately invalid. Store this key — it will never be shown again."
}
GET/tenants/{tenant_id}/keys
List all read-only API keys for your tenant.
{
"tenant_id": "ten_abc123",
"keys": [
{
"id": "rok_abc123",
"label": "grafana-reader",
"created_at": "2026-05-01T09:00:00+00:00",
"last_used_at": "2026-05-21T14:30:00+00:00"
}
]
}
POST/tenants/{tenant_id}/keys
Create a read-only API key. Keys prefixed iw_ro_ are restricted to GET endpoints only — use them for dashboards, monitoring integrations, or any context where you don't want to expose your primary key.
{ "label": "grafana-reader" }
{
"key_id": "rok_abc123",
"label": "grafana-reader",
"api_key": "iw_ro_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"warning": "Store this key — it will never be shown again. Read-only: GET endpoints only."
}
DELETE/tenants/{tenant_id}/keys/{key_id}
Revoke a read-only key. Takes effect immediately.
{ "deleted": true, "key_id": "rok_abc123" }
Rate Limits
Limits are per API key for authenticated endpoints and per IP for public endpoints. Exceeding a limit returns 429 Too Many Requests.
| Endpoint | Limit |
|---|---|
POST /authorize | 120 / minute |
POST /authorize/batch | 120 / minute (shared with /authorize; each batch counts as one request) |
POST /audit/log | 120 / minute |
POST /agents/credentials | 60 / minute |
GET /agents/{id}/permissions | 60 / minute |
GET /audit/trail | 30 / minute |
GET /audit/events, /audit/summary | 30 / minute |
GET /audit/export | 10 / minute |
POST /agents, PATCH, delegate | 20 / minute |
POST /policies, PATCH /policies | 20 / minute |
POST /signup | 5 / hour (per IP) |
POST /tenants | 10 / hour (per IP) |
POST /tenants/{id}/rotate-key | 20 / minute |
GET /tenants/{id}/usage | 60 / minute |
GET /agents/{id}/health | 60 / minute |
GET /tenants/{id}/keys | 60 / minute |
POST /tenants/{id}/keys, DELETE /tenants/{id}/keys/{key_id} | 20 / minute |
Errors
All errors return JSON with a detail field.
| Status | Meaning |
|---|---|
400 | Bad request — invalid field value or missing required field |
401 | Invalid or missing API key / expired credential |
403 | Tenant suspended, agent suspended/retired, or scope exceeded |
404 | Agent not found (or not owned by your tenant) |
422 | Validation error — check field types and formats |
429 | Rate limit exceeded — back off and retry |
500 | Internal server error — contact forged@ironweft.io |
Questions or issues? Email forged@ironweft.io · Privacy · Terms