Every API here is available over the APIs.io API and to AI agents over MCP.
{
"openapi": "3.1.0",
"info": {
"title": "DNSMint API",
"version": "1.0.0",
"description": "DNSMint turns an IP address into a stable hostname on a domain we operate, served by our authoritative DNS. Register an IP with one call, point your ACME client at the hostname, and update the IP whenever it changes while the name and certificate stay the same.",
"contact": {
"email": "hello@dnsmint.com",
"url": "https://dnsmint.com/quickstart"
}
},
"servers": [
{
"url": "https://dnsmint.com/api"
}
],
"security": [
{
"bearerAuth": []
}
],
"tags": [
{
"name": "hostnames",
"description": "Register, read, update, and release hostnames."
}
],
"paths": {
"/v1/hostnames": {
"post": {
"operationId": "createHostname",
"tags": [
"hostnames"
],
"summary": "Register an IP and mint a hostname",
"description": "Registers an IPv4 or IPv6 address and returns a new hostname. The subdomain is opaque unless you pass one, and lands on a domain assigned to your account. An account holding more than one may choose which. Public, private, and reserved addresses on every plan; the address a hostname is minted with fixes its address class for life. New registrations may briefly return \"pending\"; poll GET until \"live\". Request bodies are capped at 4KB. Plans that include custom subdomains may pass one. Any account may pass an optional dedicated domain, which must be one it already holds.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateHostnameRequest"
},
"example": {
"ip": "34.120.50.10"
}
}
}
},
"responses": {
"201": {
"description": "Hostname created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HostView"
},
"example": {
"id": "host_01937d3e8f217c4ab8e24f9a1c3d5e6b",
"hostname": "q7k4m2.a3f9c1-d4e7b8.dev",
"status": "live",
"certificate": "self",
"created_at": "2026-08-26T08:30:00.000Z"
}
}
}
},
"400": {
"description": "Invalid JSON, missing both \"ip\" and \"target\" or sending both, syntactically invalid IP address, invalid subdomain or domain, or body over 4KB.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "Not a valid IP address: 34.120.50",
"code": "BAD_REQUEST"
}
}
},
"x-error-codes": [
{
"code": "BAD_REQUEST",
"when": "The body is not valid JSON, is over 4KB, is missing both \"ip\" and \"target\" or sending both, or the address does not parse"
}
]
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"402": {
"description": "No domain to mint on yet. Either signup is unfinished (no card on file), or the team's domain is still being set up: then the error says so and Retry-After says when to ask again, and the team owner is emailed when it is ready.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "This key does not carry the scope this call needs",
"code": "FORBIDDEN"
}
}
},
"x-error-codes": [
{
"code": "PAYMENT_REQUIRED",
"when": "No domain to mint on yet: signup is unfinished, or the domain is still being set up (then with Retry-After)"
}
]
},
"403": {
"description": "The API key does not carry the scope this call needs, or its grant does not reach the hostname being minted.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "This key does not carry the scope this call needs",
"code": "FORBIDDEN"
}
}
},
"x-error-codes": [
{
"code": "FORBIDDEN",
"when": "The API key does not carry the scope this call needs, or its grant does not reach the hostname being minted"
}
]
},
"409": {
"description": "Told apart by the error code. CONFLICT: the requested subdomain is taken or retired on this domain. DOMAIN_PROVISIONING: a domain for this team is still being set up; carries Retry-After and a Location pointing at the provisioning status. NO_DOMAIN_AVAILABLE: no domain is assigned yet; it is being set up and the team owner is emailed when it is ready, so a timed retry will not help.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "Subdomain is taken or retired on this domain",
"code": "CONFLICT"
}
}
},
"x-error-codes": [
{
"code": "CONFLICT",
"when": "The requested subdomain is taken or retired on this domain"
},
{
"code": "DOMAIN_PROVISIONING",
"when": "A domain for this team is still being set up. Carries Retry-After and a Location pointing at the provisioning status"
},
{
"code": "NO_DOMAIN_AVAILABLE",
"when": "No domain is assigned to this team yet. It is being set up and the team owner is emailed when it is ready; a timed retry will not help"
}
]
},
"429": {
"description": "The account is at its hostname cap, or has made more than 30 registrations in a minute.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "Hostname cap reached (5). Release unused names or upgrade.",
"code": "RATE_LIMITED"
}
}
},
"x-error-codes": [
{
"code": "RATE_LIMITED",
"when": "The account is at its active-hostname cap, or has made more than 30 registrations in a minute"
}
]
},
"500": {
"$ref": "#/components/responses/Internal"
},
"501": {
"description": "A valid request this deployment cannot serve. Returned for certificate: \"managed\" where no certificate encryption key is configured. Retry later; do not change the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "certificate: \"managed\" is unavailable on this deployment: no certificate encryption key is configured. Use \"self\" and run an ACME client against the DNS-01 API; see https://dnsmint.com/quickstart",
"code": "NOT_IMPLEMENTED"
}
}
},
"x-error-codes": [
{
"code": "NOT_IMPLEMENTED",
"when": "certificate: \"managed\" was requested and this deployment has no certificate encryption key configured"
}
]
}
},
"x-docs": {
"anchor": "create-hostname",
"group": "hostnames",
"displayPath": "POST /api/v1/hostnames",
"summary": "Registers an IP address and mints a hostname. The subdomain is opaque unless you pass one, and lands on a domain assigned to your account.",
"params": [
{
"name": "ip",
"where": "body, required unless target is sent",
"description": "An IPv4 or IPv6 address as a string. The record type follows the address: A for IPv4, AAAA for IPv6. Public, private, and reserved ranges are all accepted, on every plan. The address you mint with fixes the hostname's address class for its lifetime - see Address classes."
},
{
"name": "target",
"where": "body, required unless ip is sent",
"description": "A platform endpoint to follow instead of an address, for a platform that gives you a URL and no IP. We resolve it and publish A or AAAA records, then follow it within the target's TTL. Send either ip or target, not both. What it answers fixes the address class, the same as an ip does."
},
{
"name": "subdomain",
"where": "body, optional",
"description": "A custom subdomain, on every plan. 3-63 lowercase letters, digits, and hyphens; no periods; no leading or trailing hyphen."
},
{
"name": "domain",
"where": "body, optional",
"description": "Which of your dedicated domains to mint on. Must already be assigned to the team. Omitted, we pick one."
},
{
"name": "certificate",
"where": "body, optional",
"description": "Who holds the private key, and with it who runs the ACME client and who talks to the CA. `self` (default) means you do all three: run an ACME client against the DNS-01 API, choose your own CA, and we never see key material. `managed` means we do all three and renew before expiry on a fresh key each time, and you pull the current pair from `GET /v1/hostnames/{id}/certificate`. See Certificate modes."
},
{
"name": "ca",
"where": "body, optional",
"description": "Which certificate authority issues, for `certificate: \"managed\"` and `certificate: \"csr\"`, the modes where we run the ACME client - `letsencrypt` (default) or `google`. Sending it with `self` is a 400: there you run the client, so the CA is a choice you make in it. Omitted, we pick Let's Encrypt unless it has recently rate-limited orders for your domain. Renewals always return to the CA that issued. See https://dnsmint.com/ca"
}
],
"curl": "curl -X POST https://dnsmint.com/api/v1/hostnames \\\n -H \"Authorization: Bearer $DNSMINT_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"ip\": \"34.120.50.10\"}'",
"responseIntro": "201 with the new hostname:",
"response": "{\n \"id\": \"host_01937d3e8f217c4ab8e24f9a1c3d5e6b\",\n \"hostname\": \"q7k4m2.a3f9c1-d4e7b8.dev\",\n \"status\": \"pending\",\n \"certificate\": \"self\",\n \"created_at\": \"2026-08-26T08:30:00.000Z\"\n}",
"notes": [
"The record is served as soon as it is written. The status reads \"pending\" until every nameserver has been observed answering for it, then \"live\"; poll GET until then.",
"Any account may pass subdomain, and may pass domain, which must be one already assigned to it; domain is only a choice once the team holds more than one.",
"Every plan has its own cap on how many hostnames an account holds at once; releasing one frees its slot. Registration past the cap returns 429, as does more than 30 registrations a minute."
]
}
},
"get": {
"operationId": "listHostnames",
"tags": [
"hostnames"
],
"summary": "List this team's hostnames",
"description": "Lists the team's hostnames, newest first. Released and terminated hostnames are excluded; suspended ones are shown.",
"parameters": [
{
"name": "limit",
"in": "query",
"required": false,
"description": "Maximum number of hostnames to return. Values are clamped to the 1 to 500 range.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 500,
"default": 100
}
},
{
"name": "skip",
"in": "query",
"required": false,
"description": "Number of hostnames to skip, for pagination. Minimum 0.",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "The account's hostnames and list metadata.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HostnameList"
},
"example": {
"hostnames": [
{
"id": "host_01937d3e8f217c4ab8e24f9a1c3d5e6b",
"hostname": "q7k4m2.a3f9c1-d4e7b8.dev",
"status": "live",
"certificate": "self",
"created_at": "2026-08-26T08:30:00.000Z"
}
],
"total": 1,
"active": 1,
"cap": 5,
"limit": 100,
"skip": 0
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/Internal"
}
},
"x-docs": {
"anchor": "list-hostnames",
"group": "hostnames",
"displayPath": "GET /api/v1/hostnames",
"summary": "Lists the team's hostnames, newest first. Released and terminated hostnames are excluded; suspended ones are shown, so a team can see what has been actioned.",
"params": [
{
"name": "limit",
"where": "query, optional",
"description": "Maximum results to return. Default 100; values are clamped to the 1 to 500 range."
},
{
"name": "skip",
"where": "query, optional",
"description": "Results to skip, for pagination. Default 0."
}
],
"curl": "curl \"https://dnsmint.com/api/v1/hostnames?limit=100&skip=0\" \\\n -H \"Authorization: Bearer $DNSMINT_KEY\"",
"responseIntro": "200 with a hostnames envelope:",
"response": "{\n \"hostnames\": [\n {\n \"id\": \"host_01937d3e8f217c4ab8e24f9a1c3d5e6b\",\n \"hostname\": \"q7k4m2.a3f9c1-d4e7b8.dev\",\n \"status\": \"live\",\n \"certificate\": \"self\",\n \"created_at\": \"2026-08-26T08:30:00.000Z\"\n }\n ],\n \"total\": 1,\n \"active\": 1,\n \"cap\": 5,\n \"limit\": 100,\n \"skip\": 0\n}",
"notes": [
"Released and terminated hostnames never appear.",
"total is the non-released count. active is live hostnames, which is what the create cap counts. cap is the plan's maxActiveHostnames. limit and skip are the values actually applied after clamping.",
"Another page exists when skip + limit < total."
]
}
}
},
"/v1/hostnames/{id}": {
"parameters": [
{
"$ref": "#/components/parameters/HostnameId"
}
],
"get": {
"operationId": "getHostname",
"tags": [
"hostnames"
],
"summary": "Read one hostname",
"description": "Returns one hostname. New registrations may briefly return \"pending\"; poll GET until \"live\".",
"responses": {
"200": {
"description": "The hostname.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HostView"
},
"example": {
"id": "host_01937d3e8f217c4ab8e24f9a1c3d5e6b",
"hostname": "q7k4m2.a3f9c1-d4e7b8.dev",
"status": "live",
"certificate": "self",
"created_at": "2026-08-26T08:30:00.000Z"
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"500": {
"$ref": "#/components/responses/Internal"
}
},
"x-docs": {
"anchor": "get-hostname",
"group": "hostnames",
"displayPath": "GET /api/v1/hostnames/{id}",
"summary": "Reads one hostname by id.",
"params": [
{
"name": "id",
"where": "path, required",
"description": "The hostname id returned at creation."
}
],
"curl": "curl https://dnsmint.com/api/v1/hostnames/host_01937d3e8f217c4ab8e24f9a1c3d5e6b \\\n -H \"Authorization: Bearer $DNSMINT_KEY\"",
"responseIntro": "200 with the hostname:",
"response": "{\n \"id\": \"host_01937d3e8f217c4ab8e24f9a1c3d5e6b\",\n \"hostname\": \"q7k4m2.a3f9c1-d4e7b8.dev\",\n \"status\": \"live\",\n \"certificate\": \"self\",\n \"created_at\": \"2026-08-26T08:30:00.000Z\"\n}",
"notes": [
"New registrations may briefly return \"pending\"; poll GET until \"live\"."
]
}
},
"put": {
"operationId": "updateHostnameIp",
"tags": [
"hostnames"
],
"summary": "Set the hostname IP",
"description": "Puts an IP on the hostname. If the address is unchanged, DNS is not rewritten. If it changed, the record is replaced and the name comes back live. Request bodies are capped at 4KB. A hostname keeps the address class it was minted with. Repointing within a class is free, including between A and AAAA; crossing from public to private or private to public returns 409. This blocks DNS rebinding, where one origin is public first, to deliver a payload under a trusted certificate, and private second, to read a response from inside the visitor's network. Release the hostname and mint a new one for the other class.\n\nSend `target` instead of `ip` to follow a platform endpoint that publishes no address of its own. We resolve it and serve A or AAAA, so the hostname keeps its certificates, its CAA and every child record.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RegisterIpRequest"
},
"example": {
"ip": "34.120.51.22"
}
}
}
},
"responses": {
"200": {
"description": "The updated hostname.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HostView"
},
"example": {
"id": "host_01937d3e8f217c4ab8e24f9a1c3d5e6b",
"hostname": "q7k4m2.a3f9c1-d4e7b8.dev",
"status": "live",
"certificate": "self",
"created_at": "2026-08-26T08:30:00.000Z"
}
}
}
},
"400": {
"description": "Invalid JSON, missing both \"ip\" and \"target\" or sending both, syntactically invalid IP address, or body over 4KB.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "Body must include an \"ip\" or a \"target\" string",
"code": "BAD_REQUEST"
}
}
},
"x-error-codes": [
{
"code": "BAD_REQUEST",
"when": "Invalid JSON, missing both \"ip\" and \"target\" or sending both, syntactically invalid IP address, or body over 4KB"
}
]
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"description": "The hostname is suspended pending an abuse review, or the API key does not carry the scope this call needs, or its grant does not reach this hostname.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "Hostname is suspended pending review",
"code": "FORBIDDEN"
}
}
},
"x-error-codes": [
{
"code": "FORBIDDEN",
"when": "The hostname is suspended pending an abuse review, or the API key does not carry the scope this call needs, or its grant does not reach this hostname"
}
]
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"409": {
"description": "Either the hostname was released or terminated, neither of which can be updated, or the new address is a different class from the one the hostname was minted with: a public hostname cannot become private, or the reverse.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "This hostname is pinned to public addresses and cannot be repointed to a private one. Release it and mint a new hostname for the private address.",
"code": "CONFLICT"
}
}
},
"x-error-codes": [
{
"code": "CONFLICT",
"when": "Either the hostname was released or terminated, neither of which can be updated, or the new address is a different class from the one the hostname was minted with: a public hostname cannot become private, or the reverse"
}
]
},
"429": {
"description": "More than 60 updates in a minute in this team.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"x-error-codes": [
{
"code": "RATE_LIMITED",
"when": "More than 60 updates in a minute in this team"
}
]
},
"500": {
"$ref": "#/components/responses/Internal"
}
},
"x-docs": {
"anchor": "update-hostname-ip",
"group": "hostnames",
"displayPath": "PUT /api/v1/hostnames/{id}",
"summary": "Sets the hostname's IP. The same address is a no-op; a new address updates the record. The name and its certificates carry over untouched.",
"params": [
{
"name": "id",
"where": "path, required",
"description": "The hostname id returned at creation."
},
{
"name": "ip",
"where": "body, instead of target",
"description": "The IPv4 or IPv6 address, public or private. Repeating the current address is a no-op; nothing needs to be sent to keep a hostname live. A different address updates the record; the record type follows the address, so a hostname can move between A and AAAA. It cannot move between address classes - a public hostname stays public and a private one stays private, and crossing that line returns 409. See Address classes."
},
{
"name": "target",
"where": "body, instead of ip",
"description": "A platform endpoint to follow instead of an address. Send this or `ip`, never both. We resolve it and publish ordinary A and AAAA records, refreshed within the target's own TTL, so the name never carries a CNAME and every record you set on the hostname keeps working. The hostname keeps the address class it was minted with, and that is re-checked every time we refresh rather than only when you set it: a target that starts answering with a private address is refused and the hostname stays on its last public one. Setting `ip` takes the hostname back off its target."
}
],
"curl": "curl -X PUT https://dnsmint.com/api/v1/hostnames/host_01937d3e8f217c4ab8e24f9a1c3d5e6b \\\n -H \"Authorization: Bearer $DNSMINT_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"ip\": \"34.120.51.22\"}'",
"responseIntro": "200 with the updated hostname:",
"response": "{\n \"id\": \"host_01937d3e8f217c4ab8e24f9a1c3d5e6b\",\n \"hostname\": \"q7k4m2.a3f9c1-d4e7b8.dev\",\n \"status\": \"pending\",\n \"certificate\": \"self\",\n \"created_at\": \"2026-08-26T08:30:00.000Z\"\n}",
"notes": [
"If the address is unchanged, DNS is not rewritten and nothing changes. There is no keepalive to send: a hostname stays live without being touched.",
"If the address changed, the record is replaced and the hostname comes back live.",
"A released or terminated hostname cannot be updated; that returns 409. A suspended one returns 403 until the review is resolved.",
"Repointing across address classes returns 409. A hostname minted on a public address can never be moved to a private one, or the reverse; release it and mint a new hostname instead. See Address classes for why.",
"More than 60 updates a minute on one team returns 429."
]
}
},
"delete": {
"operationId": "releaseHostname",
"tags": [
"hostnames"
],
"summary": "Release a hostname",
"description": "Releases the hostname. The DNS record stops being served and any certificate we hold for it is revoked. The name is free immediately: POST the same subdomain again to take it back, which mints a new hostname with a new id. Releasing an already released hostname returns the same success response.",
"responses": {
"200": {
"description": "The hostname was released.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReleaseResult"
},
"example": {
"released": true
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"description": "The hostname is suspended pending an abuse review and cannot be released.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"x-error-codes": [
{
"code": "FORBIDDEN",
"when": "The hostname is suspended pending an abuse review and cannot be released"
}
]
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"409": {
"description": "The hostname was terminated for policy violation and cannot be released.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"x-error-codes": [
{
"code": "CONFLICT",
"when": "The hostname was terminated for policy violation and cannot be released"
}
]
},
"500": {
"$ref": "#/components/responses/Internal"
}
},
"x-docs": {
"anchor": "release-hostname",
"group": "hostnames",
"displayPath": "DELETE /api/v1/hostnames/{id}",
"summary": "Releases the hostname. The record stops being served and any certificate we hold is revoked. The subdomain returns to your account and can be minted again.",
"params": [
{
"name": "id",
"where": "path, required",
"description": "The hostname id returned at creation."
}
],
"curl": "curl -X DELETE https://dnsmint.com/api/v1/hostnames/host_01937d3e8f217c4ab8e24f9a1c3d5e6b \\\n -H \"Authorization: Bearer $DNSMINT_KEY\"",
"responseIntro": "200 with a confirmation:",
"response": "{\n \"released\": true\n}",
"notes": [
"The DNS record stops being served at once. The released hostname stays released: writes to it return 409, and minting the subdomain again creates a new hostname. Releasing an already released hostname returns the same 200.",
"A suspended or terminated hostname cannot be released. Releasing one would replace an enforcement record with a customer-initiated exit, so it returns 403 or 409 instead."
]
}
}
},
"/v1/hostnames/{id}/acme-credential": {
"post": {
"operationId": "createAcmeCredential",
"summary": "Mint an acme-dns credential scoped to this hostname",
"description": "Creates a DNS-01 publishing credential for exactly this hostname. At most 5 per hostname. The password is shown only in this response.",
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"201": {
"description": "Credential created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AcmeRegistration"
}
}
}
},
"400": {
"description": "Credential limit reached (5)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"x-error-codes": [
{
"code": "BAD_REQUEST",
"when": "Credential limit reached (5)"
}
]
},
"401": {
"description": "Missing or invalid API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"x-error-codes": [
{
"code": "UNAUTHORIZED",
"when": "Missing or invalid API key"
}
]
},
"403": {
"description": "Key lacks the dns01:write scope, or is narrowed to another hostname",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"x-error-codes": [
{
"code": "FORBIDDEN",
"when": "Key lacks the dns01:write scope, or is narrowed to another hostname"
}
# --- truncated at 32 KB (179 KB total) ---
# Full source: https://artifacts.apis.io/dnsmint-com/openapi/_original/dnsmint-com-api-openapi.json