API and MCP

Purchases via the API — balance payment and a top-up link

How an AI agent orders SSL, registers and renews domains with an API key: payment from the balance, a top-up link for a person, payment notice.

A script or AI agent can buy with an API key, but it pays only from the organization's balance. If the balance is short, the API does not refuse — it returns a link for a person: they top up exactly the shortfall by card or by an IBAN invoice, and the order goes to work at once. The agent learns about the payment by itself and carries on.

Right now this works for ordering an SSL certificate (the ssl.order right) renewing a domain (the domain.renew right) and registering a new one (the domain.register right), see API keys. An agent can do the same through MCP with a key.

How it works

  1. The agent orders a certificate: POST /v1/ssl/certificates with "if_short": "pay_link".
  2. If the balance covers it, the amount is charged at once. The answer is 201, the certificate goes to domain validation.
  3. If money is short, nothing is charged or reserved. The answer is 402 with the code payment_required and a payment_url link.
  4. The agent passes the link to a person: in a chat, by email, in a message.
  5. The person opens the link without signing in, sees the order and the amount due, and pays by card or by an IBAN invoice.
  6. The money lands on the balance, and the order is paid from the balance right away.
  7. The agent sees the order is paid (via GET /v1/orders/{id} or a webhook) and goes on: waits for issuance and fetches the certificate files.

Without if_short the API behaves as before: when money is short it answers 402 balance_short and creates no order.

An order with a top-up link

curl -X POST https://api.zrazu.com/v1/ssl/certificates \
  -H "Authorization: Bearer zr_live_..." \
  -H "Idempotency-Key: 0b7c6e1a-3f42-4d8e-9a51-2c6f0e9d7b10" \
  -H "Content-Type: application/json" \
  -d '{"product_id": "…", "mode": "vat", "domain": "example.com.ua",
       "csr": "-----BEGIN CERTIFICATE REQUEST-----…", "dcv_method": "dns",
       "if_short": "pay_link"}'

The answer when money is short:

{
  "error": { "code": "payment_required", "message": { "uk": "…", "en": "…" } },
  "amount_due": 22500,
  "payment_url": "https://vse.zrazu.com/pay-link/…/",
  "expires_at": "2026-11-16T12:00:00Z",
  "order": { "id": "…", "number": "202611-48271", "status": "awaiting_payment", "amount_due": 22500, "items": [ … ] },
  "certificate": { "id": "…", "status": "unpaid", … }
}

Amounts are in kopecks. amount_due is the order minus the live money on the balance; bonuses are not counted.

The top-up link

  • The link is valid for 72 hours. The page shows the order, the amount due and the seller. Your organization's data is not on it.
  • By card: the bank fee is added on top, and the button shows the full amount. By IBAN invoice: the invoice is issued for the shortfall, and its number matches the order number.
  • The amount is recalculated every time the page is opened. If the balance was topped up meanwhile, less is due.
  • If the link was lost or has expired, POST /v1/orders/{id}/payment-link issues a new one. The previous link of this order then stops working. While a person is paying by card, no new link is issued: the answer is 409 payment_in_progress.
  • If the balance was topped up another way (in the cabinet, by transfer), the agent pays the order itself: POST /v1/orders/{id}/pay. If money is short again, the answer is 402. While the previous link is valid there is no new one (payment_url is null): the person pays through the same link.
  • A key can pay for and issue links only for orders it made itself. An order from the cabinet or another key does not exist for it (404).

The link is the right to top up exactly this order. Pass it to people you trust: the amount due hints at roughly how much is on the balance.

How the agent learns that the payment arrived

Polling is the main way for AI agents. Chat assistants do not accept incoming requests, so they ask:

curl -H "Authorization: Bearer zr_live_..." "https://api.zrazu.com/v1/orders/{id}?wait=60"

With wait (up to 60 seconds) the answer comes as soon as the order changes, or after wait seconds if nothing happened. Repeat the call in a loop. Up to 3 waits at once per key and up to 10 per organization; beyond that you get 429 too_many_waits, so ask without wait.

FieldMeaning
statusawaiting_payment — waiting for money; review — money arrived, the order is in a security review; paid — paid; partly_failed — part was not fulfilled, money for it is on the balance; cancelled — cancelled
amount_duehow much is still due, in kopecks. The amount reveals the balance, so a key with only ssl.read gets null; keys with ssl.order or billing.read see it
fulfilledtrue — everything is done: the certificate is issued or finally refused
items[].certificate_idthe certificate id: the agent continues with /v1/ssl/certificates/{id}
payment_link_expires_atuntil when the latest link is valid; null — no link or it has expired

A key with the ssl.read, ssl.order or billing.read right can read orders.

A webhook is for server integrations. If the organization has a webhook, ZRAZU sends the events order.paid, order.fulfilled and order.cancelled for orders that got a link. In the request body, the data field holds event_id and the order in the same shape as GET /v1/orders/{id}. The request is signed like other webhooks (X-Zrazu-Signature). If your server did not answer, ZRAZU repeats the event with a pause that grows up to an hour. So an event may arrive twice: tell repeats apart by event_id. Events are not sent if the webhook has the «Money» category turned off.

Domain renewal

The key needs the domain.renew right. A renewal is a separate order and does not go into the cabinet cart.

curl -X POST https://api.zrazu.com/v1/domains/example.com.ua/renew \
  -H "Authorization: Bearer zr_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"years": 1, "expires_at": "2027-03-14", "if_short": "pay_link"}'
  • years — from 1 to 10. After renewal the domain term cannot reach further than 9 years from today.
  • expires_at — required: the current expiry date from GET /v1/domains. If ours already differs, the domain was renewed earlier and the API answers 409 expiry_mismatch: you will not pay twice. The field is optional, but we recommend sending it.
  • mode — vat or no_vat. Needed only if the domain has not been paid with us yet (for example, it was just transferred in). Otherwise the same seller as last time renews it.
  • If there is enough money, the answer is 201 with the order in paid status. The new term shows in GET /v1/domains as soon as the registry confirms the renewal; fulfilled in the order becomes true.
  • If money is short, it works as for SSL: without if_short — 402 balance_short and no order; with "if_short": "pay_link" — 402 with a link.

Domain registration

The key needs the domain.register right. A registration is a separate order and does not go into the cabinet cart.

  1. Take the domain owner from GET /v1/contacts/owners: id, kind (person or org), name, complete (whether a full postal address is there) and authority (whether authority for this person is confirmed in the cabinet). There is no email or phone. The key does not create owners: a person adds a record in the cabinet, under "Contacts". The key cannot change the owner later, so choose carefully.
  2. Send the request without documents_hash. If the registry requires consent to its documents, the API answers 409 documents_required with documents (title, edition, link) and documents_hash.
  3. Read the documents and repeat the request with that documents_hash. The repeat with the hash is your organization's consent to the registry documents in the editions shown. If the domain owner is another person, you consent on their behalf and warrant that you have the authority to do so and that they have read these documents. If the documents changed, you get documents_required again with a new hash.
curl -X POST https://api.zrazu.com/v1/domains/example.com.ua/register \
  -H "Authorization: Bearer zr_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"years": 1, "owner": "RECORD_ID", "mode": "no_vat", "documents_hash": "HASH_FROM_THE_ANSWER", "if_short": "pay_link"}'
  • years — from 1 to 10.
  • owner — required: a record id from GET /v1/contacts/owners.
  • mode — required: vat or no_vat.
  • Until the right documents_hash is sent, no order is created and no money is charged.
  • If there is enough money, the answer is 201 with the order in paid status. The domain shows in GET /v1/domains as soon as the registry registers it; fulfilled in the order becomes true.
  • If money is short, it works as for SSL. The person on the top-up page sees the same documents and agrees to them personally.

Limits

  • The key's daily spending limit also counts orders waiting for payment.
  • Before payment the order passes the same security check as a payment in the cabinet. If it needs a manual check, the status is review.
  • Paying a registration order with POST /v1/orders/{id}/pay checks the owner and the documents again. If the owner record was removed from the directory or the registry documents changed, payment is refused: place the order again.
  • An unpaid order stays in the cabinet under «Orders». It can be paid or cancelled there.

If it did not work

  • `409 documents_required`. Read documents or show them to a person and repeat the request with documents_hash from this answer: the repeat means consent.
  • `422 owner_required`, `owner_not_found`. Pass owner — an id from GET /v1/contacts/owners.
  • `422 owner_unconfirmed`. Authority is not confirmed for the record: open it under "Contacts", tick the box and save.
  • `422 owner_incomplete`. The owner has no full postal address: complete it in the cabinet or pick another record.
  • `409 name_taken`. The name is already registered.
  • `409 register_unavailable`. The registry does not answer or registration in the zone is paused. Try later.
  • `429 rate_limited`. The key, organization or address used up its share of name checks at the registry. Retry in a minute.
  • `409 expiry_mismatch`. The domain was renewed already: check the term in GET /v1/domains.
  • `422 mode_required`. The domain has not been paid with us yet: pass "mode": "vat" or "no_vat".
  • `409 order_refused`. The renewal was not accepted: the domain is already being renewed, renewal is stopped, or the term would exceed 9 years. The reason is in message.uk.
  • `409 renew_unavailable`. The domain cannot be renewed right now: it is locked, a registry procedure is still running, or the registry does not answer. Try later.
  • `402 balance_short`. The request has no "if_short": "pay_link": add the field to get a link.
  • `409 not_awaiting_payment`. The order is already paid or cancelled. Check GET /v1/orders/{id}.
  • `409 balance_enough`. The balance already covers it. Pay the order: POST /v1/orders/{id}/pay.
  • `409 payment_in_progress`. A person is paying by card right now. Wait for the result with GET /v1/orders/{id}?wait=60.
  • The page says the link is not valid. The address was copied incompletely. Ask the agent to send the link again.
  • The page says the link has expired. It ran out of time or a newer link was issued for the order. Ask the agent to send the latest one or issue a new one: POST /v1/orders/{id}/payment-link.

This page in Markdown