# Call the API directly


This page is the human walkthrough for calling Economico over HTTPS, without an
MCP connector. The same procedure, written as steps an agent can follow, is
[direct-api.md](/direct-api.md). Status codes and recovery live in the
[API reference](/docs/api/). Authentication details live in
[authentication](/docs/authentication/).

Economico has one host, `https://economi.co`. REST is `/v1` on that host. The
OAuth resource is `https://economi.co/mcp`, and the only scope is `mcp`. A
token from the ChatGPT resource (`https://economi.co/mcp/chatgpt`) cannot call
`/v1`.

## Before you send a write

Use a demo company or another disposable sandbox. Open a demo from
[the demo picker](/oauth/business/demo), connect it, and take its slug from
`GET /v1/businesses`. Do not use the slug `economico`. That is Economico's own
company.

`GET /v1/business` with `X-Economico-Business` returns `demo: true` on a demo,
and the name ends in `(demo)`. Record the sandbox invoice only there, or on a
slug you have decided is disposable. Keep the access token out of the shell
history you commit, out of prompts, and out of URLs.

## Authorize

Discovery:

```sh
curl -sS https://economi.co/.well-known/oauth-authorization-server
curl -sS https://economi.co/.well-known/oauth-protected-resource/mcp
```

Interactive access is the authorization-code grant with PKCE `S256`, `scope`
`mcp`, and `resource` `https://economi.co/mcp`. A person approves the
connection in the browser. Access tokens last one hour. Interactive refresh
tokens last seven days. Refresh them at `POST /oauth/token` with
`grant_type=refresh_token`.

An unattended client uses `POST /oauth/headless/token` with
`grant_type=client_credentials` and a `private_key_jwt` assertion. That
response has an access token and no refresh token. The assertion's `aud` is
`https://economi.co/oauth/headless/token`, and its lifetime is at most 60
seconds. Signup of that client is the [authentication](/docs/authentication/)
guide, not this one.

Send the access token as `Authorization: Bearer`. A missing or rejected token
is HTTP 401 with `code` `unauthorized`, a `message`, a `hint`, and a
`WWW-Authenticate` challenge.

## Name the business

`GET /v1/businesses` needs the bearer and no business header. Every other
business route needs `X-Economico-Business: <slug>`. Vendor-catalog routes also
omit the header. `GET /v1/openapi.json` without the header is the connection
document. With the header, it is that business's permitted operations.
`GET /openapi.json` is public and grants nothing.

A missing header on a business route is HTTP 400 `validation` ("Set
X-Economico-Business to the business for this request"). A slug the connection
cannot use is HTTP 403 `forbidden` ("Access to this business is unavailable").
That text does not tell you whether the slug exists.

## See what the token may do

Authority is membership, the application grant, and the token ceiling,
intersected on every call. Read the statements. Do not infer them from the
scope `mcp`.

```sh
curl -sS https://economi.co/v1/catalog/permissions \
  -H "Authorization: Bearer $ECONOMICO_TOKEN" \
  -H "X-Economico-Business: $ECONOMICO_BUSINESS"
```

`GET /v1/catalog` lists permitted command names and descriptions.
`GET /v1/catalog/commands/parties.create` returns that command's schemas,
examples, and `credential`. If `credential` is `browser`, an OAuth token cannot
run it. `GET /v1/reports` lists report kinds. Describe a command before you
send it. The live catalog wins over this page.

## Read a balance sheet

This does not change the books. `currency` is optional. `USD` selects the USD
ledger. Amounts are integer strings of minor units.

```sh
curl -sS https://economi.co/v1/reports \
  -H "Authorization: Bearer $ECONOMICO_TOKEN" \
  -H "X-Economico-Business: $ECONOMICO_BUSINESS" \
  -H "Content-Type: application/json" \
  -d '{"kind":"balance_sheet","currency":"USD"}'
```

## Record one sandbox invoice

The copy-paste bodies are in [direct-api.md](/direct-api.md). They follow the
published monthly-subscription recipe: store a short order form, create a
customer, create a priced right and a template, create a draft contract, accept
it, then `contracts.record` with `phase` `billing`. There is no `invoices.send`.
`messages.send` is a message to another Economico business, and a demo refuses
it when `to` is set (`demo_business`, HTTP 403). This walkthrough does not send
one.

Each command is `POST /v1/commands` with `name`, `input`, `idempotency_key`, and
`effective_at`. `effective_at` is UTC and ends in `Z`. Retry a lost response
with the same key and the same input. The second success has `replayed: true`.
The same key with a different command, input, or `source_document_id` is HTTP
409 `idempotency_conflict`. A domain no-op is HTTP 200 `no_change` and has no
receipt.

After the billing record, read the July window:

```sh
curl -sS https://economi.co/v1/reports \
  -H "Authorization: Bearer $ECONOMICO_TOKEN" \
  -H "X-Economico-Business: $ECONOMICO_BUSINESS" \
  -H "Content-Type: application/json" \
  -d '{"kind":"income_statement","currency":"USD","from":"2026-07-01","until":"2026-08-01T00:00:00Z"}'
```

The recipe's later recognition step is what expects `revenue.total` of `4900`.
This walkthrough stops at the bill, so treat a 200 as success and read the
integer strings. It does not collect the payment.

## When a call fails

| Status | Meaning |
| --- | --- |
| 400 | The body does not match the current schema. Read the command again. |
| 401 | Authorize again. |
| 402 | `plan_required`. Nothing was written. Read `details.reason`. |
| 403 | This caller cannot do that. A demo also uses `demo_business`. |
| 409 | The idempotency key or the record already exists. Do not mint a new key for the same intent. |
| 429 | A real budget was exhausted. See below before sleeping `Retry-After`. |
| 503 | `busy`. The call did not start. Retry the same body after `Retry-After`. |

Authenticated `/v1` has no general per-token rate limit. Anonymous `/public/`
reads are 60 per minute per address. Vendor-catalog reads are 120 per minute
per principal. A demo stops accepting writes after 2000 events. Solo without a
paid agreement stops at 100 billable events in the period (`plan_required`,
`reason` `solo_limit`). Logo replacements are capped per UTC day. A 429
`logo_churn_exceeded` still gets `Retry-After: 60` from the generic header, and
that wait does not reset the day.

The [agent procedure](/direct-api.md) has the request bodies, the header rules,
and the stop conditions in one page.

