Important update 1: Email Support is being transitioned to Webforms. Click here for more information.

Storefront API DNS Management Guide

The Storefront API lets you manage DNS records for your customers' domains programmatically without needing to log in to Storefront Manager or the customer portal. This is useful if you have your own platform or control panel and want to keep DNS in sync, automate DNS provisioning when customers sign up, or build tooling for your support team.

Note: DNS management via the API only works for domains using Storefront's default Shopco nameservers (a.ns.shopco.com, b.ns.shopco.com, c.ns.shopco.com). Domains using custom nameservers manage their own DNS outside of Storefront.


Before you begin

Requirements

  • An active OpenSRS Storefront with API access enabled
  • API credentials (Client ID and Client Secret) generated in Storefront Manager
  • Your OpenSRS account balance above $10 to keep your Storefront active
  • Domains must be using Shopco nameservers for DNS changes to take effect

Supported record types

The API supports all DNS record types available in the Storefront interface:

  • AAAA 
  • CNAME 
  • MX 
  • NS 
  • TXT 
  • SRV 
  • DS

The same validation rules that apply in the Storefront Manager and customer portal apply to the API. Records that would be rejected in the UI will be rejected via the API with the same validation errors.


Step 1: Generate your API credentials

Your API credentials are a Client ID and Client Secret pair generated from within your Storefront Manager. The Client Secret is shown only once. Store it securely before closing the window.

  1. Log in to Storefront Manager.
  2. Navigate to Settings > Advanced Settings > API.
  3. Click Create to generate a new credential pair.
  4. You'll be prompted to confirm your login credentials before the secret is generated.
  5. A popup will display your Client Secret. Copy it to a secure location immediately. It will not be shown again.
  6. Your Client ID is displayed in the table and can be copied at any time.

Managing your credentials

From the credentials table, you can:

  • Rotate your secret — generates a new Client ID and Client Secret. You can choose a grace period (1 hour, 24 hours, 3 days, or 7 days) during which the old secret remains valid, giving you time to update your integration before the old one expires.
  • Delete your credentials — immediately invalidates the Client ID and Client Secret. Any integration using these credentials will stop working.

Security note: Treat your Client Secret like a password. Do not expose it in client-side code, commit it to source control, or share it. If you suspect a secret has been compromised, rotate it immediately.


Step 2: Obtain an access token

The Storefront API uses OAuth 2.0 Client Credentials, a server-to-server flow where your application authenticates using its Client ID and Secret directly, with no user login involved.

Token endpoints

EnvironmentToken URL
Productionhttps://auth.shopco.com/oauth2/token
Testhttps://auth.test.shopco.com/oauth2/token

Requesting a token

Send a POST request to the token endpoint for your environment, passing your credentials via HTTP Basic Auth:

curl -X POST "https://auth.shopco.com/oauth2/token" \  
-H "Content-Type: application/x-www-form-urlencoded" \  
-u "<client_id>:<client_secret>" \  
-d "grant_type=client_credentials"

A successful response returns your access token:

{  
"access_token": "eyJraWQiOiJ...",  
"token_type": "Bearer",  
"expires_in": 3600 
}

The access_token is a signed JWT valid for expires_in seconds (typically 1 hour).

Using the token

Include the token in the Authorization header on every API request:

Authorization: Bearer <access_token>

Example - Production:

curl -X GET "https://api.shopco.com/v1/domain/example.com/dns_records" \  
   -H "Authorization: Bearer eyJraWQiOiJ..."

Token expiry

Tokens expire after expires_in seconds. There is no refresh token in the Client Credentials flow. Simply request a new token using the same credentials.

Recommended practice: Cache the token and re-request it proactively 30-60 seconds before expiry to avoid race conditions at the boundary.

If a request returns 401, treat it as a signal that the token has expired or been invalidated. Discard the cached token, request a new one, and retry the request once. Do not retry indefinitely.

Authentication error responses

ScenarioStatusResponse body
No Authorization header, or not Bearer scheme401{"detail": "Missing or invalid authentication scheme"}
Token is not a valid JWT401{"detail": "Invalid token: <reason>"}
Token signature invalid401{"detail": "Invalid token: <reason>"}
Token key ID not found401{"detail": "Public key not found"}
Token valid but Client ID not recognized or inactive401{"detail": "Unauthorized: Invalid Token - reseller not found"}
Auth service unreachable503{"detail": "Failed to retrieve JWKS from upstream identity provider"}

Step 3: Make your first API call

With a valid token, you're ready to call the API.

Base URLs

EnvironmentBase URL
Productionhttps://api.shopco.com/v1
Testhttps://api.test.shopco.com/v1

All requests must be made over HTTPS. Requests over HTTP will be rejected. The examples throughout this guide use the production base URL. Substitute api.test.shopco.com when testing against the test environment.

Example: list DNS records for a domain

curl -X GET "https://api.shopco.com/v1/domain/example.com/dns_records" \  
   -H "Authorization: Bearer <access_token>"

A 200 OK response returns the full list of DNS records for that domain. See DNS endpoints for the complete reference.


Rate limiting

Requests are rate limited per reseller account to 600 requests per minute. Every authenticated response includes the following headers to help you track usage:

HeaderDescription
X-RateLimit-LimitThe limit for the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetWhen the window resets (Unix timestamp)
Retry-AfterSeconds until the current window resets

When the rate limit is exceeded, the API returns 429 Too Many Requests.


DNS record model

Each DNS record returned by the API includes the following fields:

FieldDescription
idUnique record identifier (UUID) — returned in responses, used for update/delete
dns_zone_idIdentifier of the DNS zone this record belongs to — returned in responses only
nameThe record name/host (e.g. www, @, *)
rr_typeRecord type (e.g. A, MX, TXT)
classAlways "IN" — returned in responses only
ttlTime to live in seconds. Default: 900. Minimum: 0
contentHuman-readable string representation of the record data — returned in responses only
rr_dataStructured record data — fields vary by rr_type (see Record type reference)

Example record response:

{  
"id": "0fbf4d2b-6a61-4ad8-b6f6-8af302bc87fb",  
"dns_zone_id": "5f557f5a-c4d8-44ca-bcef-e66eb38f99f7",  
"name": "www",  
"rr_type": "A",  
"class": "IN",  
"ttl": 900,  
"content": "198.51.100.25",  
"rr_data": {    
   "address": "198.51.100.25"
  } 
}

DNS endpoints

All DNS endpoints operate within the context of a specific domain. The domain must be in your Storefront and must be using Shopco nameservers.

List DNS records

Retrieve all DNS records for a domain.

GET /domain/{domain}/dns_records

Example:

curl -X GET "https://api.shopco.com/v1/domain/example.com/dns_records" \  
   -H "Authorization: Bearer <access_token>"
curl -X GET "https://api.shopco.com/v1/domain/example.com/dns_records" \  
-H "Authorization: Bearer <access_token>"

Response: 200 OK — array of DNS record objects.


Get a DNS record

Retrieve a single DNS record by ID.

GET /domain/{domain}/dns_record/{record_id}

Example:

curl -X GET "https://api.shopco.com/v1/domain/example.com/dns_record/0fbf4d2b-6a61-4ad8-b6f6-8af302bc87fb" \  
-H "Authorization: Bearer <access_token>"

Response: 200 OK — single DNS record object.


Create a DNS record

Add a new DNS record to a domain.

POST /domain/{domain}/dns_record

Request body:

{  
"name": "www",  
"rr_type": "A",  
"ttl": 900,  
"rr_data": {    
   "address": "198.51.100.25" 
 } 
}

Example — adding an A record:

curl -X POST "https://api.shopco.com/v1/domain/example.com/dns_record" \  
-H "Authorization: Bearer <access_token>" \  
-H "Content-Type: application/json" \  
-d '{    
    "name": "www",    
    "rr_type": "A",    
    "ttl": 900,    
    "rr_data": { "address": "198.51.100.25" } 
}'

Example — adding an MX record:

curl -X POST "https://api.shopco.com/v1/domain/example.com/dns_record" \  
-H "Authorization: Bearer <access_token>" \  
-H "Content-Type: application/json" \  
-d '{    
"name": "@",   
"rr_type": "MX",    
"ttl": 3600,    
"rr_data": { "server": "mail.example.net", "priority": 10 }  
}'

Example — adding a TXT record (e.g. SPF):

curl -X POST "https://api.shopco.com/v1/domain/example.com/dns_record" \  
-H "Authorization: Bearer <access_token>" \  
-H "Content-Type: application/json" \  
-d '{    
"name": "@",    
"rr_type": "TXT",    
"ttl": 3600,    
"rr_data": { "data": "v=spf1 include:sendgrid.net ~all" }  
}'

Response: 204 No Content


Update a DNS record

Update an existing DNS record by its record ID. The record ID is returned in the List and Get responses.

PUT /domain/{domain}/dns_record/{record_id}

Request body: Same structure as create. Include the full record fields you want to set.

Example:

curl -X PUT "https://api.shopco.com/v1/domain/example.com/dns_record/0fbf4d2b-6a61-4ad8-b6f6-8af302bc87fb" \  
-H "Authorization: Bearer <access_token>" \  
-H "Content-Type: application/json" \  
-d '{    
"name": "www",    
"rr_type": "A",    
"ttl": 300,    
"rr_data": { "address": "198.51.100.26" }  
}'

Response: 204 No Content


Delete a DNS record

Delete an existing DNS record by its record ID.

DELETE /domain/{domain}/dns_record/{record_id}

Example:

curl -X DELETE "https://api.shopco.com/v1/domain/example.com/dns_record/0fbf4d2b-6a61-4ad8-b6f6-8af302bc87fb" \  
   -H "Authorization: Bearer <access_token>"

Response: 204 No Content


Reset DNS records

Resets the domain's DNS records to your reseller default/template configuration. Use this to restore a domain to a known baseline. For example, after a customer has made changes you want to undo, or when re-provisioning a domain.

Note: This is a safe reset to your reseller baseline. It is not a generic zone wipe.

POST /domain/{domain}/dns_records/reset

Example:

curl -X POST "https://api.shopco.com/v1/domain/example.com/dns_records/reset" \  
 -H "Authorization: Bearer <access_token>"

Response: 204 No Content


Import DNS records

Replaces all existing DNS records for a domain with the provided set in a single operation. Use this for migration scenarios. For example, when moving a domain from another DNS provider and you have a complete zone to apply.

Note: Import is a full replacement. All existing records are deleted and replaced with the provided list. Passing an empty array clears all records. The API validates the resulting zone state before committing.

POST /domain/{domain}/dns_records/import

Request body: An array of DNS record objects.

[  
{ "name": "www", "rr_type": "A", "ttl": 900, "rr_data": { "address": "198.51.100.25" } },  
{ "name": "@", "rr_type": "MX", "ttl": 3600, "rr_data": { "server": "mail.example.net", "priority": 10 } }
]

Example:

curl -X POST "https://api.shopco.com/v1/domain/example.com/dns_records/import" \  
-H "Authorization: Bearer <access_token>" \  
-H "Content-Type: application/json" \  
-d '[    
  { "name": "www", "rr_type": "A", "ttl": 900, "rr_data": { "address": "198.51.100.25" } },    
  { "name": "@", "rr_type": "MX", "ttl": 3600, "rr_data": { "server": "mail.example.net", "priority": 10 }}
]'

Response: 204 No Content


Record type reference

rr_data fields depend on rr_type. The table below lists the required fields for each type. name and ttl are always accepted at the top level of the request body.

Typerr_data fieldsNotes
Aaddress (IPv4 string)name accepts: hostname label, @, or *
AAAAaddress (IPv6 string)name accepts: hostname label, @, or *
CNAMEtarget (FQDN, or DKIM format selector._domainkey.domain.tld)CNAME name must be unique — cannot coexist with other record types at the same name
MXserver (hostname), priority (non-negative integer)Lower priority value = higher preference
TXTdata (non-empty string)Use for SPF, DKIM, domain verification, etc.
SRVtarget (hostname), port (1-65535),
priority (non-negative integer),
weight (non-negative integer)
name format: _service._proto
NSserver (nameserver hostname)Use with caution — NS changes affect DNS delegation
DSkeytag (0-65535),
algo (1-255),
digest_type (1-255),
digest (hex string)
Used for DNSSEC; requires registry support

Default TTL: 900 seconds (15 minutes) if omitted. Minimum is 0.

Record type examples

A

{ "name": "www", "rr_type": "A", "ttl": 900, "rr_data": { "address": "198.51.100.25" } }

AAAA

{ "name": "www", "rr_type": "AAAA", "ttl": 900, "rr_data": { "address": "2001:db8::1" } }

CNAME

{ "name": "blog", "rr_type": "CNAME", "ttl": 300, "rr_data": { "target": "shops.myhost.example" } }

MX

{ "name": "@", "rr_type": "MX", "ttl": 3600, "rr_data": { "server": "mail.example.net", "priority": 10 } }

TXT

{ "name": "@", "rr_type": "TXT", "ttl": 3600, "rr_data": { "data": "v=spf1 include:mail.example.net ~all" } }

SRV

{ "name": "_sip._tcp", "rr_type": "SRV", "ttl": 3600, "rr_data": { "target": "sip01.example.net", "port": 5060, "priority": 10, "weight": 5 } }

NS

{ "name": "sub", "rr_type": "NS", "ttl": 3600, "rr_data": { "server": "ns1.example.net" } }

DS

{ "name": "@", "rr_type": "DS", "ttl": 3600, "rr_data": { "keytag": 12345, "algo": 13, "digest_type": 2, "digest": "4F8A9E3F5A8E6B4A65A9D449F0D9E4A2B018F9E4B7CEB3AA2F4D8D46C8F89A60" } }

Error responses

HTTP status codes

StatusMeaning
400 Bad RequestInvalid payload or DNS constraint violation
401 UnauthorizedMissing or invalid token
404 Not FoundDomain or record not found
429 Too Many RequestsRate limit exceeded — check Retry-After header
500 Internal Server ErrorUnexpected failure — contact support if this persists

Error response format

DNS validation errors (400) return:

{  
   "error_string": "<error code>",  
   "extra": null 
}

Not-found and server errors use the standard format:

{  
"detail": "<message>" 
}

Common error codes

error_stringCause
"duplicate dns record"An identical record already exists
"cname name not unique"A CNAME was added at a name that already has other records
"name conflict with existing cname record"A non-CNAME record was added at a name that already has a CNAME
"invalid algo for DS record"algo value is out of the valid range
"invalid keytag for DS record"keytag value is out of the valid range
"invalid digest_type for DS record"digest_type value is out of the valid range

Invalid input (missing or invalid field)

{  
"detail": [   
    {      
      "type": "missing",      
      "loc": ["body", "A", "rr_data", "address"],      
      "msg": "Field required",      
      "input": {}    
    }  
  ] 
}

Record not found:

{ "detail": "DNS record not found" }

Domain not found:

{ "detail": "Not Found" }

DNS zone not found (reset endpoint):

{ "detail": "Not found" }

Coming soon

The Storefront API is actively being expanded. Upcoming additions include:

  • Customer management — create, read, and update customer accounts via API
  • Additional endpoints as the platform grows

Questions or issues with the API? Contact OpenSRS Support.

Was this article helpful? If not please submit a request here

How helpful was this article?

Thanks for your feedback!

Do you still need help? If so please submit a request here.