Error Handling
The Partners API uses a standardized error handling approach to provide consistent, secure error responses without exposing internal system details.
Error Response Format
All API errors follow a consistent JSON structure:
{
"message": "Human-readable error message",
"code": "ERROR_CODE",
"statusCode": 400
}Response Fields
| Field | Type | Description |
|---|---|---|
message | string | Human-readable error description |
code | string | Standardized error code for programmatic handling |
statusCode | integer | HTTP status code |
Standard Error Codes
The API uses these standardized error codes across all endpoints:
| Code | Message | HTTP Status | Description |
|---|---|---|---|
VALIDATION_ERROR | "Invalid request parameters" | 400 | Invalid input data or failed validation |
AUTHENTICATION_ERROR | "Authentication required" | 401 | Missing or invalid Bearer token |
FORBIDDEN | "Access denied" | 403 | Attempting to access resources not owned by partner |
NOT_FOUND | "Resource not found" | 404 | Merchant, tag, or resource doesn't exist |
VALIDATION_ERROR | "DNS verification pending..." | 422 | DNS records not yet propagated (verification endpoint) |
SERVICE_ERROR | "Unable to process request" | 500 | Database errors or internal failures |
Authentication Errors
Authentication failures always return a consistent response to prevent information leakage:
{
"message": "Authentication required",
"code": "AUTHENTICATION_ERROR",
"statusCode": 401
}Common Authentication Issues
- Missing
Authorizationheader - Invalid Bearer token format (must be
Bearer mp_live_...) - Revoked or expired token
- Malformed token structure
Validation Errors
The API validates all request parameters using strict DTOs. Validation failures return:
{
"message": "Invalid request parameters",
"code": "VALIDATION_ERROR",
"statusCode": 400
}Common Validation Issues
- Missing required fields
- Invalid UUID format for IDs
- Invalid domain format (e.g., including protocol or www prefix)
- Out-of-range pagination values (page < 1 or limit > 100)
- Invalid data types
Resource Access Errors
Resource Not Found
When a requested resource doesn't exist:
{
"message": "Resource not found",
"code": "NOT_FOUND",
"statusCode": 404
}Access Denied
When attempting to access another partner's resources:
{
"message": "Access denied",
"code": "FORBIDDEN",
"statusCode": 403
}Special Error Cases
Some operations have specific error scenarios:
Domain Already Registered
When attempting to register a domain that's already in use:
{
"message": "Domain already registered",
"code": "VALIDATION_ERROR",
"statusCode": 409
}DNS Verification Pending
When DNS records haven't propagated yet during verification:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "DNS verification pending. Please allow time for DNS propagation and try again",
"statusCode": 422,
"timestamp": "2026-02-16T08:15:31.482Z",
"path": "/v1/partners/marktag/verify",
"method": "POST"
}
}DNS Verification Failed
When DNS record exists but points to an incorrect domain:
{
"message": "Tag verification failed. Please check your DNS configuration",
"code": "VALIDATION_ERROR",
"statusCode": 400
}Internal Server Errors
All database errors, external service failures, and unexpected errors return a generic response to prevent information leakage:
{
"message": "Unable to process request",
"code": "SERVICE_ERROR",
"statusCode": 500
}This includes:
- Database connection errors
- External service timeouts
- Unexpected application errors
- Configuration issues
Error Handling Best Practices
1. Use Error Codes for Logic
Always check the code field rather than parsing error messages:
// Good ✅
if (error.code === "AUTHENTICATION_ERROR") {
await refreshToken();
}
// Bad ❌
if (error.message.includes("Authentication")) {
await refreshToken();
}2. Implement Retry Logic
For transient failures, implement exponential backoff:
async function apiCallWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) return response;
const error = await response.json();
// Only retry on server errors
if (error.code === "SERVICE_ERROR" && i < maxRetries - 1) {
await new Promise((resolve) =>
setTimeout(resolve, Math.pow(2, i) * 1000),
);
continue;
}
throw error;
} catch (error) {
if (i === maxRetries - 1) throw error;
}
}
}3. Handle Authentication Errors
Implement token refresh logic for 401 errors:
async function makeAuthenticatedRequest(url, token) {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
const error = await response.json();
if (error.code === "AUTHENTICATION_ERROR") {
// Refresh token and retry
const newToken = await refreshAuthToken();
return makeAuthenticatedRequest(url, newToken);
}
throw error;
}
return response;
}4. Validate Input Before Sending
Check request parameters to avoid validation errors:
function validateDomain(domain) {
// Remove protocol if present
domain = domain.replace(/^https?:\/\//, "");
// Remove www prefix
domain = domain.replace(/^www\./, "");
// Validate domain format
const domainRegex = /^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/;
if (!domainRegex.test(domain)) {
throw new Error("Invalid domain format");
}
return domain;
}Error Handling Examples
Complete Error Handling Implementation
class PartnersAPIClient {
constructor(token) {
this.token = token;
this.baseURL = "https://api-alpha.markopolo.ai/v1/partners";
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
try {
const response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json",
...options.headers,
},
});
if (!response.ok) {
const error = await response.json();
return this.handleError(error);
}
return await response.json();
} catch (error) {
// Network or parsing error
console.error("Request failed:", error);
throw {
code: "NETWORK_ERROR",
message: "Network request failed",
};
}
}
handleError(error) {
switch (error.code) {
case "AUTHENTICATION_ERROR":
// Trigger re-authentication flow
this.onAuthenticationError?.();
break;
case "VALIDATION_ERROR":
// Log validation errors for debugging
console.error("Validation failed:", error.message);
break;
case "NOT_FOUND":
// Handle missing resources
console.warn("Resource not found");
break;
case "FORBIDDEN":
// Handle access denied
console.error("Access denied to resource");
break;
case "SERVICE_ERROR":
// Retry with backoff for server errors
console.error("Server error, consider retrying");
break;
default:
// Check for 422 status (DNS pending)
if (error.statusCode === 422) {
console.log("DNS propagation pending, retry in a few minutes");
}
break;
}
throw error;
}
async createMerchant(name) {
try {
return await this.request("/merchant", {
method: "POST",
body: JSON.stringify({ name }),
});
} catch (error) {
if (error.code === "VALIDATION_ERROR") {
console.error("Invalid merchant name provided");
}
throw error;
}
}
async generateMarkTag(merchantId, domain) {
// Validate domain before sending
domain = this.validateDomain(domain);
try {
return await this.request("/marktag/generate", {
method: "POST",
body: JSON.stringify({ merchantId, domain }),
});
} catch (error) {
if (
error.code === "VALIDATION_ERROR" &&
error.message.includes("already registered")
) {
console.error("Domain is already registered");
}
throw error;
}
}
validateDomain(domain) {
domain = domain.replace(/^https?:\/\//, "").replace(/^www\./, "");
if (!domain.match(/^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/)) {
throw new Error("Invalid domain format");
}
return domain;
}
}
// Usage
const client = new PartnersAPIClient("mp_live_YOUR_TOKEN");
client.onAuthenticationError = () => {
// Handle re-authentication
window.location.href = "/login";
};
// Make API calls with automatic error handling
try {
const merchant = await client.createMerchant("Acme Corp");
const marktag = await client.generateMarkTag(
merchant.merchantId,
"example.com",
);
} catch (error) {
// Errors are already logged by the client
// Handle based on error code
if (error.code === "SERVICE_ERROR") {
showUserMessage("Temporary issue, please try again");
}
}Common Error Scenarios
Creating a Merchant
| Scenario | Error Code | Resolution |
|---|---|---|
| Missing merchant name | VALIDATION_ERROR | Provide a valid merchant name |
| Database issue | SERVICE_ERROR | Retry the request |
Generating MarkTag
| Scenario | Error Code | Resolution |
|---|---|---|
| Invalid domain format | VALIDATION_ERROR | Use format: example.com (no protocol/www) |
| Merchant doesn't exist | NOT_FOUND | Create merchant first |
| Domain already registered | VALIDATION_ERROR | Domain is already in use by another partner |
| Partner domain mismatch | VALIDATION_ERROR | Use your assigned partner domain for preverified tags |
Verifying MarkTag
| Scenario | Error Code | HTTP Status | Resolution |
|---|---|---|---|
| Tag doesn't exist | NOT_FOUND | 404 | Check tag ID is correct |
| DNS not propagated | VALIDATION_ERROR | 422 | Wait for DNS propagation (5-30 minutes typical) |
| DNS misconfigured | VALIDATION_ERROR | 400 | Check CNAME record configuration |
| Not your tag | FORBIDDEN | 403 | Can only verify tags you created |
| Server error | SERVICE_ERROR | 500 | Retry with exponential backoff |
Retrieving Events
| Scenario | Error Code | Resolution |
|---|---|---|
| Invalid date range | VALIDATION_ERROR | Use valid ISO 8601 date format |
| Page out of range | VALIDATION_ERROR | Use page number within valid range |
| Merchant not found | NOT_FOUND | Check merchant ID exists |
Support
If you encounter persistent errors or need assistance:
- Check your API token is valid and not expired
- Verify your request format matches the documentation
- Ensure DNS records are properly configured for domain verification
- Contact partners@markopolo.ai for technical support