Skip to content

Troubleshooting & FAQ

This guide covers common issues, error messages, and debugging strategies for working with the Arky platform.


Symptoms:

  • API returns 401 status code
  • Error message: “Unauthorized” or “Invalid token”

Causes & Solutions:

Problem: Access token has expired

Solution:

// Ensure expiresAt is set correctly in setToken()
setToken: (tokens) => {
localStorage.setItem('accessToken', tokens.accessToken);
localStorage.setItem('refreshToken', tokens.refreshToken);
localStorage.setItem('expiresAt', tokens.expiresAt.toString()); // ← Required
}
// SDK will automatically refresh when expiresAt < now

Symptoms:

  • Browser console: “CORS policy blocked request”
  • Network tab shows preflight OPTIONS request failing

Causes & Solutions:

  1. Wrong baseUrl in SDK config

    // ❌ Wrong (uses local dev server)
    baseUrl: 'http://localhost:8000'
    // ✅ Correct (uses production API)
    baseUrl: 'https://api.arky.io'
  2. Custom headers without CORS support

    // ❌ Custom header may trigger CORS preflight
    const result = await sdk.httpClient.get('/v1/products', {
    headers: { 'X-Custom-Header': 'value' }
    });
    // ✅ Use standard headers only
    const result = await sdk.eshop.products.list({ page: 1 });
  3. Proxy misconfiguration (Vite, Webpack, Next.js)

    // vite.config.ts - Proxy API during development
    export default defineConfig({
    server: {
    proxy: {
    '/api': {
    target: 'https://api.arky.io',
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/api/, ''),
    },
    },
    },
    });

400 Bad Request: “Invalid payment amount”

Section titled “400 Bad Request: “Invalid payment amount””

Symptoms:

  • Checkout fails with validation error on payment.amount

Causes:

  • Amount doesn’t match quote total (tax + shipping + subtotal - discounts)

Solution:

// 1. Get quote first
const quote = await sdk.payment.getQuote({
marketId: 'market_us',
parts: [{ productId: 'prod_123', quantity: 2 }],
promoCode: 'SAVE10',
});
// 2. Use quote.total as payment amount
const order = await sdk.eshop.orders.create({
marketId: 'market_us',
parts: [{ productId: 'prod_123', quantity: 2, blocks: [] }],
blocks: [],
payment: {
provider: 'STRIPE',
currency: quote.currency,
amount: quote.total, // ← Must match quote
},
});

Symptoms:

  • Order created but payment status is FAILED

Causes:

  1. Stripe webhook not configured
  2. Card declined
  3. Stripe API key mismatch (test vs. prod)

Solution:

Terminal window
# Check Stripe webhook logs
# Ensure webhook URL is configured: https://api.arky.io/v1/payments/stripe/webhook
# Test webhook locally with Stripe CLI
stripe listen --forward-to localhost:8000/v1/payments/stripe/webhook

Symptoms:

  • Upload fails for large files (>10MB)

Causes:

  • File exceeds server limits

Solution:

// Compress images before upload
import imageCompression from 'browser-image-compression';
const file = document.querySelector('input[type="file"]').files[0];
const options = {
maxSizeMB: 5,
maxWidthOrHeight: 1920,
};
const compressedFile = await imageCompression(file, options);
const media = await sdk.media.upload(compressedFile, {
owner: 'product:prod_123',
generateResolutions: true,
});

400 Bad Request: “Unsupported file type”

Section titled “400 Bad Request: “Unsupported file type””

Symptoms:

  • Upload rejected with file type error

Supported Formats:

  • Images: JPG, PNG, WebP, SVG, GIF
  • Videos: MP4, WebM
  • Documents: PDF

Solution:

// Validate file type before upload
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'];
if (!allowedTypes.includes(file.type)) {
alert('Please upload a JPG, PNG, WebP, or SVG file');
return;
}
const media = await sdk.media.upload(file, { owner: 'entry:123' });

400 Bad Request: “Block validation failed”

Section titled “400 Bad Request: “Block validation failed””

Symptoms:

  • Entry create/update fails with block-specific errors

Common Causes:

{
"message": "Block validation failed",
"validationErrors": {
"blocks.title": ["Title is required"]
}
}

Solution: Ensure all required blocks are included

await sdk.cms.entries.create('posts', {
title: "My Post",
blocks: [
{ key: 'title', value: [{ en: 'My Post' }] }, // ← Required
{ key: 'body', value: [{ en: 'Content...' }] },
],
});

409 Conflict: “Time slot not available”

Section titled “409 Conflict: “Time slot not available””

Symptoms:

  • Reservation creation fails with conflict error

Causes:

  • Slot already booked by another customer
  • Provider schedule conflict

Solution:

// 1. Always fetch fresh availability
const slots = await sdk.reservation.slots.getAvailable({
serviceId: 'service_123',
providerId: 'provider_456',
date: '2025-02-01',
});
// 2. Check slot availability before booking
const selectedSlot = slots.find(s => s.startTime === desiredStartTime);
if (!selectedSlot || !selectedSlot.available) {
alert('This time slot is no longer available');
return;
}
// 3. Book immediately
const reservation = await sdk.reservation.reservations.create({
serviceId: 'service_123',
providerId: 'provider_456',
startTime: selectedSlot.startTime,
endTime: selectedSlot.endTime,
// ...
});

400 Bad Request: “Reservation outside working hours”

Section titled “400 Bad Request: “Reservation outside working hours””

Symptoms:

  • Booking fails even though slot appears available

Causes:

  • Provider’s working schedule updated after fetching slots
  • Holiday/break period

Solution:

// Check provider working schedule
const provider = await sdk.reservation.providers.get('provider_456');
console.log(provider.workingSchedule);
// Ensure reservation time matches working hours
// Server validates against current schedule, not cached slots

Symptoms:

  • Concurrent updates fail with conflict error

Causes:

  • Optimistic locking detected concurrent modification
  • Retry needed

Solution: The server automatically retries most operations. If error persists:

// Implement exponential backoff retry
async function withRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.statusCode === 409 && i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 2 ** i * 1000));
continue;
}
throw error;
}
}
}
// Usage
const product = await withRetry(() =>
sdk.eshop.products.update('prod_123', { title: 'New Title' })
);

Symptoms:

  • Search query returns no results
  • Known entries not appearing

Causes:

  1. Search index not updated (Qdrant)
  2. Incorrect search params

Solutions:

// 1. Use exact ID lookup instead of search
const product = await sdk.eshop.products.get('prod_123'); // ✅ Works
// 2. Check search query syntax
const products = await sdk.eshop.products.list({
search: 'laptop', // ✅ Simple keyword
// search: '*laptop*', // ❌ Wildcards not supported
});
// 3. Use filters instead of search
const products = await sdk.eshop.products.list({
categoryId: 'cat_electronics',
marketId: 'market_us',
});

Causes & Solutions:

Problem: Fetching entries with hydrate=true is slow

Solution:

// ❌ Slow (hydrates all relationships)
const entries = await sdk.cms.entries.list('posts', {
page: 1,
limit: 100,
hydrate: true
});
// ✅ Fast (dehydrated references)
const entries = await sdk.cms.entries.list('posts', {
page: 1,
limit: 100
});
// Hydrate only when displaying single entry
const entry = await sdk.cms.entries.get('posts', 'post_123', { hydrate: true });

Symptoms:

  • API returns 429 status code
  • Header: Retry-After: 60

Causes:

  • Exceeded rate limits (varies by endpoint)

Solution:

// Implement rate limit handling
async function apiCall() {
try {
return await sdk.eshop.products.list({ page: 1, limit: 20 });
} catch (error) {
if (error.statusCode === 429) {
const retryAfter = error.headers?.['retry-after'] || 60;
console.log(`Rate limited. Retrying after ${retryAfter}s`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return apiCall(); // Retry
}
throw error;
}
}

Best Practices:

  • Cache responses when possible
  • Use pagination instead of fetching all items
  • Implement exponential backoff for retries

All TEXT blocks support multilingual values:

// Creating multilingual entry
await sdk.cms.entries.create('posts', {
title: "My Post",
blocks: [
{
key: 'title',
value: [
{ en: 'Hello World', es: 'Hola Mundo', fr: 'Bonjour Monde' }
]
}
],
});
// Extracting localized text
const title = sdk.utils.getBlockTextValue(
entry.blocks.find(b => b.key === 'title'),
'es' // Locale
);

Yes, the SDK is optional. You can call the REST API directly:

Terminal window
curl -X GET https://api.arky.io/v1/eshop/products \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json"

See individual API docs for endpoint details.

Use tools like ngrok or Stripe CLI to forward webhooks:

Terminal window
# Stripe CLI
stripe listen --forward-to localhost:8000/v1/payments/stripe/webhook
# ngrok
ngrok http 8000
# Use ngrok URL in webhook settings

Yes, via CMS email templates:

  1. Create collection with type EMAIL_TEMPLATES
  2. Add entry with HTML blocks and variables
  3. Reference template in notifications/newsletters

See CMS API - Newsletters for details.

How do I migrate data between environments?

Section titled “How do I migrate data between environments?”

Use the API to export/import:

// Export from dev
const products = await devSdk.eshop.products.list({ page: 1, limit: 1000 });
// Import to prod
for (const product of products) {
await prodSdk.eshop.products.create({
title: product.title,
blocks: product.blocks,
prices: product.prices,
// ...
});
}

For large datasets, contact support for bulk import tools.

All timestamps are Unix timestamps (UTC). Convert in client:

const reservation = await sdk.reservation.reservations.get('res_123');
// Convert to user's timezone
const userTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
const localTime = new Date(reservation.startTime * 1000).toLocaleString('en-US', {
timeZone: userTz,
});

Use SDK timezone utils for dropdowns:

const tzGroups = sdk.utils.tzGroups;
const userTz = sdk.utils.findTimeZone('New York'); // "America/New_York"

Arky is a managed platform (SaaS). Self-hosting is not supported.

For enterprise deployments, contact sales for dedicated instances.


When reporting issues, include:

  1. Request ID (from error response headers: x-request-id)
  2. SDK version (console.log(SDK_VERSION) from arky-sdk)
  3. Error message and stack trace
  4. Reproduction steps

Check server health:

const health = await sdk.analytics.health();
console.log(health.status); // "healthy" | "degraded" | "down"