x0 for AI agents and API clients
How an AI agent (or any HTTP client) should fetch Xero-backed data from x0.
The Excel OData guide is for humans clicking through Excel. This page is the one agents should read.
Why x0 instead of calling Xero directly
- Stable table-shaped OData (
valuearrays) for Excel and agents - Server-side caching, rate-limit awareness, and sync progress
- One Bearer token per customer — no Xero OAuth dance in the agent
- Scoped API keys (
xero.accounts.read, wildcards, expiry)
Quick start (minimum)
First discover context:
GET https://api.x0.co.nz/odata/v1/X0
Authorization: Bearer x0_live_<token_id>.<secret>
Then data:
GET https://api.x0.co.nz/odata/v1/Accounts
Authorization: Bearer x0_live_<token_id>.<secret>
Optional tenant:
GET https://api.x0.co.nz/odata/v1/Accounts?tenant_id=<xero-tenant-guid>
Authorization: Bearer x0_live_<token_id>.<secret>
Or header: X-Xero-Tenant-Id: <xero-tenant-guid>
Response shape (OData v4):
{
"@odata.context": "https://api.x0.co.nz/odata/v1/$metadata#Accounts",
"value": [ { "AccountID": "...", "Code": "200", "Name": "Sales", "...": "..." } ]
}
Discovery
| Step | Request |
|---|---|
| 1. Who am I / connection | GET /odata/v1/X0 (scope xero.settings.read) |
| 2. List entity sets | GET /odata/v1 |
| 3. Schema | GET /odata/v1/$metadata |
| 4. Connected orgs | GET /odata/v1/Tenants |
| 5. Table catalog | GET /odata/v1/Tables |
| 6. Data | GET /odata/v1/Accounts, Items, CreditNotes, PurchaseOrders_Table, Journals_Table, … |
X0 returns one row: customer name/email/plan, tenant + last sync, API key label/prefix/scopes/environment, and DocsUrl / PortalUrl / ApiBaseUrl.
How tables join: data-schema.md (dashboard diagram + mermaid). MCP: get_schema_diagram.
Interactive OpenAPI: https://api.x0.co.nz/docs
Auth
- Issue a key in the customer portal (shown once).
- Format:
x0_live_<ULID>.<secret>orx0_test_<ULID>.<secret> - Headers (any of these works):
Authorization: Bearer <full-key>Authorization: Basic— username = token id (x0_live_<ULID>), password = secret; or full key in either fieldX-Api-Key: <full-key>- Keys have scopes and expiry. Missing scope →
403. Bad/expired key →401withWWW-Authenticate: Bearer, Basic realm="x0". - Optional: send
Accept-Encoding: gzip(Standard+ /gzip_responsesoption). x0 may replyContent-Encoding: gzip.
Never put the secret in URLs or chat logs. Prefer env vars / secret stores.
Query options agents should prefer
| Option | Use |
|---|---|
$select=Code,Name,Type |
Smaller payloads |
$filter=Status eq 'ACTIVE' |
Narrow rows |
$top=100 / $skip=100 |
Page through large sets |
$orderby=Code asc |
Stable ordering |
$count=true |
Include @odata.count |
Example:
GET /odata/v1/Accounts?$select=Code,Name,Type&$filter=Status eq 'ACTIVE'&$top=200
Authorization: Bearer …
Agent workflow (recommended)
- Confirm base URL (
https://api.x0.co.nzor localhttp://127.0.0.1:8001). GET /odata/v1/Tenants— pickTenantIdif more than one org.GET /odata/v1/Tables— see what is available for this key’s scopes.- Fetch entity sets with
$select/$filter/$top. - On
502/ emptyvaluewith note fields — data may still be syncing; check portal or retry later. - Before bulk pulls (BankTransactions, Invoices, Contacts, Payments…) call
GET /odata/v1/Usage.
Each Xero page counts against the customer's daily x0 plan cap; big tables can need many pages. - On
429, do not retry in a loop. Read the headers anderror.innererror, then wait.
Quotas and 429
GET /odata/v1/Usage (scope xero.settings.read, optional ?tenant_id=) returns one row per rule.
It never calls Xero and is never blocked by a cap.
| Rule | Layer | Meaning |
|---|---|---|
x0_plan_calls_day |
x0_plan |
x0 subscription cap on Xero calls per customer per day (e.g. Standard = 200). Resets 00:00 UTC. Xero 429s are not counted. |
x0_plan_egress_month |
x0_plan |
GB x0 may download from Xero per calendar month (UTC). |
partner_calls_org_day |
partner |
x0's Xero partner-tier calls per org per day. |
xero_day_remaining |
xero_platform |
Last X-DayLimit-Remaining Xero sent (rolling window, reset is an estimate). |
xero_minute_remaining |
xero_platform |
Last X-MinLimit-Remaining; x0 waits instead of failing. |
Columns: Used, Limit, Remaining, Pct, Blocking, ResetsAt (UTC), ResetsAtLocal
(NZ time), RetryAfterSeconds (when blocking), TenantId.
A cap-blocked request returns 429 with:
Retry-After: 4680
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1759017600
X-x0-RateLimit-Rule: x0_plan_calls_day
{"error": {"code": "rate_limit_exceeded",
"message": "BankTransactions_Table request blocked by your x0 plan limit: …",
"innererror": {"rule": "x0_plan_calls_day", "layer": "x0_plan", "used": 200, "limit": 200,
"remaining": 0, "resets_at": "2026-09-28T00:00:00+00:00",
"resets_at_local": "2026-09-28T13:00:00+13:00", "retry_after_seconds": 4680}}}
Tables already cached keep being served while a cap is blocking. If a big table was partly
fetched when the cap hit, x0 serves the rows it has (X-X0-Cache: partial) and continues from
the next page after the reset.
Skill vs MCP
| Approach | When |
|---|---|
| How-to page (this doc) | Any agent / Yaak / curl / n8n that can HTTP |
| Cursor skill | Teach Cursor how to call x0 correctly in this repo |
| MCP server | When chat agents should invoke tools (list_tables, describe_table, query_odata) without writing HTTP |
A skill is enough for coding agents that already have Shell/fetch. Use the MCP servers when the product goal is “ask natural language → understand schema + live Xero tables” inside Cursor/Claude without the user pasting URLs.
MCP setup (x0 + xerosim)
Shipped under shim/agent_mcp/ — see agent_mcp/README.md.
cd shim
python3 -m venv .venv-mcp
.venv-mcp/bin/pip install -r agent_mcp/requirements-mcp.txt
Cursor / Claude Desktop example:
{
"mcpServers": {
"x0": {
"command": "/path/to/shim/.venv-mcp/bin/python",
"args": ["-m", "agent_mcp.x0"],
"cwd": "/path/to/shim",
"env": { "X0_API_BASE_URL": "https://api.x0.co.nz" }
}
}
}
Tools expose per-table fields (name, type, description, sample value), linkages, cache/auth rules, and optional live query_odata when X0_API_KEY is set in the server env (never in tool args).
Full customer guide: mcp-agents.md (also in portal Guides).
Machine-friendly entry points
- OData service document:
/odata/v1 - OpenAPI UI:
/docs - This markdown: ship with the shim; also browsable in Admin → Documentation
- Yaak recipe: yaak.md
Related
- Documentation index · Glossary
- odata.md — full OData reference
- data-schema.md — ER diagram / mermaid
- yaak.md — GUI client setup
- agentic-query.md — in-portal Ask x0
- xero-simulator.md — hosted Xero sandbox (Professional+)
- support-queries.md — if the agent should open a support ticket instead