Reference data

http
GET /v1/reference

One call that tells an integration every allowed value and this venue's own limits — fetch it first and you never have to guess an enum or discover a cap through a 400. Available on every plan with an active subscription.

Response#

json
{
  "units": ["PCS", "G", "ML"],
  "badgeColors": ["RED", "PURPLE", "YELLOW", "GREEN", "BLUE", "GREY"],
  "modifierTypes": {
    "accepted": ["single_choice", "multiple_choice", "add_ingredients", "remove_ingredients"],
    "canonical": ["single_choice", "add_ingredients", "remove_ingredients"],
    "deprecated": {
      "multiple_choice": "accepted for backward compatibility, stored as 'add_ingredients'"
    }
  },
  "orderStatuses": ["new", "confirmed", "preparing", "delivering", "completed", "cancelled"],
  "orderStatusTransitions": {
    "new": ["confirmed", "cancelled"],
    "confirmed": ["preparing", "cancelled"],
    "preparing": ["delivering", "completed", "cancelled"],
    "delivering": ["completed", "cancelled"],
    "completed": [],
    "cancelled": []
  },
  "cardStyles": ["minimalism", "compact", "premium"],
  "currencies": ["UAH", "USD", "EUR", "PLN", "GBP", "CAD", "PEN", "CLP", "MXN", "COP", "UZS"],
  "languages": ["uk", "en", "es", "fr", "de", "it", "pt-BR", "ru", "pl", "cs", "zh-CN", "ar", "ja", "uz"],
  "requestLimits": {
    "bodyLimitMb": 5,
    "mediaBodyLimitMb": 21,
    "rateLimits": {
      "short": { "limit": 10, "windowSec": 1 },
      "medium": { "limit": 50, "windowSec": 10 },
      "long": { "limit": 300, "windowSec": 60 }
    },
    "planRateLimits": {
      "windowSec": 60,
      "keyedBy": "venue",
      "tiers": {
        "general": { "free": 30, "paid": 120 },
        "media": { "free": 5, "paid": 30 },
        "heavy": { "free": 10, "paid": 60 }
      }
    },
    "syncArrayCaps": { "categories": 200, "ingredients": 200, "products": 500 },
    "syncNestedCaps": {
      "modifierGroupsPerProduct": 20,
      "optionsPerModifierGroup": 50,
      "ingredientRefsPerProduct": 100
    },
    "cleanupArrayCap": 1000
  },
  "conventions": {
    "money": "All amounts are integers in minor currency units (kopecks/cents)",
    "timestamps": "ISO 8601 with an explicit timezone (UTC recommended)"
  },
  "planLimits": {
    "plan": "duckling",
    "rateLimitTier": "paid",
    "limits": {
      "maxProducts": 2000,
      "maxCategories": 200,
      "maxIngredients": 300,
      "maxBanners": 20,
      "maxQrTables": 500
    },
    "usage": {
      "products": 64,
      "categories": 8,
      "ingredients": 41,
      "banners": 2,
      "tables": 12
    },
    "features": {
      "hasDelivery": true,
      "aiEnabled": true,
      "ordersApi": true
    }
  }
}

Everything above planLimits is static catalog data served straight from the same definitions the API validates against — the values can't drift from what endpoints accept. planLimits reflects your venue's current plan and updates immediately on plan changes.

How an agent should use it#

  1. Fetch once per session, before writing.
  2. Validate enum inputs (unit, badgeColor, modifier type, cardStyle, defaultCurrency, locales) against the catalog instead of hardcoding.
  3. Check headroom before bulk syncs directly: planLimits.limits.maxProducts − planLimits.usage.products — no row fetch needed (usage uses the same filters as the limit checks).
  4. Branch features: features.hasDelivery before touching order-config settings, features.ordersApi before the Orders API.
  5. Pace requests from requestLimits.planRateLimits + planLimits.rateLimitTier — your venue's actual rate-limit budgets — instead of hardcoding them.

Example#

bash
curl https://api.duck-hub.com/v1/reference \
  -H "Authorization: Bearer dk_live_your_api_key"