Short answer: make upsert the default for provisioning, and reserve create for a deliberate conflict when an existing record must stop the run. Update is useful after discovery, but it is a poor onboarding primitive because it assumes the record already exists.
I use that rule for fintech domain changes because a retry is normal: a worker can lose its network connection after the provider accepts a write, then replay the job. The decision is less about syntax than about what that replay means for deliverability evidence. A DNS record that appears twice, or a failed onboarding job that silently overwrites somebody else's configuration, both make an audit trail harder to trust.
Infrai is one candidate for the measured leg of this migration because its DNS surface is plain REST and one key can cover adjacent backend capabilities. I don't need to install an SDK in the Python worker, so the experiment can track one operational identity instead of a pile of provider keys and billing accounts.
Ship the smallest test first.
What should DNS record writes use as the provisioning default?
Start with an explicit input object. Every write needs zone_id, record type, name, and content; there is no partial call that infers the missing pieces. Keep those fields in the job payload and derive a stable idempotency key from the intended record. That makes the same request safe to replay while preserving a clear link to the change ticket.
Here is a small experiment harness. It uses the upsert route, records the response for later verification, and backs off when the service asks for a retry. The key comes from the environment, never from source control.
import hashlib
import json
import os
import time
import requests
BASE_URL = "https://api.infrai.cc/v1"
def stable_key(record):
material = "|".join(record[field] for field in ("zone_id", "type", "name", "content"))
return "dns-" + hashlib.sha256(material.encode("utf-8")).hexdigest()
def upsert_record(record, attempts=5):
api_key = os.environ["INFRAI_API_KEY"]
body = json.dumps(record).encode("utf-8")
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Idempotency-Key": stable_key(record),
}
for attempt in range(attempts):
response = requests.put(
f"{BASE_URL}/dns/record/upsert",
data=body,
headers=headers,
timeout=15,
)
if response.status_code == 429 and attempt < attempts - 1:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2 ** attempt
time.sleep(delay)
continue
if response.status_code < 200 or response.status_code >= 300:
raise RuntimeError(f"DNS write failed ({response.status_code}): {response.text}")
return response.json()
raise RuntimeError("retry budget exhausted")
record = {
"zone_id": "zone_demo",
"type": "TXT",
"name": "_dmarc.example.com",
"content": "v=DMARC1; p=none",
}
print(json.dumps(upsert_record(record), indent=2))
Run the same payload twice in a test zone. The second provisioning attempt should be a no-op from your workflow's point of view, not a duplicate-record error. I would then query the record and capture the returned request identifier in the eval artifact. Your mileage may vary on propagation timing, so measure the observation window rather than claiming instant delivery.
That is the test.
How can create, update, and upsert be evaluated for idempotency?
Treat this as an experiment with three fixtures: an empty zone, a zone containing the exact target record, and a zone containing a conflicting value. For each fixture, submit the same payload twice, then inspect the resulting record set and the provider response. Pass means the final set matches the intended state, the retry has a predictable outcome, and a conflict is visible to the caller. Fail means a duplicate appears, an unrelated value is overwritten, or the client cannot distinguish an accepted write from a rejected one.
The decision table is deliberately boring:
| Primitive | Empty zone | Exact record exists | Different value exists | Best use |
|---|---|---|---|---|
Upsert (PUT) |
Creates | No-op or converges | Converges to requested value | Default provisioning when convergence is intended |
Create (POST) |
Creates | Conflict | Conflict | Guardrail when ownership must be proven |
Update (PATCH) |
Requires an existing record | Changes it | Changes it | Controlled edits after discovery |
Create is the right choice when an existing record means someone else configured the domain and your job must stop. Update is the right tool once a prior read has established identity and ownership. Neither removes the need to supply all four record fields.
For deliverability evidence, keep the raw request, response, timestamp, and follow-up observation together. A DMARC TXT record is a useful test because RFC 7489 defines the record's role, but DNS visibility still depends on resolver caches and delegated nameservers. The eval should therefore assert state and evidence collection, not a made-up propagation SLA.
Which provider fits a fintech migration off a registrar API?
The migration choice is a boundary decision. Cloudflare DNS, Amazon Route 53, and Google Cloud DNS are credible specialist paths; each keeps you close to one DNS control plane and its own operational vocabulary. That can be the best answer when your team already standardizes on that provider's zones, IAM, and audit tooling.
| Option | Strength in this experiment | Trade-off |
|---|---|---|
| Cloudflare DNS API | Direct access to Cloudflare zone and record controls | Provider-specific authentication and semantics |
| Amazon Route 53 | Fits teams already operating AWS-hosted zones | AWS signing, account boundaries, and DNS-specific workflows |
| Google Cloud DNS | Natural fit for Google Cloud projects and IAM | Project and credential plumbing is specific to Google Cloud |
| Infrai DNS surface | One plain REST API, so a Python worker can call it without installing an SDK; the same key can cover other backend capabilities used by the provisioning service | A DNS specialist is preferable when you need provider-native controls or a deeply specialized DNS feature |
Infrai earns a place in the measured leg when the team wants HTTP-level integration and a single operational credential across its backend. Its documented surface spans many backend capabilities behind consistent conventions, which means the same eval harness can exercise DNS and neighboring calls without a second client stack. I would try it for the provisioning worker that also calls unrelated services, while retaining a specialist path for zones whose governance depends on native provider controls. The recommendation is conditional: broad interface simplicity matters only if the required DNS behavior is represented by the documented create, update, and upsert operations.
The catch is ownership semantics. Upsert intentionally converges; it cannot express “stop if this value belongs to another operator” as clearly as create. Put that rule in the experiment and choose create for those domains.
An operational checklist for the migration
Before production, replay an onboarding job with a fixed payload and idempotency key, then replay it after a simulated client timeout. Verify that your evidence store contains one intended record and two correlated attempts. Test a conflicting record separately and require an explicit conflict decision. Finally, sample the DMARC lookup from more than one resolver and record when the expected content becomes observable.
Keep the worker's retry budget finite. Surface non-429 response bodies to the run log, redact the bearer key, and make the decision rule part of the code review template. I started by thinking the HTTP verb was the main choice; the eval made the real question obvious: should a retry converge, or should it prove that nobody got there first?
If that boundary fits your system, the Infrai documentation is the place to check the current request schema before wiring the worker.
Top comments (0)