One door into your own data.

Products, orders, stock, freight and its invoices, parcels, and e-invoicing on Peppol — as a REST API with a tenant-bound key. Your key reaches your tenant and no other; there is no parameter that changes which.

Your first call
curl https://mcp.holio.ai/api/gateway/whoami \
  -H "Authorization: Bearer holio_<key_id>_<secret>"
documented resources
operations
contract version
60/min
default rate limit

Quick start

Four calls. If the first one answers, everything else will.

  1. 1 · Confirm the key

    whoami answers 200 only when the key, its tenant binding and its access row are all in place — so it separates “bad key” from “key with no access”, which every other route reports the same way.

    GET /api/gateway/whoami
    curl -s https://mcp.holio.ai/api/gateway/whoami \
      -H "Authorization: Bearer $HOLIO_KEY"
    
    {"company_slug":"vbq_example_as","company_name":"Example AS",
     "scopes":["mcp.read"],"can_write":false}
  2. 2 · List something

    Every resource lists the same way. The rows come back under a key named after the resource — not under data — because the response is also what our own screens read.

    GET /api/orders
    curl -s "https://mcp.holio.ai/api/orders?limit=5" \
      -H "Authorization: Bearer $HOLIO_KEY"
    
    {"count":5,"limit":5,"offset":0,"has_more":true,
     "catalog_orders":[{"order_number":"1042", ...}]}
  3. 3 · Narrow it

    q searches the resource's own search columns; every filter the resource declares is a query parameter of the same name. The reference below lists both, per resource, read from the live service.

    Note the response key: the path is the resource's public name (orders), the array is named after the table behind it (catalog_orders). The public name is what we keep stable; the key is what the service returns, and pretending otherwise would break your first parse.

    Filtering and sorting
    curl -s "https://mcp.holio.ai/api/orders?q=hansen&sort=order_date&dir=desc" \
      -H "Authorization: Bearer $HOLIO_KEY"
  4. 4 · Page through it

    Read has_more, not count against total. total costs a full count and is skipped on the fast path, so it is often absent — a client that waits for it pages forever.

    Paging
    offset=0
    while :; do
      page=$(curl -s "https://mcp.holio.ai/api/orders?limit=100&offset=$offset" \
        -H "Authorization: Bearer $HOLIO_KEY")
      echo "$page" | jq -r '.catalog_orders[].order_number'
      [ "$(echo "$page" | jq -r .has_more)" = "true" ] || break
      offset=$((offset + 100))
    done

Authentication, scopes and limits

The key

A key looks like holio_<key_id>_<secret> and goes in either header:

Authorization: Bearer holio_k1a2_...
X-Holio-Key: holio_k1a2_...

It is shown once, when it is created or rotated. We store a hash, so a lost key is replaced, never recovered.

A key belongs to exactly one tenant. The gateway removes every company-bearing parameter you send — company, company_no, tenant, client and the rest — and inserts the key's own afterwards. Sending one is not an error; it simply has no effect.

Scopes

mcp.readEvery GET, and POST /api/search.
mcp.writePATCH on the resources that declare editable fields.

A key without the scope gets 403. A route outside this documentation gets 404, not 403 — so the surface cannot be mapped by reading error codes.

OAuth 2.1

For tools that speak it, the same gateway is an authorization server with PKCE and dynamic client registration: /.well-known/oauth-authorization-server. The scopes are the two above.

Rate limit

Per key, per minute. The default is 60; your plan can raise it. Over the limit you get 429 with Retry-After in seconds — honour it rather than retrying immediately, because a retry storm is counted too.

Errors

One shape everywhere:

{"error":"denied",
 "reason":"no_membership",
 "detail":"..."}

Branch on reason, never on detail — the text is for a human reading a log.

Writing

PATCH /api/<resource>/{id} takes only the fields that resource declares editable; anything else is rejected by name, so you can fix the call without guessing. Writes are optimistic: the response is the accepted state, and propagation to the system of record happens behind it. Which resources are writable, and which fields, is in the reference below.

Reference

Read live from this deployment's own OpenAPI 3.1 document and catalog. Nothing on this page is transcribed by hand, so it cannot describe an operation the service does not serve.

Loading the contract…

E-invoicing — EHF and Peppol

Electronic invoicing becomes mandatory for Norwegian B2B on 1 January 2027. Holio sends on the Peppol network on your behalf; these endpoints are how your systems see it.

What is available now

GET /api/einvoices — every document your tenant has sent, newest first, with its lifecycle state and the delivery evidence when it exists.

GET /api/einvoice/status — whether the channel is open for you: registration, sender profile, connection verdict, and the list of what still needs attention before a document can go out. It reads stored state and spends no provider calls, so it is cheap to poll.

beta These two shapes may still change. They are documented as beta rather than held back, because reading your own submissions is useful before sending is.

What is not here yet, and why

There is no POST /api/einvoices. The send path is built — mapping, submission, receipt, resend and evidence all exist — but no document has been sent through it end to end yet, because the access-point credential is still being provisioned.

We do not document an endpoint we have never once executed. When the first invoice is proven on the test network, sending appears here, in the OpenAPI document, and in the changelog — in that order.

No access point in the contract

Holio reaches Peppol through an access-point provider. You will not find its name anywhere in this API: not in a path, not in a field, not in an error. That is deliberate and it is tested — the whole document is searched for it on every build.

The provider is ours to change. Your integration should not be able to notice when we do.

Getting a key

If you are a Holio customer

An administrator on your tenant issues the key, chooses its scopes, and can rotate or revoke it at any time. Rotation keeps the old key working for 24 hours so a deployment does not have to be simultaneous.

Ask your Holio contact, or write to hei@holio.ai with the tenant and what you intend to build.

If you are building for a customer

The key belongs to the tenant, not to you. Ask them to issue one and to name you as the integrator, so it can be revoked without touching their other integrations.

Stability

Resources marked beta may change shape. Everything else carries the promise: a breaking change is announced before it ships, and the previous shape keeps answering for the announced period.

Additive changes — a new field, a new resource, a new optional parameter — are not breaking, so write clients that ignore fields they do not know.