Skip to content

Core Concepts

Before diving into the API, it’s important to understand Arky’s core abstractions and how they work together.

A Business is the top-level tenant in Arky. Every resource (collections, products, services, orders, reservations, etc.) belongs to a business.

{
id: string;
name: string;
timezone: string; // e.g., "America/New_York"
statuses: StatusEvent[]; // Status history
gallery: GalleryItem[]; // Business media/branding
configs: BusinessConfigs; // Markets, zones, blocks, webhooks, etc.
}

Business Configs

The configs object contains all business-level settings:

  • languages: Supported locales (e.g., [{ code: 'en', isDefault: true }])
  • markets: Price/tax/payment configurations per region
  • zones: Geographic shipping and tax rules
  • buildHooks: URLs triggered on content changes (for static site rebuilds)
  • webhooks: Event listeners for orders, reservations, etc.
  • orderBlocks: Custom form fields for checkout
  • reservationBlocks: Custom form fields for reservations
  • paymentProvider: Stripe configuration
  • aiProvider: AI block generation config (Deepseek)

Arky supports multi-region pricing and logistics out of the box.

A Market defines currency, tax mode, and payment methods for a region:

{
id: 'US',
currency: 'USD',
taxMode: 'EXCLUSIVE', // or 'INCLUSIVE'
taxBps: 700, // 7% tax (basis points: 100 = 1%)
paymentMethods: [
{ method: 'CREDIT_CARD' },
{ method: 'CASH' }
]
}

Tax Modes:

  • EXCLUSIVE: Tax is added on top of prices (common in US)
  • INCLUSIVE: Tax is included in prices (common in EU)

A Zone groups countries for shipping and tax purposes:

{
id: 'north-america',
name: 'North America',
countries: ['US', 'CA', 'MX'],
taxBps: 800, // 8% zone-level tax
shippingMethods: [
{
id: 'standard',
type: 'SHIPPING',
prices: [
{ market: 'US', amount: 500, freeThreshold: 5000 } // $5, free over $50
],
taxable: true,
etaText: '3-5 business days'
},
{
id: 'pickup',
type: 'PICKUP',
prices: [{ market: 'US', amount: 0 }],
location: { address: '123 Main St, NY', coordinates: { lat: 40.7, lon: -74 } }
}
]
}

Zone Resolution:

  • If a country is listed in a zone, that zone’s rules apply
  • If countries: [], the zone acts as a fallback for all countries

All monetary amounts in Arky are stored in minor units (cents):

  • amount: 1999 = $19.99
  • amount: 500 = $5.00

Products and services have market-based prices:

{
prices: [
{ market: 'US', amount: 2999 }, // $29.99 in US
{ market: 'EU', amount: 2799 }, // €27.99 in EU
{ market: 'UK', amount: 2499 } // £24.99 in UK
]
}

Blocks are Arky’s universal field system—think Notion-style structured content. They’re used everywhere: CMS entries, product attributes, order forms, reservation metadata.

TypeDescriptionExample Value
TextString with optional validation (email, phone, URL, etc.)"John Doe" or { en: "Hello", fr: "Bonjour" }
NumberInteger or decimal42 or 19.99
BooleanTrue/falsetrue
RelationshipReference to media or other entries["media:abc123"] or ["entry:def456"]
GeoLocationAddress + coordinates{ address: "123 Main St", coordinates: { lat: 40.7, lon: -74 } }
DateTimestamp1698765432
Block (nested)Repeatable group of fieldsArray of objects with nested blocks
{
id: string;
key: string; // Field name (e.g., "title", "description")
type: 'TEXT' | 'NUMBER' | 'BOOLEAN' | 'RELATIONSHIP' | 'GEO_LOCATION' | ...;
properties: {
label?: { en: string; fr: string; ... }; // Localized labels
minLength?: number;
maxLength?: number;
pattern?: string; // Regex for validation
variant?: 'EMAIL' | 'PHONE_NUMBER' | 'URL'; // Text variants
// ... type-specific properties
};
value: any[]; // Always an array (for consistency)
}

Text blocks support localization:

// Simple text
{ key: 'title', type: 'TEXT', value: ["Hello World"] }
// Multilingual text
{ key: 'title', type: 'TEXT', value: [{ en: "Hello", fr: "Bonjour" }] }

Use sdk.utils.getBlockTextValue(block, 'en') to extract the right locale.

  • Hydration: Expands relationships (e.g., "media:abc" → full media object)
  • Dehydration: Reduces to references (for AI processing or storage)

The server auto-hydrates responses; the SDK provides utilities for working with both forms.

  1. JWT (Access + Refresh)

    • Created via email/password login or OAuth
    • Access tokens expire (configurable); refresh tokens are long-lived
    • Used with Authorization: Bearer <token> header
  2. API Keys

    • For server-to-server integrations
    • No expiry
    • Used with X-API-Key: <key> header
    • Set provider: 'API' in getToken() to signal this mode
  3. Guest Tokens

    • Auto-created for unauthenticated users (if autoGuest: true)
    • Limited permissions (read-only for public data)

Every user can have roles associated with a business. Each role defines permissions:

{
id: string;
name: string; // e.g., "Business Owner", "Manager", "Member"
businessId: string;
permissions: [
{
action: 'ADMIN' | 'WRITE' | 'READ',
resource: '/businesses/{businessId}', // Path pattern
conditions: [] // Optional conditions (future use)
}
]
}

Default Roles (auto-created for new businesses):

  • Business Owner: Full admin access
  • Manager: Write access to all resources
  • Member: Write access to orders and reservations
  • Viewer: Read-only access
  1. Client includes Authorization or X-API-Key header
  2. Server extracts user ID and business context
  3. For protected routes, server checks if user’s roles grant required permission
  4. If authorized, request proceeds; otherwise, 403 Forbidden

A Collection is a content type (like “Blog Posts”, “Products”, “Team Members”):

{
id: string;
name: string;
businessId: string;
schema: Block[]; // Field definitions
}

An Entry is an instance of a collection (like a specific blog post):

{
id: string;
owner: string; // Format: "collection:{collectionId}" or custom
blocks: Block[]; // Filled-in values matching the collection schema
statuses: StatusEvent[]; // DRAFT, PUBLISHED, ARCHIVED
}

Owner Model: Entries can belong to collections or other arbitrary owners (e.g., "user:abc", "order:xyz") for flexible content organization.

A Product is a sellable item:

{
id: string;
name: string;
slug: string; // URL-friendly identifier
categoryIds: string[];
variants: ProductVariant[];
gallery: GalleryItem[];
statuses: StatusEvent[]; // ACTIVE, INACTIVE, ARCHIVED
}

Each product has one or more Variants (size, color, etc.):

{
id: string;
name: string;
sku: string;
prices: Price[]; // Market-based pricing
attributes: Record<string, string>; // { size: 'M', color: 'Blue' }
inventory: number; // Stock level (-1 = unlimited)
}

Orders represent completed purchases:

{
id: string;
userId: string;
businessId: string;
items: OrderItem[]; // Product variants + quantities
payment: Payment; // Amount, currency, method, Stripe IDs
shippingAddress: Location;
blocks: Block[]; // Custom checkout fields (from business.configs.orderBlocks)
statuses: StatusEvent[]; // INITIATED, CONFIRMED, SHIPPED, COMPLETED, CANCELLED
}

A Service is something bookable (e.g., “Haircut”, “Massage”):

{
id: string;
name: string;
duration: number; // Minutes
prices: Price[]; // Market-based
gallery: GalleryItem[];
}

A Provider delivers a service (e.g., a stylist, therapist):

{
id: string;
name: string;
serviceIds: string[]; // Services they can provide
workingTime: WorkingTime; // Availability schedule
}

Defines when a provider is available:

{
workingDays: [
{ day: 'monday', workingHours: [{ from: 540, to: 1020 }] } // 9am-5pm (minutes from midnight)
],
outcastDates: [
{ month: 12, day: 25, workingHours: [] } // Closed on Christmas
],
specificDates: [
{ date: 1698796800, workingHours: [{ from: 600, to: 720 }] } // Special hours on a date
]
}

A Reservation is a booked time slot:

{
id: string;
userId: string;
businessId: string;
parts: ReservationPart[]; // Can book multiple services/slots at once
payment: Payment;
blocks: Block[]; // Custom reservation fields (from business.configs.reservationBlocks)
statuses: StatusEvent[]; // PENDING, CONFIRMED, COMPLETED, CANCELLED
}

Each ReservationPart:

{
serviceId: string;
providerId?: string; // Optional (if service allows any provider)
from: number; // Unix timestamp
to: number; // Unix timestamp
}

Before checkout, request a Quote to calculate totals:

// Request
{
businessId: string;
market: string;
currency: string;
paymentMethod: 'CREDIT_CARD' | 'CASH';
lines: [
{ type: 'PRODUCT_VARIANT', productId: '...', variantId: '...', quantity: 2 },
{ type: 'SERVICE', serviceId: '...', quantity: 1 }
],
shippingMethodId?: string;
promoCode?: string;
}
// Response (Quote)
{
currency: string;
market: string;
subtotal: number; // Sum of line items (minor units)
shipping: number;
discount: number; // From promo code
tax: number;
total: number;
lineItems: [ /* breakdown per item */ ],
promoCode: { /* validation result */ } | null
}
  1. Build cart (client-side or via API)
  2. Get quotePOST /v1/payments/quote
  3. CheckoutPOST /v1/businesses/{id}/orders/checkout or /v1/reservations/checkout
  4. Stripe handles payment (if CREDIT_CARD), webhook confirms
  5. Order/Reservation created with status CONFIRMED