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
- The agent orders a certificate:
POST /v1/ssl/certificateswith"if_short": "pay_link". - If the balance covers it, the amount is charged at once. The answer is
201, the certificate goes to domain validation. - If money is short, nothing is charged or reserved. The answer is
402with the codepayment_requiredand apayment_urllink. - The agent passes the link to a person: in a chat, by email, in a message.
- The person opens the link without signing in, sees the order and the amount due, and pays by card or by an IBAN invoice.
- The money lands on the balance, and the order is paid from the balance right away.
- 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-linkissues 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 is409 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 is402. While the previous link is valid there is no new one (payment_urlisnull): 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.
| Field | Meaning |
|---|---|
status | awaiting_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_due | how 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 |
fulfilled | true — everything is done: the certificate is issued or finally refused |
items[].certificate_id | the certificate id: the agent continues with /v1/ssl/certificates/{id} |
payment_link_expires_at | until 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 fromGET /v1/domains. If ours already differs, the domain was renewed earlier and the API answers409 expiry_mismatch: you will not pay twice. The field is optional, but we recommend sending it.mode—vatorno_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
201with the order inpaidstatus. The new term shows inGET /v1/domainsas soon as the registry confirms the renewal;fulfilledin the order becomestrue. - If money is short, it works as for SSL: without
if_short—402 balance_shortand no order; with"if_short": "pay_link"—402with 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.
- Take the domain owner from
GET /v1/contacts/owners:id, kind (personororg), name,complete(whether a full postal address is there) andauthority(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. - Send the request without
documents_hash. If the registry requires consent to its documents, the API answers409 documents_requiredwithdocuments(title, edition, link) anddocuments_hash. - 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 getdocuments_requiredagain 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 recordidfromGET /v1/contacts/owners.mode— required:vatorno_vat.- Until the right
documents_hashis sent, no order is created and no money is charged. - If there is enough money, the answer is
201with the order inpaidstatus. The domain shows inGET /v1/domainsas soon as the registry registers it;fulfilledin the order becomestrue. - 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}/paychecks 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
documentsor show them to a person and repeat the request withdocuments_hashfrom this answer: the repeat means consent. - `422 owner_required`, `owner_not_found`. Pass
owner— anidfromGET /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.