The account page at /account (on the Zappocity host, such as zappo.city) calls these. A Zappocity account is one sign-in for every Zappocity product.
Sessions. POST /api/account/session (or a verification link) sets an HttpOnly, Secure, SameSite=Lax cookie (__Host-zappocity_account) and returns a CSRF token, which every request other than GET must send in X-CSRF-Token. Sessions last 30 days and end after 7 days unused. Bodies must be application/json.
Protection. Ten wrong passwords in a row lock an account for 15 minutes, and sign-in, sign-up and reset requests are throttled by address. The captcha (when the operator turns it on) is answered in captcha. Answers never say whether an email has an account: sign-up and reset answer the same either way, and a locked account answers like a throttled one.
See docs/kb/admin/zappocity-accounts.md.
Account
/api/account/delete
Whether the account is to be deleted
confirmWith is password, or email for an account without one (passkeys only).
200The deletion state.{ "confirmWith": "password", "deleteAfter": null }401Not signed in, or the CSRF token is missing or wrong.
/api/account/delete
Delete my Zappocity account
After the grace period the account is erased: email, name, password, profile, two-step sign-in, security keys, sessions, sign-in history, memberships, badges and invitations go; billing, points and audit records keep a "deleted" placeholder. Until then it works as before and DELETE cancels. mailboxes true also deletes the Mailocity mailboxes signed in to with this account, on the same day; otherwise they stay and are signed in to with their own passwords. Refused while the person is the only owner of an organization that has other members or products.
202Scheduled.{ "deleteAfter": "2026-10-23T12:00:00Z", "status": "active" }401Not signed in, or the CSRF token is missing or wrong.403Wrong password or email409Refused, with the reason in detail: the only owner of an organization, or a mailbox under a legal hold or its team's last owner.
/api/account/delete
Keep my Zappocity account (and the mailboxes scheduled with it)
204No longer to be deleted.401Not signed in, or the CSRF token is missing or wrong.
/api/account/desks
Make a supporto.city help desk
Makes an organization the signed-in person owns, named name, subscribed to supporto.city's Free plan: its desk is ready at once with the person as its administrator, at url (its customer page; the agent app is /desk/{slug} on the same host). Needs a confirmed email. A person can make up to 3 (409 after).
{
"name": "Acme Support"
}
201The desk.{ "name": "Acme Support", "slug": "acme-support", "url": "https://acme-support.supporto.city/support" }400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.403The email is not confirmed.409Too many desks.422A value is not acceptable, such as a password under 12 characters.
/api/account/invites
The person's invitation link and the invitations they sent
link is the person's own sign-up link (#signup&invite=CODE); people who sign up with it are recorded as invited by them (for referral rewards). joined counts them.
200The link and invitations.{ "items": [ { "email": "bob@example.com", "joined": true, "sentAt": "2026-10-08T12:00:00Z" } ], "joined": 1, "link": "https://zappo.city/account#signup\u0026invite=k3j9m2xp4q" }401Not signed in, or the CSRF token is missing or wrong.
/api/account/invites
Email an invitation
Needs a confirmed address; at most 20 a day; not to someone who already has an account.
{
"email": "bob@example.com"
}
204Sent.401Not signed in, or the CSRF token is missing or wrong.403The person's own address is not confirmed.409That address already has an account.422Not an email address429Enough invitations for today.
/api/account/mail
Your mailboxes and where Mailocity is
The Mailocity mailboxes linked to the signed-in person's account (a mailbox is linked when they sign in to webmail with it), never anyone else's, with the team each is in, and the addresses of webmail, team management and Mailocity sign-up. offered is false (and the rest empty) where Mailocity is not served with this account page.
200The mailboxes.{ "links": { "manage": "https://manage.example.com/", "signup": "https://join.example.com/", "webmail": "https://mail.example.com/" }, "mailboxes": [ { "address": "amy@acme.example", "orgId": "2f1c7d2e-58a4-4f0e-9d3c-2f1f8f3f0a11", "team": "Acme Inc" } ], "offered": true }401Not signed in, or the CSRF token is missing or wrong.
/api/account/me
The account and its organizations
Owners and billing members also see each organization's subscriptions. person.number is the customer number (shown as C-17), to give when contacting support. Each organization's orgKind (since 1.27.0) is personal (one person's, made for a free or solo mailbox, with no members to invite) or company.
200The account.{ "orgs": [ { "orgId": "0f9e8d7c-6b5a-4c3d-2e1f-0a9b8c7d6e5f", "orgKind": "company", "orgName": "Acme", "role": "owner", "subscriptions": [ { "period": "year", "plan": "team", "product": "mailocity", "status": "active" } ] } ], "person": { "email": "amy@example.com", "emailVerifiedAt": "2026-10-08T12:00:00Z", "id": "6c1f0b7e-2d4a-4b8e-9f3c-1a2b3c4d5e6f", "name": "Amy", "number": 17, "timeZone": "Europe/London" } }401Not signed in, or the CSRF token is missing or wrong.
/api/account/me
Change your name or time zone
timeZone (since 1.28.0) is an IANA zone such as Europe/London: the account page shows times in it, and Zappocity's messages to you (sign-in alerts, invitations) use it. Empty uses your device's on screen and the platform's in messages. Mailboxes have their own, set in webmail. 422 for a zone the server does not know.
{
"timeZone": "America/Toronto"
}
200The account.401Not signed in, or the CSRF token is missing or wrong.422A value is not acceptable, such as a password under 12 characters.
/api/account/orgs/{orgId}/apply-credit
Pay open invoices from credit
For owners and billing members: pays the open invoices listed (or all), oldest first, from the organization's credit, as far as it goes. The balance already counts open invoices as owed, so it does not change. Returns the invoices paid.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
200The invoices paid.{ "items": [ { "number": 7, "paymentMethod": "credit", "status": "paid" } ] }401Not signed in, or the CSRF token is missing or wrong.404Not an organization whose billing you handle
/api/account/orgs/{orgId}/billing
An organization's balance, history and invoices
For its owners and billing members. creditNotes are the credit notes that took back all or part of its invoices' charges (kind void, refund or credit), newest first. paymentsTest is true while payments go through Stripe's test mode (test cards, no real money).
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
200The billing.{ "billing": { "address": { "city": "Berlin", "country": "DE", "line1": "Hauptstr. 1", "postalCode": "10115" }, "autoPay": true, "balanceCents": 2500, "free": false, "taxId": { "type": "eu_vat", "value": "DE123456789" } }, "creditNotes": [ { "amountCents": 2500, "createdAt": "2026-11-05T13:20:49Z", "invoiceNumber": 7, "kind": "credit", "number": 12, "reason": "Outage on 3 November", "tag": "CN-12" } ], "disputes": [], "invoices": [], "ledger": [], "org": { "name": "Acme" }, "payments": true, "paymentsTest": false, "taxEnabled": true }401Not signed in, or the CSRF token is missing or wrong.404Not an organization whose billing you handle.
/api/account/orgs/{orgId}/billing
Pay invoices automatically or not; the billing address and tax number
When tax is on (taxEnabled), invoices are taxed from the billing address (country required) and tax number. A tax number needs a country; Stripe's types such as eu_vat, gb_vat, au_abn.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
{
"address": {
"city": "Berlin",
"country": "DE",
"line1": "Hauptstr. 1",
"postalCode": "10115"
},
"taxId": {
"type": "eu_vat",
"value": "DE123456789"
}
}
200The billing profile.401Not signed in, or the CSRF token is missing or wrong.404Not an organization whose billing you handle.422The address or tax number is not valid.
/api/account/orgs/{orgId}/coupons
The organization's coupons
For its owners and billing members. Every coupon it redeemed, newest first; active says it is still in force, and invoices and discountCents are what it has taken off so far.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
200The redemptions.{ "items": [ { "active": true, "by": "pat@acme.test", "coupon": { "code": "WELCOME20", "description": "20% off your first three months", "kind": "percent", "percentOff": 20 }, "discountCents": 200, "endReason": null, "endedAt": null, "endsAt": "2027-01-09T12:00:00Z", "id": "0b8e5f43-2d6c-4c55-8f1e-7c9a3e2d1b00", "invoices": 1, "invoicesLeft": null, "redeemedAt": "2026-10-09T12:00:00Z" } ] }401Not signed in, or the CSRF token is missing or wrong.404No such organization
/api/account/orgs/{orgId}/coupons
Redeem a code
Discounts come off the next invoices it covers; a boost raises the product's limits at once. Wrong codes are limited to 10 an hour.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
{
"code": "WELCOME20"
}
201The redemption.401Not signed in, or the CSRF token is missing or wrong.403Missing or wrong X-CSRF-Token header.404No such code (or organization).409Refused, with the reason in detail (expired, used up, first order only...).429Too many wrong codes.
/api/account/orgs/{orgId}/credit-notes/{creditNoteId}/pdf
A credit note as a PDF file
The amount taken back from one of the organization's invoices, and why.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
creditNoteId * | path | string |
200The PDF file, shown in the browser (Content-Disposition inline, named after its number, such as ZI-12.pdf).401Not signed in, or the CSRF token is missing or wrong.404No such credit note of this organization
/api/account/orgs/{orgId}/funds
Add funds (buy credit ahead)
For the organization's owners and billing members, where staff offer it (the billing answer has funds with minCents, maxCents and maxBalanceCents, or null). Answers the payment provider's page to pay the amount; once it is paid the balance is credited, and it pays the next invoices.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
{
"amountCents": 5000
}
200Where to go to pay.{ "url": "https://checkout.stripe.com/c/pay/cs_live_xxx" }401Not signed in, or the CSRF token is missing or wrong.403Adding funds is not offered.404No such organization422The amount is outside the limits503Card payments are not set up.
/api/account/orgs/{orgId}/invoices/{invoiceId}
One invoice with its lines
With its credit notes (creditNotes) and how much they took back (creditedCents).
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
invoiceId * | path | string |
200The invoice.401Not signed in, or the CSRF token is missing or wrong.404No such invoice of this organization.
/api/account/orgs/{orgId}/invoices/{invoiceId}/pay
Pay an open invoice now
Answers the provider's page to pay it (any enabled method); the method used is saved for next time.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
invoiceId * | path | string |
200Where to go.401Not signed in, or the CSRF token is missing or wrong.404No such invoice of this organization.409The invoice has nothing to pay.503Card payments are not set up.
/api/account/orgs/{orgId}/invoices/{invoiceId}/pdf
An invoice as a PDF file
For the organization's owners and billing members. Laid out the way Zappocity's staff set (sender, logo, colour, paper); stamped PAID, VOID or OVERDUE.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
invoiceId * | path | string |
200The PDF file, shown in the browser (Content-Disposition inline, named after its number, such as ZI-12.pdf).401Not signed in, or the CSRF token is missing or wrong.404No such invoice of this organization
/api/account/orgs/{orgId}/pay
Pay several invoices at once
For owners and billing members: opens the payment provider's page (url) for the open invoices listed, a line each; the browser comes back to /account#billing. They are paid when the provider confirms the total was paid; one paid meanwhile (from credit, say) leaves what was paid for it on the balance as credit.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
{
"invoices": [
"0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
]
}
200The payment page.{ "totalCents": 14500, "url": "https://checkout.stripe.com/c/pay/cs_test_123" }401Not signed in, or the CSRF token is missing or wrong.404Not an organization whose billing you handle422No invoices503Payments are not set up.
/api/account/orgs/{orgId}/payment-method
Save a payment method
Answers the payment provider's page (url). Finished, the browser comes back to /account?org={orgId}&setup={sessionId}#billing, and the page confirms the session (payment-method/confirm); cancelled, to /account#billing. The method then pays renewals automatically.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
200Where to go.{ "url": "https://checkout.stripe.com/c/pay/cs_..." }401Not signed in, or the CSRF token is missing or wrong.404Not an organization whose billing you handle.503Card payments are not set up.
/api/account/orgs/{orgId}/payment-method
Remove the saved payment method
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
204Removed.401Not signed in, or the CSRF token is missing or wrong.404Not an organization whose billing you handle.503Card payments are not set up.
/api/account/orgs/{orgId}/payment-method/confirm
Confirm a payment method saved on the provider's page
Saves the method of the setup session the browser came back with, so it shows at once even when the provider's webhook is late or never arrives. Stripe is asked about the session with the key in use. It is accepted only as a completed setup whose metadata names this organization, for the organization's customer in the mode in use, with a succeeded setup intent. After the webhook it changes nothing.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
{
"sessionId": "cs_test_a1B2c3D4"
}
200Saved.{ "paymentMethod": "Visa ending 4242 (test)" }401Not signed in, or the CSRF token is missing or wrong.404Not an organization whose billing you handle.422Not a finished setup of this organization (another organization's or customer's503Card payments are not set up.
/api/account/orgs/{orgId}/quotes/{quoteId}/pdf
A quote sent to the organization as a PDF file (an estimate)
Only quotes sent to the organization; drafts are not found.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
quoteId * | path | string |
200The PDF file, shown in the browser (Content-Disposition inline, named after its number, such as ZI-12.pdf).401Not signed in, or the CSRF token is missing or wrong.404No such quote sent to this organization
/api/account/orgs/{orgId}/subscriptions
Subscribe an organization to a product's plan
For the organization's owners and billing members (403 for others; 404 for an organization the person is not in), for products bought here (Tether; 404 for others), while the product is available. One subscription per organization: a second is refused with 409 (change its plan with PATCH instead). A paid plan needs a payment method saved for the organization (or billing waived by staff), else 409 with the reason. The subscription starts now and is invoiced on the next billing run (within the hour); the product is told at once. Products send people here with /account#buy=tether&plan=tether_pro&period=year&org={orgId} (org and period optional), which opens this as a form. Since 1.26.0.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
{
"period": "year",
"plan": "tether_pro",
"product": "tether"
}
201The subscription.{ "canceledAt": null, "createdAt": "2026-10-26T12:00:00Z", "id": "6f1c0d2e-5b7a-4c1e-9a3d-2b8e4f6a1c90", "orgId": "0b9a7c4e-2d1f-4e8a-b6c3-5a7d9e1f2b40", "orgName": "Acme", "period": "year", "plan": "tether_pro", "product": "tether", "renewsAt": "2026-10-26T12:00:00Z", "resourceRef": "org-0b9a7c4e", "seats": 1, "status": "active", "unpaidSuspendedAt": null, "updatedAt": "2026-10-26T12:00:00Z" }400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.403Not an owner or billing member404No such organization409Refused
/api/account/orgs/{orgId}/subscriptions/{subscriptionId}
Change a self-serve subscription's plan, seats or billing period
For the organization's owners and billing members (403 for others), and only for products customers change themselves (supporto.city, dnso.city, Tether; 404 for others). A dearer plan and more seats are invoiced at once for the rest of the period already paid (the difference in price, in proportion to the time left); a cheaper plan or fewer seats take effect without a refund. period (month or year) applies from the next renewal: yearly is cheaper (dnso.city's monthly prices are 20% more). A plan, seats or period the resource does not fit (more agents, queues, zones or records than allowed) or that is not offered is refused with 409 and the reason.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
subscriptionId * | path | string |
200The subscription.400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.403Not an owner or billing member.404No such subscription here.409Refused
/api/account/password
Change your password
Needs the current one. Every other session ends, and the address is told by email.
204Changed.401Not signed in, or the CSRF token is missing or wrong.403The current password is not right (it counts as a failed sign-in).422A value is not acceptable, such as a password under 12 characters.
/api/account/plans/{product}
A product's plans and prices
For products customers change themselves (supporto.city, dnso.city, Tether); 404 for others.
| Parameter | In | Type | |
|---|---|---|---|
product * | path | string |
200Its active plans.{ "currency": "USD", "plans": [ { "active": true, "code": "team", "features": [ "help_center", "sla" ], "limits": { "agents": -1, "agentsBySeats": 1, "queues": -1 }, "name": "Team", "perSeat": true, "prices": { "month": 2280, "year": 22800 } } ] }404Not a self-serve product.
/api/account/points
The person's points, their history and what the program gives
program says what earns points (per dollar paid, per referral that becomes a customer, and so on) and what they buy: credit at pointValue hundredths of a cent each (at least minRedeemPoints at a time), and Mailocity invitations at inviteCostPoints each when invitations is true.
200The points.{ "items": [ { "at": "2026-10-09T12:00:00Z", "balance": 520, "by": "Zappocity loyalty", "description": "Referral: bob@example.com became a customer", "id": 7, "kind": "referral", "points": 500, "reference": "" } ], "points": 520, "program": { "enabled": true, "invitations": true, "inviteCostPoints": 100, "inviteUsedPoints": 20, "minRedeemPoints": 500, "pointValue": 100, "pointsPerDollar": 1, "referralPoints": 500, "welcomePoints": 200 } }401Not signed in, or the CSRF token is missing or wrong.
/api/account/points/credit
Turn points into credit for an organization
For an organization the person is an owner or billing member of. The credit is on its balance and pays its next invoices.
{
"orgId": "3c1d9a7e-55b2-4b8e-9e1f-0a2b3c4d5e6f",
"points": 500
}
201The points entry and the credit entry.401Not signed in, or the CSRF token is missing or wrong.403Missing or wrong X-CSRF-Token header.404No such organization409Refused, with the reason in detail: the program is off, fewer than minRedeemPoints, or not enough points.
/api/account/points/invitations
Get more Mailocity invitations for points
Adds them to the person's Mailocity mailbox (the one they sign in to with this account); 1 to 50 at a time.
{
"count": 2
}
201The points entry and how many invitations the mailbox now has.{ "entry": { "balance": 320, "kind": "invitations", "points": -200 }, "invitationsLeft": 3 }401Not signed in, or the CSRF token is missing or wrong.403Missing or wrong X-CSRF-Token header.409Refused, with the reason in detail: not offered, not enough points, or no Mailocity mailbox (the points are given back).
/api/account/profile
The person's profile and badges
200The profile and badges.{ "badges": [ { "color": "success", "icon": "star", "key": "early-adopter", "name": "Early adopter", "rule": "early_adopter" } ], "profile": { "bio": "Support lead", "createdAt": "2026-10-09T12:00:00Z", "handle": "pat", "name": "Pat Lee", "personId": "19adb6dd-1eac-4d74-8134-2668d30e9561", "pronouns": "they/them", "visible": true, "website": "https://pat.example" } }401Not signed in, or the CSRF token is missing or wrong.
/api/account/profile
Change the profile
handle is 3 to 30 lower-case letters, digits, '.', '_' and '-' (null removes it; some names are kept for the platform); website is an https:// address. visible lets others see it: in webmail next to mail from you, and by handle. Badges are given by staff or their rules.
{
"bio": "Support lead",
"handle": "pat",
"name": "Pat Lee",
"pronouns": "they/them",
"visible": true,
"website": "https://pat.example"
}
200The profile and badges.401Not signed in, or the CSRF token is missing or wrong.403Missing or wrong X-CSRF-Token header.409That handle is taken.422A value is not valid.
/api/account/profiles/{handle}
Someone's visible profile
For people signed in; only profiles made visible (and the person's own).
| Parameter | In | Type | |
|---|---|---|---|
handle * | path | string |
200The profile and the badges that show.{ "badges": [ { "color": "primary", "icon": "user", "key": "staff", "name": "Staff", "rule": "staff" } ], "bio": "Support lead", "handle": "pat", "name": "Pat Lee", "pronouns": "they/them", "visible": true }401Not signed in, or the CSRF token is missing or wrong.404No such visible profile.
/api/account/security-alerts
Whether sign-ins from new devices are emailed
A sign-in from an address and browser not seen in the last 90 days is emailed when newSignIn is on (the default). Changes to the password, two-step sign-in and security keys are always emailed.
200The setting.{ "newSignIn": true }401Not signed in, or the CSRF token is missing or wrong.
/api/account/security-alerts
Turn sign-in alerts on or off
{
"newSignIn": false
}
200The setting.401Not signed in, or the CSRF token is missing or wrong.
/api/account/security-keys
The account's security keys and passkeys
Hardware keys (YubiKey and others) and passkeys (1Password, iCloud Keychain, Google Password Manager, Windows Hello) registered with WebAuthn. Any of them makes signing in take a second step; a passkey (passkey: true) can also sign in on its own.
200The keys.{ "items": [ { "createdAt": "2026-10-08T12:00:00Z", "id": "q8V2...", "lastUsed": "2026-10-09T08:30:00Z", "name": "YubiKey 5C", "passkey": false } ] }401Not signed in, or the CSRF token is missing or wrong.
/api/account/security-keys
Finish adding a key or passkey
Send the challengeId from POST /api/account/security-keys/register and the browser's navigator.credentials.create() result (as JSON). The first key turns two-step sign-in on: every other device is signed out and recovery codes are returned once. The address is told by email.
{
"challengeId": "5b0b4c1e-7d1a-4f5e-9a3b-1c2d3e4f5a6b",
"credential": {
"id": "q8V2...",
"rawId": "q8V2...",
"response": {
"attestationObject": "o2Nm...",
"clientDataJSON": "eyJ0..."
},
"type": "public-key"
},
"name": "YubiKey 5C"
}
201Added.{ "id": "q8V2...", "name": "YubiKey 5C", "passkey": false, "recoveryCodes": [ "k3j9-2mxp", "..." ] }400The challenge expired or was used.401Not signed in, or the CSRF token is missing or wrong.409The key is already registered422The key's answer did not check out.
/api/account/security-keys/register
Start adding a key or passkey
Needs the current password. Returns options for navigator.credentials.create() (publicKey) and a challengeId, good for ten minutes and once. With passkey: true the authenticator must keep a discoverable credential and verify the user (PIN or biometrics), so it can sign in without a password.
{
"currentPassword": "correct horse battery",
"passkey": true
}
200The options.{ "challengeId": "5b0b4c1e-7d1a-4f5e-9a3b-1c2d3e4f5a6b", "options": { "publicKey": { "challenge": "Wm9...", "pubKeyCredParams": [ { "alg": -7, "type": "public-key" } ], "rp": { "id": "zappo.city", "name": "Zappocity" } } } }401Not signed in, or the CSRF token is missing or wrong.403Wrong password.
/api/account/security-keys/{keyId}
Rename a key
| Parameter | In | Type | |
|---|---|---|---|
keyId * | path | string | The key's id (base64url). |
{
"name": "Backup YubiKey"
}
204Renamed.401Not signed in, or the CSRF token is missing or wrong.404No such key.
/api/account/security-keys/{keyId}
Remove a key or passkey
Needs the current password. With no key and no authenticator app left, two-step sign-in is off and the recovery codes go. The address is told by email.
| Parameter | In | Type | |
|---|---|---|---|
keyId * | path | string | The key's id (base64url). |
{
"currentPassword": "correct horse battery"
}
204Removed.401Not signed in, or the CSRF token is missing or wrong.403Wrong password.404No such key.
/api/account/sessions
Where you are signed in
200Most recently used first.{ "items": [ { "createdAt": "2026-10-08T12:00:00Z", "current": true, "expiresAt": "2026-11-07T12:00:00Z", "id": 4, "ip": "203.0.113.9", "lastSeen": "2026-10-08T12:30:00Z", "userAgent": "Firefox" } ] }401Not signed in, or the CSRF token is missing or wrong.
/api/account/sessions
Sign out everywhere else
204Done.401Not signed in, or the CSRF token is missing or wrong.
/api/account/signins
Your recent sign-in attempts
200Newest first, at most 100.{ "items": [ { "at": "2026-10-08T12:00:00Z", "email": "amy@example.com", "ip": "203.0.113.9", "reason": "password", "success": false, "userAgent": "Firefox" } ] }401Not signed in, or the CSRF token is missing or wrong.
/api/account/ssh-keys
Your SSH keys, and how to reach Hatch
The public keys that sign you in to Hatch, the emergency shell for the servers you manage, and (when it is on) where to connect: ssh -p PORT USER@HOST, with the host key's fingerprint to check the first time.
200The keys.{ "hatch": { "enabled": true, "fingerprint": "SHA256:k3yF1ngerprintExampleExampleExampleExample0", "host": "hatch.example.com", "port": 2222, "user": "amy@example.com" }, "items": [ { "createdAt": "2026-11-08T12:00:00Z", "fingerprint": "SHA256:Hq3Y0exampleexampleexampleexampleexample0", "id": "4d5e6f70-8192-4a3b-9c4d-5e6f708192a3", "lastUsedAt": null, "name": "amy@laptop", "publicKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyOnly" } ] }401Not signed in, or the CSRF token is missing or wrong.
/api/account/ssh-keys
Add an SSH key
One public key (the .pub file's line): Ed25519, ECDSA, a security key, or RSA of 2048 bits or more. It takes your current password, since a key reaches every server you manage, and you are emailed. A key can be on one account only, since the key says who signs in to Hatch (the user name is ignored). The name defaults to the key's comment. At most 20 keys.
{
"currentPassword": "your current password",
"publicKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyOnly amy@laptop"
}
201Added.{ "createdAt": "2026-11-08T12:00:00Z", "fingerprint": "SHA256:Hq3Y0exampleexampleexampleexampleexample0", "id": "4d5e6f70-8192-4a3b-9c4d-5e6f708192a3", "lastUsedAt": null, "name": "amy@laptop", "publicKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyOnly" }401Not signed in, or the CSRF token is missing or wrong.403Your current password is not right.409That key is on an account already (this one or another; whose is not said).422Not a public key Hatch takes, no password on the account, or 20 keys already.
/api/account/ssh-keys/{keyId}
Remove an SSH key
It no longer signs in to Hatch; sessions it opened end within a minute.
| Parameter | In | Type | |
|---|---|---|---|
keyId * | path | string |
204Removed.401Not signed in, or the CSRF token is missing or wrong.404No such key on your account.
/api/account/storage
Your organizations' object storage
The storage accounts of your organizations' object storage subscriptions, with what each keeps and may keep, whether you manage it (owners do; admins too on plans with team access), and where S3 clients connect (path-style; off until staff give the service its host).
200The accounts and the endpoint.{ "endpoint": { "enabled": true, "host": "s3.example.com", "pathStyle": true, "region": "us-east-1", "url": "https://s3.example.com" }, "items": [ { "buckets": 2, "bytesUsed": 1048576, "canManage": true, "id": "6f1d2c3b-4a5e-4f60-8172-839a4b5c6d7e", "limits": { "buckets": 10, "storageBytes": 250000000000 }, "name": "Acme Files", "orgId": "0a1b2c3d-4e5f-4061-8273-94a5b6c7d8e9", "orgName": "Acme Files", "plan": "starter", "status": "active" } ] }401Not signed in, or the CSRF token is missing or wrong.
/api/account/storage/{accountId}/buckets
A storage account's buckets
For the account's managers; anyone else is told it isn't there (or, for its organization's other members, 403).
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string |
200The buckets, by name.401Not signed in, or the CSRF token is missing or wrong.403You are in the organization but don't manage its storage.404No such storage account of yours.
/api/account/storage/{accountId}/buckets
Make a bucket
Bucket names are one namespace for everyone, as on S3: 3 to 63 lower-case letters, digits, dots and hyphens, starting and ending with a letter or digit, not shaped like an IP address. Up to the plan's number of buckets.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string |
{
"name": "acme-backups"
}
201Made.{ "accountId": "6f1d2c3b-4a5e-4f60-8172-839a4b5c6d7e", "bytes": 0, "createdAt": "2026-11-08T12:00:00Z", "id": "1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9", "name": "acme-backups", "objects": 0 }401Not signed in, or the CSRF token is missing or wrong.403You don't manage this account's storage.404No such storage account of yours.409The name is taken (by you or anyone), or the account is suspended.422Not a bucket name we take, or the plan's buckets are used.
/api/account/storage/{accountId}/buckets/{bucketId}
Remove an empty bucket
Access keys held to the bucket are removed with it.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
bucketId * | path | string |
204Removed.401Not signed in, or the CSRF token is missing or wrong.403You don't manage this account's storage.404No such bucket in this account.409The bucket holds objects or unfinished uploads, or the account is suspended.
/api/account/storage/{accountId}/keys
A storage account's access keys
Never their secrets, which are shown once, when made.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string |
200The keys, oldest first.401Not signed in, or the CSRF token is missing or wrong.403You don't manage this account's storage.404No such storage account of yours.
/api/account/storage/{accountId}/keys
Make an access key
An access key id and secret for S3 clients. The secret is in this answer only: we keep it sealed and never show it again. A key can be held to one bucket of the account, or to reading. At most 50.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string |
{
"bucketId": "1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9",
"name": "backup server",
"readOnly": false
}
201Made; keep the secret now.{ "key": { "accessKeyId": "ZKEXAMPLEEXAMPLEEXA", "accountId": "6f1d2c3b-4a5e-4f60-8172-839a4b5c6d7e", "bucketId": "1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9", "createdAt": "2026-11-08T12:00:00Z", "id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f9a0", "lastUsedAt": null, "name": "backup server", "readOnly": false }, "secretAccessKey": "your-secret-is-shown-here-once" }401Not signed in, or the CSRF token is missing or wrong.403You don't manage this account's storage.404No such storage account of yours.409The account is suspended.422No name, a bucket that isn't this account's, or 50 keys already.
/api/account/storage/{accountId}/keys/{keyId}
Remove an access key
Requests signed with it are refused at once.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
keyId * | path | string |
204Removed.401Not signed in, or the CSRF token is missing or wrong.403You don't manage this account's storage.404No such key in this account.409The account is suspended.
/api/account/two-factor
Whether two-step sign-in is on
200The state.{ "enabled": true, "recoveryCodesLeft": 9 }401Not signed in, or the CSRF token is missing or wrong.
/api/account/two-factor
Turn two-step sign-in off
Needs the password and a current code (or a recovery code). The address is told by email.
204Off.401Not signed in, or the CSRF token is missing or wrong.403The password or code is not right (it counts as a failed sign-in).
/api/account/two-factor/enable
Turn it on with a first code
Returns ten recovery codes, shown once. Every other session ends and the address is told by email.
200The recovery codes.{ "recoveryCodes": [ "abcd-efgh-jkmn" ] }401Not signed in, or the CSRF token is missing or wrong.409Setup was not started.422The code is not right.
/api/account/two-factor/recovery-codes
Replace the recovery codes
200The new codes401Not signed in, or the CSRF token is missing or wrong.403The password is not right.409Two-step sign-in is off.
/api/account/two-factor/setup
Start setting up an authenticator app
Returns a new secret as text, an otpauth URI and a QR code. Nothing changes until it is enabled with a code.
200The secret.{ "qrCode": "data:image/png;base64,...", "secret": "JBSWY3DPEHPK3PXP", "uri": "otpauth://totp/Zappocity:amy@example.com?secret=..." }401Not signed in, or the CSRF token is missing or wrong.403The password is not right.409It is already on.
Billing
/api/account/orgs/{orgId}/quotes
The quotes we sent an organization
For its owners and billing members: every quote sent to it (never drafts), newest first, with its lines, what the first invoice and each month or year after come to, and whether it can still be answered (status sent and expired false).
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
200The quotes.{ "items": [ { "answeredAt": null, "answeredBy": "", "declineReason": "", "expired": false, "firstCents": 27500, "id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a", "invoiceId": null, "lines": [ { "description": "Mailbox migration, hours", "period": "once", "quantity": 3, "unitCents": 7500 }, { "description": "Priority support", "period": "month", "quantity": 1, "unitCents": 5000 } ], "monthlyCents": 5000, "notes": "", "number": 1001, "orgId": "9f8e7d6c-5b4a-4321-8fed-cba987654321", "orgName": "Acme Co", "sentAt": "2026-11-05T12:00:00Z", "status": "sent", "subject": "Migration and priority support", "tag": "ZQ-1001", "validUntil": "2026-12-05T23:59:59Z", "yearlyCents": 0 } ] }401Not signed in, or the CSRF token is missing or wrong.404No such organization
/api/account/orgs/{orgId}/quotes/{quoteId}/accept
Accept a quote
An owner or billing member accepts a sent quote before it expires: its lines become charges on the organization and are invoiced at once (the answer has the invoice, to pay as any other); monthly or yearly lines are billed each period after.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
quoteId * | path | string |
200The quote, accepted, and its invoice.{ "invoice": { "number": 1043, "status": "open", "totalCents": 27500 }, "quote": { "status": "accepted", "tag": "ZQ-1001" } }401Not signed in, or the CSRF token is missing or wrong.404No such quote or organization409Already answered410Expired.
/api/account/orgs/{orgId}/quotes/{quoteId}/decline
Decline a quote
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
quoteId * | path | string |
{
"reason": "Over budget this quarter."
}
200The quote401Not signed in, or the CSRF token is missing or wrong.404No such quote or organization409Already answered410Expired.422The reason is too long.
DNS
/api/account/dns
Your DNS accounts
The dnso.city DNS accounts of the organizations you are a member of; owners change their zones, other members read them.
200The accounts.{ "items": [ { "canEdit": true, "id": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6a7b", "name": "Acme", "orgId": "0f9e8d7c-6b5a-4c3d-2e1f-0a9b8c7d6e5f", "orgName": "Acme", "plan": "free", "status": "active", "zones": 1 } ] }401Not signed in, or the CSRF token is missing or wrong.
/api/account/dns
Start a dnso.city DNS account
Makes an organization you own, named name, subscribed to dnso.city's Free plan (3 zones). Needs a confirmed email and dnso.city available; up to 3 per person (409 after).
{
"name": "Acme"
}
201The DNS account.400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.403The email is not confirmed.409Not open yet422A value is not acceptable, such as a password under 12 characters.
/api/account/dns/{accountId}/zones
A DNS account's zones
The zones dnso.city serves for the account, with their serials and record counts, and the account itself.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string |
200The zones.{ "account": { "id": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6a7b", "name": "Acme", "plan": "personal", "status": "active" }, "items": [ { "id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901", "name": "example.com", "records": 2, "serial": 2026100901, "status": "active" } ] }401Not signed in, or the CSRF token is missing or wrong.404No such DNS account
/api/account/dns/{accountId}/zones
Serve a zone
Adds a domain for dnso.city to serve, within the plan's zones. A domain is served for one account only (409 when it is already). Point the domain's name servers at dnso.city's at its registrar for the answers to be used.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string |
201The zone.400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account409The zone is served already, or the plan's limit is reached.422The record or zone is not valid
/api/account/dns/{accountId}/zones/{zoneId}
A zone and its records
With its DNSSEC: every zone is signed with a key of its own; dnssec.ds is the DS record to give the domain's registrar to turn DNSSEC on. With its secondary DNS: transfers lists the addresses it may be transferred to and its TSIG key (never the secret). A secondary zone has kind secondary, its records as last transferred (read only), and secondary: its primaries, key name, serial and the state of its checks.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
200The zone and its records.{ "dnssec": { "algorithm": 13, "ds": "24680 13 2 9A1B...", "dsRecord": "example.com.\t3600\tIN\tDS\t24680 13 2 9A1B...", "keyTag": 24680, "ready": true }, "records": [ { "data": "10 mail.example.com.", "id": "3c4d5e6f-7081-4293-a4b5-c6d7e8f90123", "name": "@", "ttl": 3600, "type": "MX" } ], "transfers": { "addresses": [], "key": null }, "zone": { "accountId": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6a7b", "id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901", "name": "example.com", "records": 2, "serial": 2026100901, "status": "active" } }401Not signed in, or the CSRF token is missing or wrong.404No such DNS account
/api/account/dns/{accountId}/zones/{zoneId}
Stop serving a zone
Its records go with it.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
204Removed.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account409The zone is served already, or the plan's limit is reached.422The record or zone is not valid
/api/account/dns/{accountId}/zones/{zoneId}/delegation
Check the zone's delegation
Asks the zone's parent (the registry, for most domains) directly, not a resolver's cache: status is delegated (dnso.city's name servers and no others), partly (some of them, or others too: usual for a secondary zone), elsewhere (none of them), unregistered (the parent has no such name) or unknown (the parent could not be asked; error says why). ds is on (the parent's DS record is the zone's: DNSSEC works), mismatch (DS records, none the zone's: resolvers that check DNSSEC fail; replace it at the registrar), off or unknown. Kept a minute. When the zone is linked to a provider, linked names it: its name servers answer for the domain with its own DNSSEC key, so elsewhere and a mismatched DS record are expected then.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
200The parent's answer.{ "delegation": { "checkedAt": "2026-10-09T12:00:00Z", "ds": "off", "missing": [ "ns2.dnso.city" ], "nameServers": [ "ns1.dnso.city", "ns1.oldhost.net" ], "parent": "com", "status": "partly" }, "expected": [ "ns1.dnso.city", "ns2.dnso.city" ], "zone": "example.com" }401Not signed in, or the CSRF token is missing or wrong.404No such DNS account or zone here.503Delegation is not checked by this server.
/api/account/dns/{accountId}/zones/{zoneId}/primaries
Set a secondary zone's primaries
The name servers a secondary zone is transferred from (IP addresses on the internet, 1 to 10, asked on port 53), and, when key is given, the TSIG key they want (null: none; left out: unchanged). The zone is checked with them within a minute. Answers the zone as GET does.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
{
"addresses": [
"192.0.2.53"
],
"key": {
"algorithm": "hmac-sha256",
"name": "transfer.example.org",
"secret": "c2VjcmV0LXNlY3JldC1zZWNyZXQtc2VjcmV0LTAxMjM="
}
}
200The zone400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account or zone here.409The zone is not a secondary.422An address or the key is not valid.
/api/account/dns/{accountId}/zones/{zoneId}/push
The provider the zone is pushed to
dnso.city keeps every zone; a zone whose name servers are another provider's (Route53, Cloudflare, DigitalOcean, Gandi, GoDaddy, Hetzner, Linode, Namecheap, OVHcloud, Porkbun, Spaceship, Vultr) can be linked to it, and dnso.city then pushes every change there through the provider's API, a moment after it is made, and again every hour. providers lists them with the fields each needs. Secrets are never shown; secretsSet names those kept. unmanaged are the provider's records dnso.city leaves alone, as last read.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
200Whether and where the zone is pushed, and how the last push went.{ "changes": 3, "error": "", "inStep": true, "linked": true, "mode": "authoritative", "offered": true, "provider": "cloudflare", "pushedAt": "2026-11-07T12:00:00Z", "pushedSerial": 12, "secretsSet": [ "apiToken" ], "settings": {}, "unmanaged": [ { "data": "1 . alpn=h2", "name": "@", "ttl": 300, "type": "HTTPS" } ], "updatedAt": "2026-11-07T11:59:00Z" }401Not signed in, or the CSRF token is missing or wrong.404No such DNS account or zone here.
/api/account/dns/{accountId}/zones/{zoneId}/push
Link the zone to the provider whose name servers answer for it
The credentials are checked by reading the zone at the provider; nothing is changed when that fails. mode authoritative (the default) makes the provider's copy exactly dnso.city's, removing and overwriting what dnso.city does not have, after first importing the provider's records dnso.city lacks (import, default true); managed touches only the record sets dnso.city has (and removes there only those it pushed before), listing the provider's others as unmanaged. Either way the provider's SOA, its name servers at the zone's top, and record types dnso.city cannot hold are left alone. Credentials not given again are kept when the provider is the same. Once linked, the zone's DNSSEC is the provider's: dnso.city's DS is no longer offered. The zone is pushed at once; a failure comes back as warning.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
{
"credentials": {
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
},
"mode": "authoritative",
"provider": "route53"
}
200The link, with `imported` (records added to dnso.city), `leftAtProvider` and any `warning`.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account or zone here409The provider's records would pass the plan's limit.422Not a provider, missing or malformed credentials, a secondary zone, or the provider could not be read with them.
/api/account/dns/{accountId}/zones/{zoneId}/push
Stop pushing the zone
The records at the provider stay as they are. dnso.city's DNSSEC is offered again.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
204Unlinked.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account or zone here
/api/account/dns/{accountId}/zones/{zoneId}/push/adopt
Bring the provider's unmanaged records into dnso.city
Reads the zone at the provider and adds its record sets dnso.city lacks (all, or those named in sets as "name TYPE"), so dnso.city manages them from then on. Types dnso.city cannot hold stay at the provider (leftAtProvider).
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
{
"sets": [
"www A",
"@ MX"
]
}
200The link, with `adopted` (records added).401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account or zone here409The records would pass the plan's limit.422The provider could not be read.
/api/account/dns/{accountId}/zones/{zoneId}/push/run
Push the zone now
Pushes it now (unless a push of it is running), putting back anything changed at the provider. A failure comes back as warning.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
200The link and how the push went.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account or zone here
/api/account/dns/{accountId}/zones/{zoneId}/records
Add a record
Checked as DNS reads it, and with the zone's other records: a name with a CNAME has nothing else, the zone itself is never a CNAME (use ALIAS), its own NS records are dnso.city's. The serial goes up. Answers the zone and its records.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
{
"data": "10 mail.example.com.",
"name": "@",
"ttl": 3600,
"type": "MX"
}
201The zone and its records.{ "dnssec": { "algorithm": 13, "ds": "24680 13 2 9A1B...", "dsRecord": "example.com.\t3600\tIN\tDS\t24680 13 2 9A1B...", "keyTag": 24680, "ready": true }, "records": [ { "data": "10 mail.example.com.", "id": "3c4d5e6f-7081-4293-a4b5-c6d7e8f90123", "name": "@", "ttl": 3600, "type": "MX" } ], "zone": { "accountId": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6a7b", "id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901", "name": "example.com", "records": 2, "serial": 2026100901, "status": "active" } }400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account409The zone is served already, or the plan's limit is reached.422The record or zone is not valid
/api/account/dns/{accountId}/zones/{zoneId}/records/{recordId}
Replace a record
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string | |
recordId * | path | string |
{
"data": "10 mail.example.com.",
"name": "@",
"ttl": 3600,
"type": "MX"
}
200The zone and its records.400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account409The zone is served already, or the plan's limit is reached.422The record or zone is not valid
/api/account/dns/{accountId}/zones/{zoneId}/records/{recordId}
Remove a record
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string | |
recordId * | path | string |
200The zone and its records.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account409The zone is served already, or the plan's limit is reached.422The record or zone is not valid
/api/account/dns/{accountId}/zones/{zoneId}/refresh
Check a secondary zone now
Asks the primaries for the zone's SOA and transfers the zone if its serial moved. What happened is in secondary (lastError empty when it went well). Otherwise this happens on the schedule the zone's SOA sets (refresh, and retry after a failure) and when a primary sends NOTIFY to dnso.city's name servers.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
200The zone, its records and its secondary state.{ "dnssec": { "ready": false, "secondary": true }, "records": [ { "data": "192.0.2.7", "name": "www", "ttl": 300, "type": "A" } ], "secondary": { "expire": 1209600, "keyName": "transfer.example.org.", "lastCheck": "2026-10-09T12:00:00Z", "lastError": "", "lastOk": "2026-10-09T12:00:00Z", "lastTransfer": "2026-10-09T12:00:00Z", "nextCheck": "2026-10-09T13:00:00Z", "primaries": [ "192.0.2.53" ], "primarySerial": 7, "records": 3, "refresh": 3600, "retry": 600 }, "transfers": { "addresses": [], "key": null }, "zone": { "id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901", "kind": "secondary", "name": "example.org", "records": 3, "serial": 7, "status": "active" } }401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones.404No such DNS account or zone here.409The zone is not a secondary.
/api/account/dns/{accountId}/zones/{zoneId}/transfers
Set the addresses the zone is transferred to
Secondary DNS, on plans that include it: the zone may be transferred (AXFR, or IXFR, answered with the whole zone) to these addresses, over TCP and only with a request signed with the zone's TSIG key (HMAC-SHA256); they are sent a NOTIFY when the zone changes. Addresses are on the internet (no private or loopback ones), at most 10; an empty list stops transfers. The first time, the zone gets its key, and the answer carries its secret: shown this once, so keep it. The zone goes out unsigned (dnso.city signs as it answers), so while the registrar has the zone's DS record, every name server for it must be dnso.city's. ALIAS records are not transferred. The serial goes up.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
{
"addresses": [
"192.0.2.53",
"2001:db8::53"
]
}
200The zone's transfers; `key.secret` only when the key was just made.{ "addresses": [ "192.0.2.53", "2001:db8::53" ], "key": { "algorithm": "hmac-sha256", "createdAt": "2026-10-09T12:00:00Z", "name": "xfr.example.com.", "secret": "q2Lw...base64...=" } }400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones, or the plan does not include secondary DNS.404No such DNS account or zone here.422An address is not valid, not on the internet, or there are more than 10.
/api/account/dns/{accountId}/zones/{zoneId}/transfers/key
Replace the zone's TSIG key
A new key for transfers; the old one stops working once the name servers next read the zone (within a minute). The new secret is in this answer only.
| Parameter | In | Type | |
|---|---|---|---|
accountId * | path | string | |
zoneId * | path | string |
200The zone's transfers with the new key's secret.{ "addresses": [ "192.0.2.53" ], "key": { "algorithm": "hmac-sha256", "createdAt": "2026-10-09T12:00:00Z", "name": "xfr.example.com.", "secret": "b7Rk...base64...=" } }401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change its zones, or the plan does not include secondary DNS.404No such DNS account or zone here.
Organizations
/api/account/invitations
Invitations to join organizations
The invitations to your email address that have not expired. None are listed until the address is confirmed (emailConfirmed).
200The invitations.{ "emailConfirmed": true, "items": [ { "createdAt": "2026-10-18T09:00:00Z", "email": "sam@example.com", "expiresAt": "2026-11-01T09:00:00Z", "id": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f", "invitedBy": "Alice", "orgId": "0f9e8d7c-6b5a-4c3d-2e1f-0a9b8c7d6e5f", "orgName": "Acme", "role": "member" } ] }401Not signed in, or the CSRF token is missing or wrong.
/api/account/invitations/{invitationId}
Decline the invitation
| Parameter | In | Type | |
|---|---|---|---|
invitationId * | path | string |
204Declined.401Not signed in, or the CSRF token is missing or wrong.403Your email address is not confirmed yet.404No such invitation to your address
/api/account/invitations/{invitationId}/accept
Join the organization
You become a member with the invitation's role (keeping your role if you are one already).
| Parameter | In | Type | |
|---|---|---|---|
invitationId * | path | string |
200Your membership.{ "createdAt": "2026-01-02T10:00:00Z", "email": "alice@example.com", "fromMailbox": false, "name": "Alice", "orgId": "0f9e8d7c-6b5a-4c3d-2e1f-0a9b8c7d6e5f", "orgKind": "company", "orgName": "Acme", "personId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "role": "owner" }401Not signed in, or the CSRF token is missing or wrong.403Your email address is not confirmed yet.404No such invitation to your address
/api/account/orgs
Start an organization
Makes a company organization you own, alongside any you already belong to. Needs a confirmed email address (403); a person owns at most 20 (409).
{
"name": "Acme"
}
201The organization.{ "id": "0f9e8d7c-6b5a-4c3d-2e1f-0a9b8c7d6e5f", "kind": "company", "members": 1, "name": "Acme", "status": "active" }401Not signed in, or the CSRF token is missing or wrong.403Your email address is not confirmed yet.409You own the most organizations one person can.422The name is missing or too long.
/api/account/orgs/{orgId}/invitations
Invite someone to the organization
For owners and admins with a confirmed email address; only owners invite owners (403). Emails the address a link to the account page, where the person with that confirmed address accepts or declines; it lasts 14 days. Inviting the same address again changes the role and starts the 14 days again, but is emailed again only after an hour. Each person sends at most 50 invitations a day (429).
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
{
"email": "sam@example.com",
"role": "member"
}
201The members and invitations401Not signed in, or the CSRF token is missing or wrong.403Your email address is not confirmed404Not an organization whose members you manage.409The address is a member's already.422Not an email address429You sent the day's invitations.
/api/account/orgs/{orgId}/invitations/{invitationId}
Withdraw an invitation
For owners and admins.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
invitationId * | path | string |
204Withdrawn.401Not signed in, or the CSRF token is missing or wrong.404No such invitation
/api/account/orgs/{orgId}/members
An organization's members and open invitations
For its owners and admins.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
200The members, and invitations not yet answered or expired.{ "invitations": [ { "createdAt": "2026-10-18T09:00:00Z", "email": "sam@example.com", "expiresAt": "2026-11-01T09:00:00Z", "id": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f", "invitedBy": "Alice", "orgId": "0f9e8d7c-6b5a-4c3d-2e1f-0a9b8c7d6e5f", "orgName": "Acme", "role": "member" } ], "members": [ { "createdAt": "2026-01-02T10:00:00Z", "email": "alice@example.com", "fromMailbox": false, "name": "Alice", "orgId": "0f9e8d7c-6b5a-4c3d-2e1f-0a9b8c7d6e5f", "orgKind": "company", "orgName": "Acme", "personId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "role": "owner" } ] }401Not signed in, or the CSRF token is missing or wrong.404Not an organization whose members you manage.
/api/account/orgs/{orgId}/members/{personId}
Change a member's role
For owners and admins; only owners make owners or change an owner's role (403). The last owner cannot be demoted (409). A member with a mailbox in one of the organization's Mailocity teams gets the role on those mailboxes too.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
personId * | path | string |
{
"role": "billing"
}
200The members and invitations401Not signed in, or the CSRF token is missing or wrong.403Only owners make or change owners.404No such member409The organization would have no owner.422Not one of the roles.
/api/account/orgs/{orgId}/members/{personId}
Remove a member, or leave
Your own id leaves the organization, whatever your role. Owners and admins remove others (only owners remove owners). The last owner cannot go (409), nor a member with a mailbox in one of its Mailocity teams while the mailbox is there (409, naming it).
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
personId * | path | string |
204Removed.401Not signed in, or the CSRF token is missing or wrong.403Only owners remove owners.404No such member or organization.409The last owner
/api/account/orgs/{orgId}/whois
How the organization appears in ARIN's whois
Where we register its servers' address blocks with ARIN (SWIP), whois shows private (the default: ARIN's Private Customer, with only the city, state, postal code and country of its billing address) or organization (its name and billing address). With the blocks registered. For its owners and admins.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
200The choice and the blocks registered.{ "reassignments": [ { "net": "2001:db8:1000:1::/64", "netHandle": "NET6-2001-DB8-1000-1-1", "status": "registered" } ], "whois": "private" }401Not signed in, or the CSRF token is missing or wrong.404No such organization
/api/account/orgs/{orgId}/whois
Change how the organization appears in ARIN's whois
For its owners. The blocks registered are updated at ARIN within minutes. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
{
"whois": "organization"
}
200The choice and the blocks registered.401Not signed in, or the CSRF token is missing or wrong.403Only the organization's owners change it.404No such organization422Not private or organization.
Servers
/api/account/orgs/{orgId}/servers
The organization's servers
For the organization's owners and admins. Each VPS subscription is one server; setUp is false until we have set its VM up. state is the server's state now (with uptime and CPU, memory, disk and network use when the plan includes status and usage), or stateError says it could not be read. network has its IPv4 addresses (bind them, or take them by DHCP) and its IPv6: a management address in a shared /64 with its gateway, and a /64 of its own routed to the management address. With ipv6.dhcp, both can also be taken by DHCPv6 (the /64 as a delegated prefix). unpaid says how far a suspension for an unpaid invoice has gone: offline (its network is off, still running), shutdown, or removed (its VM is gone); empty otherwise.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string |
200The servers.{ "items": [ { "createdAt": "2026-11-06T12:00:00Z", "features": [ "power", "usage", "ipv6" ], "id": "1f0c2b9e-3a5d-4a8e-9c1b-7d2e4f6a8b0c", "kind": "qemu", "name": "Server 1", "network": { "ipv4": [ { "address": "203.0.113.10", "bridge": "", "gateway": "", "prefix": 32, "routed": true } ], "ipv4Management": { "address": "10.255.0.10", "bridge": "vmbr0", "dhcp": true, "gateway": "10.255.0.1", "network": "10.255.0.0/24" }, "ipv6": { "bridge": "vmbr1", "dhcp": false, "gateway": "2001:db8:ffff::1", "management": "2001:db8:ffff::100", "network": "2001:db8:ffff::/64", "nic": 1, "prefix": "2001:db8:1000:1::/64" }, "mac": "bc:24:11:aa:bb:cc" }, "plan": { "code": "vps-small", "diskGb": 40, "ipv4": 1, "memoryMb": 2048, "name": "Small", "snapshots": 3, "vcpus": 1 }, "setUp": true, "state": { "cpu": 0.04, "cpus": 1, "maxmem": 2147483648, "mem": 734003200, "netin": 1048576, "netout": 524288, "status": "running", "uptime": 86400 }, "status": "active", "unpaid": "" } ] }401Not signed in, or the CSRF token is missing or wrong.404No such organization
/api/account/orgs/{orgId}/servers/{serverId}
A server
As in the list, for the organization's owners and admins.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
200The server401Not signed in, or the CSRF token is missing or wrong.404No such server
/api/account/orgs/{orgId}/servers/{serverId}
Rename a server
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"name": "web1"
}
200The server401Not signed in, or the CSRF token is missing or wrong.404No such server422The name is empty
/api/account/orgs/{orgId}/servers/{serverId}/access
A server's access settings
What the VM's cloud-init sets at its next start: its hostname, the user it makes, the SSH keys that user accepts, whether a password is set (never the password), and whether the VM has a cloud-init drive (without one, ask support). VMs only: a container's access is set inside it. With the access feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
200The access settings.{ "cloudInit": true, "hostname": "web1.example.com", "passwordSet": true, "sshKeys": [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample amy@laptop" ], "user": "debian" }401Not signed in, or the CSRF token is missing or wrong.404No such server, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a container, no cloud-init drive, or a field not as described.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/access
Set a server's hostname, user, SSH keys or password
Changes what the VM's cloud-init sets; fields left out stay as they are. The VM takes the change on at its next start through us (a reboot from inside the VM does not): send reboot true to reboot a running VM now. An empty password makes a random one of 20 characters, answered once as password and never kept by us; a password given must be 12 to 128 characters. An empty sshKeys removes them all. The password is never recorded; the change is, by what it set. With the access feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"password": "",
"reboot": true
}
200The access settings now, the password made (only when one was), and whether it was rebooted.{ "cloudInit": true, "hostname": "web1", "password": "x7Kp2mQr9TzVw4NcHb3E", "passwordSet": true, "rebooted": true, "sshKeys": [], "user": "root" }401Not signed in, or the CSRF token is missing or wrong.404No such server, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a container, no cloud-init drive, or a field not as described.429Another action was taken on the server a moment ago (reboot only).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/backups
A server's backups
The server's backups on its cluster's backup storage, newest first, with how many are kept and whether a backup or restore is running now. Only the backups Proxmox lists for the server's own VM are given. Where the plan includes backups, for the organization's owners and admins, while the server is active.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
200The backups.{ "items": [ { "ctime": 1793500000, "notes": "Zappocity: web1", "protected": false, "size": 1073741824, "vmid": 101, "volid": "backups:backup/vzdump-qemu-101-2026_11_01-02_00_00.vma.zst" } ], "keep": 7, "running": false }401Not signed in, or the CSRF token is missing or wrong.404No such server409The server is still being set up.422Not allowed: the plan has no backups, the server is suspended, or backups are not set up for it yet.429A backup was taken or restored less than ten minutes ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/backups
Back a server up now
A snapshot backup, so the server keeps running. Proxmox keeps the newest few of the server (the cluster's backupKeep) and removes the oldest beyond that. Refused while Proxmox is busy with the server. Customers may take a backup, or restore one, every ten minutes. Where the plan includes backups, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
202Started; the backup appears in the list once Proxmox has finished.{ "started": true }401Not signed in, or the CSRF token is missing or wrong.404No such server409The server is still being set up.422Not allowed: the plan has no backups, the server is suspended, or backups are not set up for it yet.429A backup was taken or restored less than ten minutes ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/backups/delete
Delete one of a server's backups
Removes one of the server's own backups from the storage; a protected one cannot be removed. Where the plan includes backups, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"volid": "backups:backup/vzdump-qemu-101-2026_11_01-02_00_00.vma.zst"
}
200Deleted.{ "deleted": "backups:backup/vzdump-qemu-101-2026_11_01-02_00_00.vma.zst" }401Not signed in, or the CSRF token is missing or wrong.404No such server (or you do not manage the organization's servers), or no such backup of it.409The server is still being set up.422Not allowed: the plan has no backups, the server is suspended, or backups are not set up for it yet.429A backup was taken or restored less than ten minutes ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/backups/restore
Restore a backup over a server
Puts one of the server's own backups back over its disks: everything on the server now is replaced. The server must be stopped, and Proxmox not busy with it. Start it again once the restore has finished. Where the plan includes backups, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"volid": "backups:backup/vzdump-qemu-101-2026_11_01-02_00_00.vma.zst"
}
202Started.{ "started": true }401Not signed in, or the CSRF token is missing or wrong.404No such server (or you do not manage the organization's servers), or no such backup of it.409The server is still being set up.422Not allowed: the plan has no backups, the server is suspended, or backups are not set up for it yet.429A backup was taken or restored less than ten minutes ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/console
The server's text console (a websocket)
Upgrades to a websocket relayed to the server's text console: its first serial port (serial0, which must be a socket) for a VM, or the container's console. Send JSON text messages: {"t": "d", "d": "ls\r"} types keys, {"t": "r", "c": 120, "r": 40} gives the terminal's size in columns and rows. Binary messages back are the console's output, for a terminal to show; a text message {"closed": "why"} comes before Zappocity closes it. The Origin must be this site's. For the organization's owners and admins, while the server is active with the console feature; checked again every minute while open, and closed when that no longer holds, after eight hours, or with three consoles open on the server already. Opening one is recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string | |
csrf * | query | string | The session's CSRF token (a browser cannot set headers on a websocket). |
101Switched to the websocket.401Not signed in403Opened from another site (the Origin is not this site).404No such server409The server is still being set up.422Suspended, the plan has no console, or three consoles are open on it already.502The console could not be reached.
/api/account/orgs/{orgId}/servers/{serverId}/firewall
A server's firewall
The server's own Proxmox firewall: its options, every rule in order (managed ones are ours: the port 25 block until support opens it), aliases, IP sets, and the addresses its IP filter lets it send from (its IPv4 addresses, management IPv6 address and routed /64; ours, kept in step with its addresses). Reading it first puts our part back if it was changed in Proxmox. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/aliases
Add a firewall alias
A name for an address or network, for rules' source and dest. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"cidr": "198.51.100.0/24",
"comment": "Our office",
"name": "office"
}
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/aliases/{name}
Change a firewall alias
Changes an alias's address or network and comment. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string | |
name * | path | string |
{
"cidr": "198.51.100.0/25"
}
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/aliases/{name}
Remove a firewall alias
Removes an alias (Proxmox refuses while a rule uses it). With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string | |
name * | path | string |
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/ipsets
Make a firewall IP set
An empty set of addresses and networks, for rules as +name. Names starting ipfilter are the IP filter's. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"name": "monitoring"
}
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/ipsets/{name}
Remove a firewall IP set
Removes one of the server's IP sets and its entries; never the IP filter's. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string | |
name * | path | string |
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/ipsets/{name}/entries
Add to a firewall IP set
Adds an address or network (nomatch leaves it out of the set). Never to the IP filter's. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string | |
name * | path | string |
{
"cidr": "203.0.113.5"
}
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/ipsets/{name}/entries/delete
Take an address out of a firewall IP set
Removes an address or network from one of the server's IP sets; never the IP filter's. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string | |
name * | path | string |
{
"cidr": "203.0.113.5"
}
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/options
Set a server's firewall policies and options
The policies for traffic no rule matches, log levels, and DHCP and NDP; fields left out stay as they are. The firewall itself, its IP and MAC filters and router advertisements are ours: asking to change them is refused (set both policies to ACCEPT to let everything through). With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"policyIn": "DROP",
"policyOut": "ACCEPT"
}
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/rules
Add a firewall rule
Adds a rule at pos in the whole list (default the end), never above ours, so the port 25 block always comes first. At most 200 rules. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"action": "ACCEPT",
"comment": "Web",
"dport": "443",
"enable": true,
"proto": "tcp",
"type": "in"
}
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/rules/{pos}
Change a firewall rule
Replaces the rule at pos (fields left out are removed), and moves it to moveTo when given, never above ours. Ours cannot be changed: the port 25 rule says to open a support ticket. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string | |
pos * | path | integer |
{
"action": "ACCEPT",
"enable": true,
"macro": "SSH",
"moveTo": 1,
"source": "office",
"type": "in"
}
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/firewall/rules/{pos}
Remove a firewall rule
Removes the rule at pos. Ours cannot be removed: the port 25 rule says to open a support ticket. With the firewall feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string | |
pos * | path | integer |
200The firewall as it is now.{ "addresses": [ "192.0.2.10", "2001:db8:ffff::100", "2001:db8:1000:1::/64" ], "aliases": [ { "cidr": "198.51.100.0/24", "comment": "Our office", "name": "office" } ], "ipsets": [ { "comment": "", "entries": [ { "cidr": "203.0.113.5" } ], "name": "monitoring" } ], "options": { "dhcp": true, "logLevelIn": "nolog", "logLevelOut": "nolog", "ndp": true, "policyIn": "DROP", "policyOut": "ACCEPT" }, "rules": [ { "action": "REJECT", "comment": "zappocity: outgoing mail (port 25) is blocked; open a support ticket to have it opened", "dport": "25", "enable": true, "managed": true, "pos": 0, "proto": "tcp", "type": "out" }, { "action": "ACCEPT", "comment": "SSH from anywhere", "enable": true, "macro": "SSH", "managed": false, "pos": 1, "type": "in" } ], "smtpOpen": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such rule, alias or IP set on it, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a field not one Proxmox takes, or a change to our part (the port 25 rule says to open a support ticket; the IP filter and the firewall staying on are ours).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/history
A server's usage history
The server's average use over each step of a timeframe, oldest first, as Proxmox keeps it (about 70 steps: a minute each for an hour, half an hour for a day, and so on). cpu is a fraction of the server's vCPUs (vcpus, as the plan gives now; 1 is all of them), mem and maxmem are bytes, and netin, netout, diskread and diskwrite are bytes a second. Steps with no data (the server was stopped) are left out. Where the plan includes usage graphs, for the organization's owners and admins, while the server is active.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string | |
timeframe | query | string |
200The history.{ "points": [ { "cpu": 0.12, "diskread": 4096, "diskwrite": 20480, "maxmem": 2147483648, "mem": 1073741824, "netin": 1520.5, "netout": 830.2, "time": 1793500200 } ], "timeframe": "day", "vcpus": 2 }401Not signed in, or the CSRF token is missing or wrong.404No such server, or you do not manage the organization's servers.409The server is still being set up.422Not allowed: the plan has no usage graphs, the server is suspended, or the timeframe is not one of those listed.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/power
Start, shut down, stop, reboot or reset a server
Where the plan includes power controls, for the organization's owners and admins. shutdown asks the server's system to shut down and turns it off after three minutes if it has not; stop turns it off at once, like pulling the plug; reboot asks it to restart; reset (not for containers) restarts it at once. A suspended server cannot be started. Actions are at least five seconds apart. The action is carried out after the answer: read the server again to see its state.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"action": "reboot"
}
202Taken.{ "action": "reboot" }401Not signed in, or the CSRF token is missing or wrong.404No such server409The server is still being set up.422Not allowed: the plan has no power controls, the server is suspended, or the action is unknown.429Another action was taken a moment ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/rdns
Name one of a server's addresses (reverse DNS)
Sets the PTR record of one of the server's addresses: an IPv4 address of a pool with a reverse zone, its management address, or any address of its routed /64 (at most 32 of those), in the reverse zones staff chose on dnso.city. Not in a delegated /64 (see rdns-delegation). The name must resolve (A or AAAA) to the address, so nobody can name an address after a domain they do not control. An empty hostname removes the name. Names go when their address is taken back. For the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"address": "192.0.2.10",
"hostname": "mail.example.com"
}
200The server, with its network's names under reverseDns.{ "network": { "reverseDns": { "delegation": null, "ipv4": [ "192.0.2.10" ], "management": true, "names": { "192.0.2.10": "mail.example.com" }, "prefix": true } } }401Not signed in, or the CSRF token is missing or wrong.404No such server422Not the server's address, reverse DNS not offered for it, a name that does not resolve to it, or the server is suspended.
/api/account/orgs/{orgId}/servers/{serverId}/rdns-delegation
Delegate a server's routed /64 to name servers (reverse DNS)
Puts NS records for the server's routed /64 (its 16-nibble name under ip6.arpa) in the pool's reverse zone, so its people keep every name in the /64 on name servers of their own; the names Zappocity kept in the /64 go, and new ones are refused while it is delegated. Each name server must resolve. dnso.city's own name servers can be used only once the /64's zone is on dnso.city in a DNS account the caller owns (only staff and the people who manage the server can add that zone); its DS record is then put beside the NS records, unless the zone is linked to another provider. An empty list removes the delegation; it also goes when the /64 is taken back. For the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"nameservers": [
"ns1.example.com",
"ns2.example.com"
]
}
200The server, with the delegation under reverseDns.{ "network": { "reverseDns": { "delegation": { "ds": [], "nameservers": [ "ns1.example.com", "ns2.example.com" ], "prefix": "2001:db8:1000:1::/64", "updatedAt": "2026-11-07T12:00:00Z" }, "ipv4": [ "192.0.2.10" ], "management": true, "names": {}, "prefix": true } } }401Not signed in, or the CSRF token is missing or wrong.404No such server422No routed /64, reverse DNS not offered for it, name servers that do not resolve, dnso.city's without the zone in a DNS account you own, or the server is suspended.
/api/account/orgs/{orgId}/servers/{serverId}/reinstall
The images a server may be reinstalled from
Its cluster's templates. VMs only. With the rescue (or reinstall) feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
200The templates.{ "templates": [ { "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e", "name": "Debian 12" }, { "id": "6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f", "name": "Ubuntu 24.04" } ] }401Not signed in, or the CSRF token is missing or wrong.404No such server, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a container, or a request not as described (the reason says which).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/reinstall
Reinstall a server
Erases the VM (it is stopped and destroyed with its disks and snapshots; backups stay) and starts building a new one from the template, with the same addresses. The server's name must be typed as confirm. SSH keys left out are the ones the VM has now; the template's user is used, and a password can be set on the Access tab afterwards. The build runs in the background: the server shows as being set up until it is ready. Spaced like power actions. With the rescue (or reinstall) feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"confirm": "web1",
"templateId": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e"
}
202Erased; the build started.{ "started": true }401Not signed in, or the CSRF token is missing or wrong.404No such server, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a container, or a request not as described (the reason says which).429Another action was taken on the server a moment ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/rescue
A server's rescue state and images
Whether the VM is booting from an image (and which), and the ISO images it may boot from (its cluster's: rescue systems and installers). VMs only. With the rescue (or reinstall) feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
200The rescue state.{ "active": true, "image": "SystemRescue 11", "images": [ { "id": "3f1c2a9e-7b4d-4e1a-9c2f-5d6e7f8a9b0c", "name": "SystemRescue 11" }, { "description": "Installs Debian 12 over the console, with an encrypted disk if wanted", "id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", "installer": true, "name": "Debian 12 installer" } ] }401Not signed in, or the CSRF token is missing or wrong.404No such server, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a container, or a request not as described (the reason says which).502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/rescue
Boot a server from an image
Puts the image in a free CD slot (or the VM's CD drive) first in the boot order, with its disks still attached, and starts the VM, or reboots it if running. It stays so, across reboots, until rescue is ended. Refused while in rescue, with no free CD slot, or when the image isn't on the VM's node. Spaced like power actions. With the rescue (or reinstall) feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"imageId": "3f1c2a9e-7b4d-4e1a-9c2f-5d6e7f8a9b0c"
}
200In rescue.{ "active": true, "image": "SystemRescue 11", "images": [ { "id": "3f1c2a9e-7b4d-4e1a-9c2f-5d6e7f8a9b0c", "name": "SystemRescue 11" }, { "description": "Installs Debian 12 over the console, with an encrypted disk if wanted", "id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", "installer": true, "name": "Debian 12 installer" } ] }401Not signed in, or the CSRF token is missing or wrong.404No such server, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a container, or a request not as described (the reason says which).429Another action was taken on the server a moment ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/rescue/end
End rescue
Takes the image out, puts the CD slot and the boot order back as they were, and reboots the VM if it is running. With the rescue (or reinstall) feature, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
200Out of rescue.{ "active": false, "image": "", "images": [ { "id": "3f1c2a9e-7b4d-4e1a-9c2f-5d6e7f8a9b0c", "name": "SystemRescue 11" } ] }401Not signed in, or the CSRF token is missing or wrong.404No such server, or you do not manage the organization's servers.409The server is still being set up.422Refused: not in the plan, the server suspended, a container, or a request not as described (the reason says which).429Another action was taken on the server a moment ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/snapshots
A server's snapshots
The server's snapshots in Proxmox, newest first (limit is how many the plan lets the server keep), and whether Proxmox is taking, rolling back or deleting one now. Only the snapshots Proxmox lists for the server's own VM are given. ram says the running memory was saved too. Where the plan includes snapshots, for the organization's owners and admins, while the server is active.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
200The snapshots.{ "items": [ { "description": "Zappocity: web1", "name": "before-upgrade", "parent": "", "ram": false, "time": 1793500000 } ], "limit": 3, "running": false }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such snapshot of it, or you do not manage the organization's servers.409The server is still being set up.422Not allowed: the plan has no snapshots or keeps no more, the name is taken or not one Proxmox takes, the server is suspended, or Proxmox is busy with it.429A snapshot was taken or rolled back less than two minutes ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/snapshots
Snapshot a server now
Takes a snapshot of the server's disks (and, with ram, a running VM's memory, so a rollback resumes it running; never a container's). Every field is optional (send {}): a name is made from the time. Its people may keep up to the plan's snapshots limit and take or roll back one every two minutes. Refused while Proxmox is busy with the server. Where the plan includes snapshots, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"description": "Before the PHP upgrade",
"name": "before-upgrade",
"ram": false
}
202Started; the snapshot appears in the list once Proxmox has finished.{ "started": true }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such snapshot of it, or you do not manage the organization's servers.409The server is still being set up.422Not allowed: the plan has no snapshots or keeps no more, the name is taken or not one Proxmox takes, the server is suspended, or Proxmox is busy with it.429A snapshot was taken or rolled back less than two minutes ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/snapshots/delete
Delete one of a server's snapshots
Removes one of the server's own snapshots; the server keeps what is on it now. Refused while Proxmox is busy with the server. Where the plan includes snapshots, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"name": "before-upgrade"
}
202Started.{ "started": true }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such snapshot of it, or you do not manage the organization's servers.409The server is still being set up.422Not allowed: the plan has no snapshots or keeps no more, the name is taken or not one Proxmox takes, the server is suspended, or Proxmox is busy with it.429A snapshot was taken or rolled back less than two minutes ago.502The server could not be reached just now.
/api/account/orgs/{orgId}/servers/{serverId}/snapshots/rollback
Roll a server back to one of its snapshots
Puts the server's disks (and its configuration, and memory where it was saved) back as they were at the snapshot: whatever changed since is lost. A snapshot taken with more vCPUs or memory than the plan has now is refused (ask staff). Once the rollback has finished the plan's vCPUs and memory are put on the VM again. Where the plan includes snapshots, for the organization's owners and admins, while the server is active. Recorded in the organization's activity.
| Parameter | In | Type | |
|---|---|---|---|
orgId * | path | string | |
serverId * | path | string |
{
"name": "before-upgrade"
}
202Started.{ "started": true }401Not signed in, or the CSRF token is missing or wrong.404No such server, no such snapshot of it, or you do not manage the organization's servers.409The server is still being set up.422Not allowed: the plan has no snapshots or keeps no more, the name is taken or not one Proxmox takes, the server is suspended, or Proxmox is busy with it.429A snapshot was taken or rolled back less than two minutes ago.502The server could not be reached just now.
/api/vps/dhcp
The DHCP subnets and reservations, for a Kea host (kea-sync)
For kea-sync on the Kea host, with the host's own token (made by staff with role dhcp) in Authorization: Bearer zk_..., not a session; a router's token is refused, and a Kea host's token reads nothing else. subnets4 are the on-link IPv4 pools and the IPv4 management network that have a Kea subnet id, each with the router's address on it, its bridge and a reservation (MAC to address) for each address of a linked server; routed addresses have none (their server's management address is reserved instead). subnets6 is the management /64 when it has a Kea DHCPv6 subnet id, with each linked server's management address and routed /64 (as a delegated prefix) for the MAC of the card its IPv6 is on. Each read is recorded against the host (when, from where, how many reservations).
200The subnets.{ "host": "kea1", "subnets4": [ { "bridge": "vmbr0", "id": 1, "reservations": [ { "hwAddress": "bc:24:11:aa:bb:01", "ipAddress": "192.0.2.2", "serverId": "1f0c2b9e-3a5d-4a8e-9c1b-7d2e4f6a8b0c" } ], "routers": "192.0.2.1", "subnet": "192.0.2.0/24" } ], "subnets6": [ { "bridge": "vmbr1", "id": 6, "reservations": [ { "hwAddress": "bc:24:11:aa:bb:61", "ipAddress": "2001:db8:ffff::100", "prefix": "2001:db8:1000:1::/64", "serverId": "1f0c2b9e-3a5d-4a8e-9c1b-7d2e4f6a8b0c" } ], "subnet": "2001:db8:ffff::/64" } ] }401No Kea host token
/api/vps/routes
The routes, for a router (prefixd)
For prefixd on a router, with the router's own token (made by staff) in Authorization: Bearer zr_..., not a session. Answers the management /64, its gateway, the pool and each /64 to route via its server's management address. Suspended servers have no route. metadata is the IPv4 address of the Proxmox host running the metadata service, which prefixd routes 169.254.169.254/32 to ("" for no route). management4, gateway4 and pools4 are the IPv4 management network, the router's address on it and the routed IPv4 pools; routes4 are each routed address, a /32, via its server's management address. Each read is recorded against the router (when, from where, how many).
200The routes.{ "gateway": "2001:db8:ffff::1", "gateway4": "10.255.0.1", "management": "2001:db8:ffff::/64", "management4": "10.255.0.0/24", "metadata": "192.0.2.42", "pool": "2001:db8:1000::/40", "pools4": [ "203.0.113.0/24" ], "router": "edge1", "routes": [ { "prefix": "2001:db8:1000:1::/64", "serverId": "1f0c2b9e-3a5d-4a8e-9c1b-7d2e4f6a8b0c", "via": "2001:db8:ffff::100" } ], "routes4": [ { "prefix": "203.0.113.10/32", "serverId": "1f0c2b9e-3a5d-4a8e-9c1b-7d2e4f6a8b0c", "via": "10.255.0.10" } ] }401No router token
Sign-in
/api/account/session/passkey
Sign in with a passkey (start)
No email or password; the passkey names its account. Returns options for navigator.credentials.get() and a challengeId.
200The options and a challengeId.
/api/account/session/passkey/finish
Sign in with a passkey (finish)
The browser's navigator.credentials.get() result. The passkey must have verified the user (PIN or biometrics), so the session counts as two-step for every product signed in to through Zappocity.
{
"challengeId": "5b0b4c1e-7d1a-4f5e-9a3b-1c2d3e4f5a6b",
"credential": {
"id": "q8V2...",
"response": {
"authenticatorData": "SZYN...",
"clientDataJSON": "eyJ0...",
"signature": "MEUC...",
"userHandle": "dXNl..."
},
"type": "public-key"
}
}
201Signed in; as POST /api/account/session.400The challenge expired or was used.401The passkey did not check out.
/api/account/session/security-key
Sign in with a password and a security key (start)
When POST /api/account/session answers twoFactorRequired with securityKey: true, send the email and password here for options for navigator.credentials.get().
{
"email": "amy@example.com",
"password": "correct horse battery"
}
200The options and a challengeId.401Wrong email or password.409The account has no security key.429Too many failed sign-ins.
/api/account/session/security-key/finish
Sign in with a password and a security key (finish)
The browser's navigator.credentials.get() result. A key whose signature counter went backwards (a copy) is refused.
{
"challengeId": "5b0b4c1e-7d1a-4f5e-9a3b-1c2d3e4f5a6b",
"credential": {
"id": "q8V2...",
"response": {
"authenticatorData": "SZYN...",
"clientDataJSON": "eyJ0...",
"signature": "MEUC..."
},
"type": "public-key"
}
}
201Signed in; as POST /api/account/session.400The challenge expired or was used.401The key did not check out.
Signing in
/api/account/handoff
A one-time code for a desk's host
For the signed-in person, a code that signs them in on one supporto.city desk's host, once, within two minutes, and only in the browser holding the state the host started with. Answers where to send the browser. 422 when the host is not a desk's.
{
"host": "acme.supporto.city",
"next": "/desk/acme",
"state": "q1w2e3..."
}
200Where to go.{ "url": "https://acme.supporto.city/api/account/handoff/redeem?code=...\u0026next=%2Fdesk%2Facme" }400Not JSON, or unknown fields.401Not signed in, or the CSRF token is missing or wrong.422A value is not acceptable, such as a password under 12 characters.
/api/account/handoff/redeem
Use a handoff code
On the desk's host: with this browser's state cookie, uses up the code, opens a session here (two-step sign-in carried over) and redirects to next. 400 when the code is wrong, used, expired, for another host or another browser.
| Parameter | In | Type | |
|---|---|---|---|
code * | query | string | |
next | query | string |
302Signed in; to the desk.400The code cannot be used here.403The account is suspended.
/api/account/handoff/start
Sign in on a supporto.city desk's host
On a desk's own host (acme.supporto.city): keeps a state in a cookie of this host (__Host-zappocity_handoff, ten minutes) and redirects to the Zappocity account page with #handoff=<host>&state=<state>&next=<next>, where the signed-in page asks for a code. On any other host it redirects to /account#next=<next>. next is a desk's agent app (/desk or /desk/{desk}); anything else becomes /desk.
| Parameter | In | Type | |
|---|---|---|---|
next | query | string |
302To the account page.
/api/account/reset
Email a password reset link
The answer is the same whether or not the address has an account. Links work once, for an hour.
202If the address has an account403The captcha answer is missing or wrong (`captchaRequired` is true).429Too many attempts from this address, or too many recent links, or the account is locked.
/api/account/reset/confirm
Set a new password from a reset link
The link also confirms the address. Every session ends.
200The password is set.400The link has expired or was already used.422A value is not acceptable, such as a password under 12 characters.
/api/account/session
Who is signed in, and the CSRF token
200Signed in.{ "csrfToken": "Yk3...", "expiresAt": "2026-11-07T12:00:00Z", "person": { "email": "amy@example.com", "emailVerifiedAt": null, "id": "6c1f0b7e-2d4a-4b8e-9f3c-1a2b3c4d5e6f", "name": "Amy" } }401Not signed in, or the CSRF token is missing or wrong.
/api/account/session
Sign in
201Signed in.{ "csrfToken": "Yk3...", "expiresAt": "2026-11-07T12:00:00Z", "person": { "email": "amy@example.com", "emailVerifiedAt": null, "id": "6c1f0b7e-2d4a-4b8e-9f3c-1a2b3c4d5e6f", "name": "Amy" } }400Not JSON, or unknown fields.401Wrong email or password.403The account is suspended (only said after the right password)429Too many attempts from this address, or too many recent links, or the account is locked.
/api/account/session
Sign out
Ends this session, and the webmail, panel and help desk sessions signed in through it. On a help desk's own address, it ends the zappo.city session it was handed off from too.
204Signed out.
/api/account/session/confirm
Prove who you are again
For an application that asked (OpenID Connect prompt=login, or a max_age the session's last proof is older than): the password, and the code when two-step sign-in is on. The session stays the same; its next ID tokens carry the new auth_time. A wrong password or code counts like one at sign-in. Needs X-CSRF-Token.
{
"code": "123456",
"password": "correct horse battery staple"
}
204Confirmed.401Not signed in, or the CSRF token is missing or wrong.403Wrong password or code; twoFactorRequired is true when a code is needed.429Too many failed attempts.
Signing up
/api/account/signup
Create an account
Makes an unconfirmed account and emails a link to confirm the address (which also signs in). If the address already has an account, its owner is emailed instead; the answer is the same.
{
"email": "amy@example.com",
"name": "Amy",
"password": "correct horse battery"
}
202Check your email.400Not JSON, or unknown fields.403The captcha answer is missing or wrong (`captchaRequired` is true).422A value is not acceptable, such as a password under 12 characters.429Too many attempts from this address, or too many recent links, or the account is locked.
/api/account/verify
Confirm an email address from its link, and sign in
200Confirmed and signed in (as Session), or, for an account with two-step sign-in, confirmed with signIn true: sign in the usual way.400The link has expired or was already used.
/api/account/verify/resend
Send a new confirmation link
202Sent.401Not signed in, or the CSRF token is missing or wrong.409The address is already confirmed.429Too many attempts from this address, or too many recent links, or the account is locked.
Status
/api/account/status
Service status
Incidents and maintenance: those not ended that have started or start within two weeks, and those ended in the last week, each with its updates (newest first) and the products it affects (none: every product). Anyone may read it, signed in or not; never who on staff posted it. For someone signed in, affectsYou says whether it affects every product or one that an organization they are in subscribes to.
200The notices.{ "items": [ { "affectsYou": true, "createdAt": "2026-11-05T09:05:00Z", "endsAt": null, "id": "7d1e2f3a-4b5c-4d6e-8f90-a1b2c3d4e5f6", "kind": "incident", "productNames": [ "Mailocity" ], "products": [ "mailocity" ], "resolvedAt": null, "severity": "major", "startsAt": "2026-11-05T09:00:00Z", "status": "identified", "title": "Slow mail delivery", "updatedAt": "2026-11-05T09:40:00Z", "updates": [ { "body": "A full disk on one relay; mail is queued, not lost.", "createdAt": "2026-11-05T09:40:00Z", "id": 2, "status": "identified" } ] } ] }
Support
/api/account/desk
The desk this host is
On a supporto.city desk's own host, the desk's name, for its customer page (/support). On that host the support and help routes (tickets, queues, help articles) are the desk's, not Zappocity's. 404 on any other host.
200The desk.{ "name": "Acme Support", "slug": "acme", "status": "active" }404Not a desk's host.
/api/account/help
The help center
Its categories and articles: published public ones, and those for customers when signed in. q searches them (the best matches first, with a snippet); limit=5 gives a few suggestions while writing a request. Signing in is not needed.
| Parameter | In | Type | |
|---|---|---|---|
q | query | string | |
category | query | string | |
limit | query | integer |
200The categories and articles.{ "categories": [ { "articles": 6, "name": "Billing", "slug": "billing" } ], "items": [ { "category": "billing", "kind": "faq", "slug": "refund", "snippet": "Open a request under Billing within 30 days", "title": "How do I get a refund?" } ], "signedIn": false }404No such category.
/api/account/help/{slug}
A help article, rendered
| Parameter | In | Type | |
|---|---|---|---|
slug * | path | string |
200The article.{ "category": "billing", "html": "\u003cp\u003eOpen a request under \u003cstrong\u003eBilling\u003c/strong\u003e within 30 days.\u003c/p\u003e", "kind": "faq", "slug": "refund", "title": "How do I get a refund?", "updatedAt": "2026-10-09T12:00:00Z" }404No such article
/api/account/help/{slug}/rating
Say whether an article helped
Counted at most 20 times an hour from one address for an article.
| Parameter | In | Type | |
|---|---|---|---|
slug * | path | string |
{
"helpful": true
}
204Counted.404No such article.422helpful is missing.
/api/account/support/queues
What a request can be about
The active, public queues (support, billing, abuse...), each with the questions it asks (fields: answer them in fields when opening a request; required ones must be answered).
200The queues.{ "items": [ { "allowContacts": true, "description": "Invoices, payments, plans and refunds.", "fields": [ { "help": "On your invoice, such as ZI-1042.", "key": "order_number", "kind": "text", "label": "Order number", "options": [], "required": true } ], "name": "Billing", "slug": "billing" } ] }401Not signed in, or the CSRF token is missing or wrong.
/api/account/tickets
Your support requests
Opened by you, or sent from or naming your confirmed address as a contact. Newest activity first. tag is the request as it is shown (its queue's prefix and the number).
200The tickets.{ "items": [ { "number": 41, "prefix": "BIL", "priority": "normal", "queue": "billing", "requesterEmail": "amy@example.com", "status": "pending", "subject": "Invoice question", "tag": "BIL-41", "updatedAt": "2026-10-08T12:00:00Z" } ] }401Not signed in, or the CSRF token is missing or wrong.
/api/account/tickets
Open a support request
You get a confirmation by email; contacts (when the queue takes them) also get the replies.
{
"body": "Why was I charged twice?",
"fields": {
"order_number": "ZI-1042"
},
"queue": "billing",
"subject": "Invoice question"
}
201The ticket.401Not signed in, or the CSRF token is missing or wrong.422A value is not acceptable, such as a password under 12 characters.
/api/account/tickets/{number}
A request with its messages and contacts
Internal notes are never shown. queue says whether contacts can be added and whether you may close it. fields are the answers to the queue's questions.
| Parameter | In | Type | |
|---|---|---|---|
number * | path | string | The number, with or without its prefix (BIL-41, or ZC-41 from before queues had their own). |
200OK.401Not signed in, or the CSRF token is missing or wrong.404No such ticket
/api/account/tickets/{number}/close
Close your request
Where the queue lets customers; abuse reports, for one, stay open until staff close them (403).
| Parameter | In | Type | |
|---|---|---|---|
number * | path | string |
200OK.401Not signed in, or the CSRF token is missing or wrong.403Only staff close requests in this queue.404No such ticket
/api/account/tickets/{number}/contacts
Add someone to the replies
Only where the queue allows contacts (409 otherwise); at most 20.
| Parameter | In | Type | |
|---|---|---|---|
number * | path | string |
200OK.401Not signed in, or the CSRF token is missing or wrong.404No such ticket409The queue does not take contacts
/api/account/tickets/{number}/contacts/{email}
Take someone off the replies
| Parameter | In | Type | |
|---|---|---|---|
number * | path | string | |
email * | path | string |
200OK.401Not signed in, or the CSRF token is missing or wrong.404No such ticket
/api/account/tickets/{number}/messages
Reply
Reopens a resolved request; a closed one takes no more (409).
| Parameter | In | Type | |
|---|---|---|---|
number * | path | string |
201The ticket with its messages.401Not signed in, or the CSRF token is missing or wrong.404No such ticket409The request is closed.
/api/account/tickets/{number}/rating
Rate a resolved or closed request
The requester rates a resolved or closed request, 1 to 5 with an optional comment, where its queue asks (the ticket's queue has askRating); again to change it. Contacts on the ticket cannot. Staff see it on the ticket, as an internal note, and in the ratings report.
| Parameter | In | Type | |
|---|---|---|---|
number * | path | string |
{
"comment": "Quick, thanks!",
"rating": 5
}
200The ticket with its messages401Not signed in, or the CSRF token is missing or wrong.403Not the requester404No such ticket409The request is not resolved or closed.422The rating is not 1 to 5
/api/account/tickets/{number}/secrets/{secretId}/view
View the secure information in a reply, once
A staff reply may carry secure information (its message has a secret with its id and label). Only the ticket's requester, signed in, can view it, and only once: afterwards it is deleted, or kept for staff only (afterView). Contacts on the ticket, and notification links, see that it is there but cannot view it. Answers carry Cache-Control no-store.
| Parameter | In | Type | |
|---|---|---|---|
number * | path | string | |
secretId * | path | string |
200The data.{ "afterView": "delete", "data": "c0rrect-h0rse", "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" }401Not signed in, or the CSRF token is missing or wrong.403Not the ticket's requester.404No such ticket or secure information410Already viewed
/api/support/links/{token}
Open a request from a notification link, without signing in
Each notification link is for one address and works for 30 days, where the queue allows replying on the web without signing in. Otherwise the answer is 403 with signIn true and the ticket's number.
| Parameter | In | Type | |
|---|---|---|---|
token * | path | string |
200The ticket with its messages.403The queue needs signing in.404The link has expired.
/api/support/links/{token}/messages
Reply from a notification link
| Parameter | In | Type | |
|---|---|---|---|
token * | path | string |
201The ticket with its messages.403The queue needs signing in.404The link has expired.409The request is closed.
/api/support/links/{token}/rating
Rate a request from a notification link
As POST /api/account/tickets/{number}/rating, without signing in, through a link sent to the requester's own address (one sent to a contact gets 403), where the queue allows replying on the web without signing in.
| Parameter | In | Type | |
|---|---|---|---|
token * | path | string |
{
"rating": 4
}
200The ticket with its messages403Not the requester's link404The link has expired.409The request is not resolved or closed.422The rating is not 1 to 5