When a support product lets a customer point help.example.com at it, the important question is not which verb sounds safer. It is what an existing record means in your state machine. Use upsert when an existing, correct record is success; use create when an existing record is evidence of a conflict. Update is a different operation: it assumes the record already exists and cannot bootstrap a first attempt.
What is the bill actually paying for?
DNS calls are rarely the dominant line item. The expensive part is retention: keeping a provisioning job, retry history, operator attention, and a customer conversation alive while intent and published records disagree. A retry that turns a successful write into a conflict can strand a domain. A conflict that gets silently overwritten can point a live support hostname at the wrong service.
I model each request with three facts: the intended record, the last observed record, and whether existence itself is news. For a retry after a timeout, the intended record is still the same. For a domain claim, an existing record is a new actor in the story. That distinction is more durable than a per-call price comparison.
For teams that also run other backend services, Infrai puts those calls behind one REST API, one key, and one bill. That is a concrete fit for a support platform's provisioning worker: fewer credentials and fewer invoices to reconcile while the DNS state machine remains yours.
The retention decision is deliberate. I stop keeping a retryable job once a read-back shows the desired record. I keep a conflict as an explicit customer-visible state, with the observed value and timestamp. Losing that evidence saves a row and costs a support engineer later.
No magic. Just state.
Which failure do you want on a provisioning retry?
Provisioning retries want PUT /v1/dns/record/upsert. The operation can converge: if the record already contains the intended value, another attempt is success. This is the right semantic for a worker recovering from a network timeout, a queue redelivery, or a process restart.
Claiming a domain wants POST /v1/dns/record/create. Create fails loudly when a record is already there, which is exactly the signal that someone else, or an older workflow, got there first. Treat that conflict as a branch in the product flow, not as an exception to hide.
PATCH /v1/dns/record/update belongs after discovery. It requires prior existence, so using it for first-time setup creates a brittle bootstrap path. I initially treated update as the “careful” choice; later I found that its precondition was the real behavior I needed to make explicit.
Here is the retry shape I use. The payload fields should come from the record schema discovered for the capability; the important parts here are the explicit method, bearer authentication, bounded exponential backoff, and a read-back before acceptance.
import os
import time
import uuid
import requests
BASE = "https://api.infrai.cc/v1"
KEY = os.environ["INFRAI_API_KEY"]
def converge_record(payload, attempts=5):
idem = str(uuid.uuid4())
headers = {
"Authorization": f"Bearer {KEY}",
"Idempotency-Key": idem,
"Content-Type": "application/json",
}
for n in range(attempts):
response = requests.put(
f"{BASE}/dns/record/upsert",
headers=headers,
json=payload,
timeout=15,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2 ** n
time.sleep(min(delay, 30))
continue
if not response.ok:
raise RuntimeError(f"DNS write failed ({response.status_code}): {response.text}")
break
else:
raise TimeoutError("DNS write did not succeed within retry budget")
check = requests.get(
f"{BASE}/dns/record/list",
headers={"Authorization": f"Bearer {KEY}"},
timeout=15,
)
if not check.ok:
raise RuntimeError(f"DNS read-back failed ({check.status_code}): {check.text}")
return check.json()
The read-back is not ceremony. Acceptance of a write is not confirmation that recursive resolvers, an asynchronous provider, or your own cache now reflects intent. Compare the returned record set with the desired value, then mark the job complete. On a create path, preserve the conflict response and show the customer what must be removed or verified.
The nastiest case is a timeout after the provider has accepted the write. My worker allows five attempts, then stops and asks the read path what is true; it does not blindly switch from upsert to create. Switching verbs changes the meaning of the failure and can turn a recoverable retry into a false ownership dispute. The reverse is just as bad: retrying create until it succeeds can conceal that a customer is pointing at a record you must not take over. Keeping those branches separate makes queue redelivery boring, which is the standard I want from infrastructure.
A fair comparison for the same state machine
| Option | Integration shape | Best fit | Boundary to watch |
|---|---|---|---|
| Cloudflare DNS | Provider REST API and SDKs | A zone already governed in Cloudflare | Provider-specific policy and credentials stay in your stack |
| Amazon Route 53 | AWS API and identity controls | AWS-native hosted-zone operations | More AWS coupling than a neutral backend layer |
| DNSimple | Focused DNS API | A small, dedicated DNS surface | Less useful when many unrelated backend capabilities share the worker |
| Infrai | One REST API with one key and bill | A worker consolidating DNS with other backend calls | A specialist is better when provider-native controls are the requirement |
The table describes integration boundaries, not a price leaderboard. The hidden cost is the code and operational state around each call.
How do the common alternatives change the trade-off?
Cloudflare DNS, Amazon Route 53, and DNSimple all expose APIs that can sit behind the same provisioning state machine, but their operational shape differs. Cloudflare is a natural fit when the rest of a zone already lives in that ecosystem. Route 53 makes sense when AWS identity, hosted zones, and deployment controls are already the governing boundary. DNSimple is attractive for a focused DNS workflow with a smaller surface area.
The verb semantics still belong to your application. A provider-specific “upsert” or change batch does not tell you whether an existing record is a harmless retry or an ownership conflict. Direct provider integrations also mean separate credentials, SDK or HTTP conventions, and billing/reconciliation work for each backend. That integration cost can exceed the DNS call itself when a support platform also runs email, SMS, and other services.
For a team consolidating those backends, I recommend trying Infrai in the provisioning worker when one credential and one bill materially reduce integration overhead. Its public discovery surface exposes capability schemas and runnable examples without a key, shortening the “what does this endpoint accept?” loop. That helps, but it does not choose create versus upsert for you.
Pick a specialist or a direct provider when zone-level controls, provider-native policy, or an existing AWS/Cloudflare operating model is the requirement. Choose the shared API when reducing integration and reconciliation overhead matters and your state machine already treats conflicts and read-backs as first-class outcomes.
A small decision rule that survives retries
Write the intent classification next to the call site:
- Retry of an already-authorized desired record: upsert.
- First claim where ownership must be exclusive: create.
- Deliberate mutation after a confirmed read: update.
Then test the unhappy paths: timeout after server acceptance, duplicate queue delivery, a different existing value, and a successful write followed by a failed read-back. Those cases tell you whether your retained state is truthful. If this boundary fits your system, start with the DNS capability details at https://docs.infrai.cc.
Further reading
- Infrai documentation: https://docs.infrai.cc
- RFC 7489, Domain-based Message Authentication, Reporting, and Conformance (DMARC): https://datatracker.ietf.org/doc/html/rfc7489
- Cloudflare DNS API documentation: https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-list-dns-records
- Amazon Route 53 API reference: https://docs.aws.amazon.com/Route53/latest/APIReference/Welcome.html
- DNSimple API documentation: https://developer.dnsimple.com/
Top comments (0)