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:
- A
- 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.
- Log in to Storefront Manager.
- Navigate to Settings > Advanced Settings > API.
- Click Create to generate a new credential pair.
- You'll be prompted to confirm your login credentials before the secret is generated.
- A popup will display your Client Secret. Copy it to a secure location immediately. It will not be shown again.
- 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
| Environment | Token URL |
|---|---|
| Production | https://auth.shopco.com/oauth2/token |
| Test | https://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
| Scenario | Status | Response body |
|---|---|---|
| No Authorization header, or not Bearer scheme | 401 | {"detail": "Missing or invalid authentication scheme"} |
| Token is not a valid JWT | 401 | {"detail": "Invalid token: <reason>"} |
| Token signature invalid | 401 | {"detail": "Invalid token: <reason>"} |
| Token key ID not found | 401 | {"detail": "Public key not found"} |
| Token valid but Client ID not recognized or inactive | 401 | {"detail": "Unauthorized: Invalid Token - reseller not found"} |
| Auth service unreachable | 503 | {"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
| Environment | Base URL |
|---|---|
| Production | https://api.shopco.com/v1 |
| Test | https://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:
| Header | Description |
|---|---|
| X-RateLimit-Limit | The limit for the current window |
| X-RateLimit-Remaining | Requests remaining in the current window |
| X-RateLimit-Reset | When the window resets (Unix timestamp) |
| Retry-After | Seconds 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:
| Field | Description |
|---|---|
| id | Unique record identifier (UUID) — returned in responses, used for update/delete |
| dns_zone_id | Identifier of the DNS zone this record belongs to — returned in responses only |
| name | The record name/host (e.g. www, @, *) |
| rr_type | Record type (e.g. A, MX, TXT) |
| class | Always "IN" — returned in responses only |
| ttl | Time to live in seconds. Default: 900. Minimum: 0 |
| content | Human-readable string representation of the record data — returned in responses only |
| rr_data | Structured 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_recordsExample:
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_recordRequest 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/resetExample:
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/importRequest 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.
| Type | rr_data fields | Notes |
|---|---|---|
| A | address (IPv4 string) | name accepts: hostname label, @, or * |
| AAAA | address (IPv6 string) | name accepts: hostname label, @, or * |
| CNAME | target (FQDN, or DKIM format selector._domainkey.domain.tld) | CNAME name must be unique — cannot coexist with other record types at the same name |
| MX | server (hostname), priority (non-negative integer) | Lower priority value = higher preference |
| TXT | data (non-empty string) | Use for SPF, DKIM, domain verification, etc. |
| SRV | target (hostname), port (1-65535), priority (non-negative integer), weight (non-negative integer) | name format: _service._proto |
| NS | server (nameserver hostname) | Use with caution — NS changes affect DNS delegation |
| DS | keytag (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
| Status | Meaning |
|---|---|
| 400 Bad Request | Invalid payload or DNS constraint violation |
| 401 Unauthorized | Missing or invalid token |
| 404 Not Found | Domain or record not found |
| 429 Too Many Requests | Rate limit exceeded — check Retry-After header |
| 500 Internal Server Error | Unexpected 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_string | Cause |
|---|---|
| "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!