PREVIEW - design review build. Changes are disabled here; the live panel is dns.moklav.com.
Moklav GlobalMoklav Global

MOKLAV API guide (DNS + Hosting)

Base URL: https://dns.moklav.com/api/v1 OpenAPI 3 spec: https://dns.moklav.com/api/v1/openapi.json (interactive docs: https://dns.moklav.com/docs) This guide is written for both humans and AI agents. Everything the web panel can do, the API can do.

1. Authentication

Create a key at https://dns.moklav.com/account/api (shown once). Send it on every request:

Authorization: Bearer mk_xxxxxxxxxxxxxxxxxxxxxxxx

Scopes:

  • dns: manage the DNS zones selected when the key was created.
  • hosting:read: read hosting accounts, sites, mail, databases, backups, jobs.
  • hosting:write: everything in hosting:read plus create/change/delete.
  • A "read-only" key never changes anything.

Check your key with GET /me.

2. Response format

Success: hosting endpoints return {"data": ...}. List endpoints return {"data": [...], "pagination": {"limit": 50, "next_cursor": "NTA=", "total": 120}}. (The original DNS endpoints /zones... return plain JSON objects/arrays, unchanged for compatibility.)

Errors always look like this (HTTP status mirrors error.status):

{"error": {"code": "not_found", "message": "Hosting account not found", "status": 404,
           "details": null, "request_id": "req_4f..."}, "detail": "Hosting account not found"}

Error codes: bad_request, unauthenticated (401), forbidden, insufficient_scope, domain_not_owned, plan_limit (403), not_found (404), conflict, already_exists, account_not_ready, account_suspended, ssl_not_issued, too_many_pending, request_in_progress, limit_reached (409), validation_error, confirmation_required, idempotency_key_reused (422), rate_limited (429), internal_error (500), upstream_error (502), unavailable, no_capacity (503), upstream_timeout (504). Validation errors list each field in error.details.errors[] as {field, message, type}. Every response carries X-Request-Id; quote it when contacting support. You may send your own X-Request-Id.

3. Rate limits

300 requests per minute per API key. Headers on every authenticated response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (unix time). On 429 wait Retry-After seconds.

4. Idempotency (safe retries)

Send Idempotency-Key: <unique string> on any POST/PUT/PATCH/DELETE. Repeating the same request with the same key within 24 hours returns the original response (header Idempotent-Replayed: true) instead of doing the work twice. Same key with a different body -> 422 idempotency_key_reused; while the first is still running -> 409 request_in_progress. Agents: ALWAYS set an Idempotency-Key on writes, and retry on network errors, 429, 502, 503, 504 with backoff.

5. Pagination

List endpoints accept limit (1-200, default 50) and cursor. Pass pagination.next_cursor as cursor until it is null.

6. Async jobs

Slow operations (create account, add site, SSL, PHP switch, app install, mail domain, backup, restore, plan change, suspend, delete) return 202 Accepted with a job:

{"data": {"id": "job_ab12...", "type": "site.create", "status": "queued", "result": null, "error": null,
          "links": {"self": "/api/v1/jobs/job_ab12..."}}}

Poll GET /jobs/{id} every 2-5 s until status is succeeded or failed. Results (e.g. DNS records, WordPress admin password) are in result; failures in error using the same codes as above. GET /jobs?status=running lists jobs.

7. Webhooks

POST /webhooks {"url": "https://example.com/hook", "events": ["job.succeeded","job.failed"]} returns a secret once. Events: job.succeeded, job.failed, order.created, order.approved, order.rejected, account.suspended, account.unsuspended, account.deleted, or *. Each delivery is a POST with JSON {"id","event","created_at","data"} and headers X-Moklav-Event, X-Moklav-Timestamp, X-Moklav-Signature: v1=<hex> where hex = HMAC-SHA256(secret, timestamp + "." + raw_body). Reject if the timestamp is older than 5 minutes. Up to 3 attempts. URL must be https on a public address.

8. Hosting workflow (step by step)

Plans (monthly, tax included, no contract, no setup fee). Plan ids are stable; display names may change:

idnameJPY/monthlimits
nanoShinobi 忍11010 GB, 1 website, 10 mailboxes, 1 DB
liteRonin 浪人22030 GB, 5 websites, unlimited mail, 5 DB, WordPress, daily backup
standardSamurai 侍440100 GB, 20 websites, SSH, 7-day backups
proShogun 将軍880200 GB, unlimited websites, SSH, 14-day backups
  • GET /hosting/plans (no auth) to see plans and prices.
  • POST /hosting/orders {"plan":"lite"} -> order pending. Billing is currently off, so staff approve orders; approval creates the account (account.create job). Watch GET /hosting/orders or the order.approved webhook. Max 3 pending orders per customer (409 too_many_pending, error.details.pending_orders lists them); cancel with POST /hosting/orders/{id}/cancel.
  • GET /hosting/accounts -> your account id (e.g. 12) once status is active.
  • POST /hosting/accounts/12/sites {"domain":"example.com","php":"8.3"} -> job. If example.com is a verified zone in your MOKLAV DNS, A/AAAA records for it and www are created automatically and Let's Encrypt SSL is issued. Otherwise the job result lists dns_records to create at your DNS provider; then POST /hosting/accounts/12/sites/example.com/ssl.
  • POST /hosting/accounts/12/sites/example.com/wordpress {"admin_email":"you@example.com"} -> job; result has admin_url, admin_user, admin_password (shown once). Other apps: GET /hosting/apps, then POST /hosting/accounts/12/sites/{domain}/apps {"app":"Joomla","options":{...}}.
  • Mail: POST /hosting/accounts/12/mail/domains {"domain":"example.com"} (MX, SPF, DKIM, DMARC set automatically for MOKLAV DNS zones; else see GET .../mail/domains/{domain}/dns), then POST .../mail/domains/example.com/mailboxes {"local_part":"info"} -> generated password + IMAP/SMTP/webmail settings.
  • Databases POST .../databases {"name":"shop"}; FTP users POST .../ftp; cron POST .../cron; SSH/SFTP keys POST .../ssh-keys; PHP PUT .../sites/{domain}/php {"php":"8.2"}.
  • Backups: GET .../backups, POST .../backups (now), POST .../backups/restore {"backup":"<name>"}.
  • Usage: GET /hosting/accounts/12 (live usage vs limits) and GET .../stats (monthly history).
  • Delete: destructive calls require confirmation: DELETE .../sites/{domain}?confirm={domain},
`DELETE /hosting/accounts/12 {"confirm":"mk000012"}`.

9. DNS workflow

GET /zones, GET /zones/{zone}/records, POST /zones/{zone}/records {"name":"www","type":"A","content":"192.0.2.1","ttl":300}, PUT /zones/{zone}/rrset (replace all values), DELETE /zones/{zone}/records, GET /zones/{zone}/export, GET /zones/{zone}/dnssec, ACME DNS-01 helpers POST /acme/present and /acme/cleanup. Changes are live on ns1/ns2.moklav.com within seconds.

10. Examples

curl:

export K=mk_your_key
curl -s https://dns.moklav.com/api/v1/hosting/plans
curl -s -X POST https://dns.moklav.com/api/v1/hosting/accounts/12/sites \
  -H "Authorization: Bearer $K" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" -d '{"domain":"example.com"}'
curl -s https://dns.moklav.com/api/v1/jobs/job_xxx -H "Authorization: Bearer $K"

Python:

import time, uuid, httpx
API = "https://dns.moklav.com/api/v1"
c = httpx.Client(headers={"Authorization": "Bearer mk_your_key"}, timeout=60)

def call(method, path, **kw):
    for attempt in range(5):
        h = {"Idempotency-Key": kw.pop("idem", None) or str(uuid.uuid4())} if method != "GET" else {}
        r = c.request(method, API + path, headers=h, **kw)
        if r.status_code in (429, 502, 503, 504):
            time.sleep(int(r.headers.get("Retry-After", 2 ** attempt))); continue
        if r.status_code >= 400: raise RuntimeError(r.json()["error"])
        return r.json()
    raise RuntimeError("gave up")

def wait(job):
    while job["status"] in ("queued", "running"):
        time.sleep(3); job = call("GET", f"/jobs/{job['id']}")["data"]
    if job["status"] == "failed": raise RuntimeError(job["error"])
    return job["result"]

acct = call("GET", "/hosting/accounts")["data"][0]["id"]
print(wait(call("POST", f"/hosting/accounts/{acct}/sites", json={"domain": "example.com"})["data"]))

11. Rules for AI agents

  • Read GET /me first to learn scopes. Never guess ids; list first.
  • Always send Idempotency-Key on writes; reuse the same key when retrying the same intent.
  • For 202 responses, poll the job; do not repeat the original request.
  • Secrets (passwords, webhook secrets, WordPress admin password) are returned once; store them securely, never log them.
  • Destructive operations need an explicit confirm value; ask your human before deleting.