What an app can do
An installed app can manage DNS in two ways, depending on where the domain was bought:- Domains registered by Salesive (
provider: "dynadot"): read the zone and add, change or remove individual records. - Custom domains bought elsewhere (
provider: null): file a record request that the merchant applies at their own registrar. Salesive verifies it and notifies your app.
WRITE_DOMAINS is a different runtime permission. It covers adding and removing domains and
changing the store subdomain. It does not allow DNS writes. The full zone replace
(PUT /domains/:id/dns) is refused for app tokens even with every permission granted.Request the WRITE_DNS permission
WRITE_DNS is a runtime permission, not an OAuth scope. Your app asks for it from inside its
iframe, the merchant sees a dashboard modal, and the grant is stored on the installation for a
duration the merchant chooses: once, 1day, 7days, 14days or 31days.
A WRITE_DNS grant is always scoped to one domain, so a bug or a bad actor cannot touch
another zone in the store. You can also limit it to specific record types, and give the merchant
a reason for the request.
Through the App Bridge
requestPermission calls
are unchanged.
Raw postMessage
If you do not use the helper package, send the same fields to the dashboard:salesive:permission-response message carrying permission,
granted, mode and expiresAt.
Grants die with the installation. Reinstalling the app starts with no DNS access until the
merchant approves a new grant. The merchant can revoke a grant at any time from the dashboard.
Endpoints
All routes are underhttps://api.salesive.com/api/v1/domains. Your app token is bound to one
store, so you never send a shop id.
List records
GET /domains/:id/dns/records is the read to use before a write. Each record carries:
id: the deterministic record id, used inPATCHandDELETE.owner:app,merchantorplatform.installationId: the installation that owns an app-created record, otherwisenull.protected:truewhen the record cannot be changed by an app.
GET /domains/:id/dns returns the same zone in the dashboard’s older shape
({ domain, ttl, records: [{ id: "main-0", host, type, value, value2 }] }). Its ids are
positional, not stable, so use /dns/records when you intend to write.
Add a record
@ for the root record, or the subdomain label only (send, not send.acme.com). MX
records carry their priority in value2 as a numeric string:
Change a record
PATCH /domains/:id/dns/records/:recordId replaces the whole record, so send host, type,
value and value2 every time. A PATCH is not a partial update.
id in the response for any follow-up write, or list the zone again.
Remove a record
How recordId is built
Dynadot has no record ids, so Salesive derives one:host and type are lowercased and values are trimmed before hashing. The same record always
has the same id, so your app can address a record straight from a list response without a second
lookup. Because content is part of the hash, an edit produces a new id.
Protected records and ownership
Apps own only the records they created. Ownership is stored next to the zone, keyed by the deterministic record id, and it is checked before every write.
The root
A and AAAA records point the domain at the storefront. A record carrying the
storefront address is what makes the store resolve, so no app may set or remove it. Nameservers,
transfer lock, renewal and domain deletion are never reachable with an app token.
protected: true on a list response is advisory: it tells your UI which records cannot be
edited, and the same checks run again on the write. A record can be protected because of its
type or value even when no app owns it.Limits
Writes to a domain are serialised so two requests cannot read, change and write the same zone at
once. If another write holds the domain lock, the request fails with
409 DNS_BUSY and can be
retried after a moment.
Error codes
DNS errors return the message and a top levelcode:
A missing runtime grant answers with the code nested under
data:
403 and a message naming the missing scope.
Custom domains bought elsewhere
Salesive cannot write DNS at a registrar it does not control. For a custom domain connected to the store, your app files a request instead. The merchant copies the values into their own registrar, and a recurring job resolves the record until it matches.1
Find the domain id
Call
GET /domains with READ_DOMAINS and pick the custom domain. The id is the _id of
the domain, not the domain name.2
Request WRITE_DNS for that domain
Send the App Bridge request with
scope.domainId and, if you know them, the record types you
will need.3
File the record request
POST /domains/:id/dns/requests with the host, type, value, optional TTL and a short purpose.4
Merchant applies it
The domain page lists pending requests with copy and paste values and a check now button.
The merchant adds the record at their registrar.
5
Salesive verifies it
The verifier runs every five minutes, resolves the record, and marks the request
verified.
Your app receives a domains/dns_verified webhook.A and AAAA, the storefront address, NS
and SOA are refused. Filing the same pending request twice returns the existing request instead
of creating a duplicate.
GET /domains/:id/dns/requests lists requests for the domain. An app token sees only the
requests filed by its own installation. The verifier checks A, AAAA, CNAME, MX, TXT,
NS and SRV records and gives up after 120 checks, which is about ten hours at the five
minute cadence. A request that never matches is marked rejected, with the reason in
lastError.
Webhooks
DNS changes send their own topics instead of the genericdomains/updated, so your app can tell
a record write apart from a domain being added or removed. Every delivery needs READ_DOMAINS,
and like all Salesive webhooks it carries identifiers only. Re-fetch the zone with
GET /domains/:id/dns/records.
domains/dns_verified adds the request id and the record that was verified:
Audit trail
Every DNS write is recorded, whether it came from an app or the merchant. The entry stores the installation, the actor, the action, the record id, and the before and after snapshots.GET /domains/:id/dns/changes returns entries newest first. limit defaults to 50 and is capped
at 200. Reverting is a merchant action (POST /domains/:id/dns/changes/:changeId/revert), because
it can restore a record the app does not own.
Next steps
Scopes & permissions
The OAuth scopes and runtime permissions an app can request.
App Bridge
Request runtime permissions and handle lifecycle events.
Webhooks
Signature verification, retries and the full topic list.
List domains
Find the domain id before requesting a grant.

