Gabito Public API — integration guide

REST API for reading salon data from Gabito. No JWT on public unit endpoints — use a 10-character public slug for marketing data without an access code. Build your own website, mobile app, or partner integration.

Overview

All public endpoints live under the production REST API base URL. Responses are JSON.

https://apis.gabito.eu/api/v1
  • Format: JSON (Content-Type: application/json for POST bodies)
  • Methods: mostly GET (read)
  • Unit data: scoped by 10-character public slug (recommended) or legacy numeric unitId with access code
  • No session JWT on public unit endpoints — slug routes for profile, services, and reviews need no access code
  • Anti-enumeration: numeric unitId URLs without an access code return 404 unit_public_numeric_route_blocked
  • Field visibility: managers control which fields appear via disclosure settings; responses include a disclosure object where relevant

This guide covers read-only GET endpoints for building your own website or widget: services, unit profile, contact, opening hours, gallery photos, promotions, bestsellers, reviews, and SEO/about text. For online booking, AI chat, and the client portal, link to Gabito-hosted pages instead of calling write APIs.

Manager-only routes under /unit-public-api/staff/* and chat/booking write endpoints require JWT or session tokens and are not documented here.

Quick start — credentials & manager panel

To call unit-scoped endpoints you need:

  1. Public slug — 10-character alphanumeric identifier (e.g. AbCdEfGh12) for public pages at /public/unit/AbCdEfGh12 and API paths /public/units/s/AbCdEfGh12/…. Visible in manager → Public unit API settings. Numeric marketing URLs such as /public/unit/42 are blocked.
  2. Unit ID — internal numeric identifier (manager panel, client portal login). Do not expose in public marketing links.
  3. Unit public access code — required for online booking and public chat deep links, and for legacy numeric API routes. Not required for read-only marketing data on slug routes.

Where to find the access code

Log in to the Gabito manager panel → open Public unit API settings at:

/unit-public-api/settings

In the Public API — access code and pages section, set the system parameter unit_public_api.access_code (unit scope). The code is also stored on the unit record as public_api_access_code. Copy it into your integration — treat it like a password.

Enable the channels you need (services, profile, reviews) using the toggle in each module row. Each channel must be ON or its endpoints return 403 with unit_public_api_channel_disabled.

Discover enabled channels (slug route, no access code)

GET /public/units/s/AbCdEfGh12/spa-appearance
GET /public/units/s/AbCdEfGh12   → unit_id, name, public_slug

Replace AbCdEfGh12 with your unit’s 10-character slug from the manager panel. Legacy GET /public/units/{unitId}/spa-appearance without an access code returns 404 unit_public_numeric_route_blocked.

Short links: Gabito also supports URL shortener codes (7+ characters) at https://s.gabito.eu/{code}. These are separate from the 10-character public unit slug.

Authentication

Public marketing endpoints use the slug path prefix /public/units/s/{slug}/… — no access code required. Online booking and AI chat require public_code on Gabito-hosted page URLs. Legacy numeric API routes (/public/units/{unitId}/…) still accept the access code but are not recommended for new integrations.

Slug routes — no access code

All read-only marketing endpoints listed in Unit Public API work with only the 10-character slug in the path. Safe to call from browsers and CDNs without exposing secrets.

Access code — booking, chat, legacy numeric API

When you need the access code, send it via HTTP header (recommended) or query parameter on Gabito-hosted pages:

HTTP header (legacy numeric routes)

X-Unit-Public-Code: <your_access_code>

Query parameter (Gabito-hosted pages)

?public_code=<your_code>
?code=<your_code>

Prefer the header for server-side legacy API calls — query strings may appear in logs and referrer headers.

Common errors

  • 404 unit_public_numeric_route_blocked — numeric unitId URL used without access code (anti-enumeration)
  • 401 unit_public_code_required — legacy numeric route called without code
  • 403 unit_public_code_invalid — wrong code
  • 403 unit_public_api_not_configured — owner has not set an access code yet
  • 404 unit_not_found — invalid slug or unitId
  • 403 unit_public_api_channel_disabled — channel turned off in manager settings

Logged-in client-portal users accessing their own unit may bypass the public code (server-side session). Third-party sites should use slug routes for marketing data and send the access code only for booking/chat links.

Channel flags

Each data area can be enabled or disabled per unit. Flags appear in GET /public/units/s/{slug}/spa-appearance under data.channels.

{
  "data": {
    "unit_id": 42,
    "seo": {
      "public_about_text": "We are a boutique wellness studio…",
      "public_hero_lead": "Book your visit online in minutes."
    },
    "channels": {
      "customer_reviews": true,
      "public_services": true,
      "unit_profile": true,
      "service_rankings": true,
      "public_chat": true,
      "online_booking": true,
      "client_portal_enabled": true
    }
  }
}
  • public_servicesGET …/services (catalog, promotions, bestsellers)
  • unit_profileGET …/profile, GET …/opening-hours (contact, logo, gallery photos)
  • customer_reviewsGET …/customer-reviews, GET …/customer-reviews/stats
  • service_rankingsGET …/service-rankings (curated promotion and bestseller lists)
  • online_booking, public_chat, client_portal_enabled — no separate read API in this guide; use Gabito-hosted page URLs when these flags are true

spa-appearance, page-settings, and seo-document-head are always available on slug routes (no access code). The seo block includes public_about_text (“About the salon”) and hero copy for your landing page.

Unit Public API

Read salon data for a specific unit. Use slug routes (/public/units/s/{slug}/…) — no access code required. Rate limit: 120 requests/minute per IP on unit endpoints; sitemap: 60/minute.

Path pattern: replace {slug} with a 10-character alphanumeric slug (example: AbCdEfGh12). Legacy numeric paths /public/units/{unitId}/… mirror the same resources but require X-Unit-Public-Code — except spa-appearance and seo-document-head, which are blocked without code.

Resolve slug (no code)

  • GET /public/units/s/{slug}unit_id, name, public_slug

SPA appearance & about text (no code)

  • GET /public/units/s/{slug}/spa-appearance — template, layout, SEO, channel flags

Use data.seo.public_about_text for the “About the salon” section and data.seo.public_hero_lead for a hero subtitle. Other SEO fields (meta title, description, canonical URL) are included when configured.

SEO document head (no code, SSR)

  • GET /public/units/s/{slug}/seo-document-head — HTML <title>, meta, Open Graph, JSON-LD for server-side rendering

Page settings (no code)

  • GET /public/units/s/{slug}/page-settings — public page module visibility and layout options

Unit profile & contact (channel: unit_profile, no code)

  • GET /public/units/s/{slug}/profile — name, address, contact, logo, gallery photos
  • GET /public/units/s/{slug}/opening-hours — opening hours only (also available inside profile when enabled)

Profile fields depend on disclosure flags: api_show_unit_name, api_show_address, api_show_phone, api_show_email, api_show_logo, api_show_gallery, etc. When gallery is enabled, gallery_groups returns grouped photo URLs configured in the manager panel.

GET /public/units/s/AbCdEfGh12/profile

{
  "data": {
    "unit_id": 42,
    "name": "Beauty Studio Example",
    "city": "Warsaw",
    "phone": "+48 123 456 789",
    "logo_url": "https://…",
    "gallery_groups": [
      {
        "group_index": 1,
        "auto_rotate": true,
        "interval_seconds": 5,
        "default_media_id": 101,
        "photos": [{ "id": 101, "url": "https://…", "thumb_url": "https://…", "original_name": "reception.jpg" }]
      }
    ]
  },
  "disclosure": {
    "api_show_unit_name": true,
    "api_show_address": false,
    "api_show_phone": true
  },
  "message": "…"
}

Services (channel: public_services, no code)

  • GET /public/units/s/{slug}/services — services grouped by online-visible categories

Only active services with is_visible_online=true are returned. Categories hidden online are omitted entirely. Response includes currency, groups, ungrouped_services, promotion_services, and bestseller_services (curated lists for promo and bestseller panels).

{
  "data": {
    "currency": "PLN",
    "groups": [
      {
        "service_category_id": 1,
        "name": "Treatments",
        "description": null,
        "services": [
          {
            "service_id": 10,
            "name": "Relaxing massage",
            "code": "M01",
            "description": "60-minute full body massage",
            "duration": 60,
            "net_price": "100.00",
            "gross_price": "123.00",
            "photo_url": null,
            "photo_thumb_url": null
          }
        ]
      }
    ],
    "ungrouped_services": [],
    "promotion_services": [],
    "bestseller_services": []
  },
  "disclosure": {
    "code": { "enabled": true },
    "photo": { "enabled": true },
    "description": { "enabled": true },
    "duration": { "enabled": true },
    "price": { "enabled": true },
    "employees": { "enabled": false }
  }
}

Service name and service_id are always present. Other fields appear only when enabled in disclosure settings. Service photos use photo_url / photo_thumb_url when the photo disclosure flag is on.

Service rankings (channel: service_rankings, no code)

  • GET /public/units/s/{slug}/service-rankings — manager-curated promotion and bestseller service lists

Customer reviews (channel: customer_reviews, no code)

  • GET /public/units/s/{slug}/customer-reviews?page=1&per_page=20 — paginated reviews
  • GET /public/units/s/{slug}/customer-reviews/stats — aggregate ratings

Query params: page (default 1), per_page (1–50, default 20). Review text and sub-ratings appear only when enabled in template settings.

{
  "data": [
    {
      "id": 1,
      "submitted_at": "2023-10-27T12:00:00+00:00",
      "client_first_name": "Anna",
      "employee_name": "Maria Kowalska",
      "service_names": "Massage, Facial",
      "overall_rating": 5,
      "opinion_text": "Excellent service."
    }
  ],
  "meta": { "current_page": 1, "last_page": 5, "per_page": 20, "total": 100 }
}

Legacy numeric routes (access code required)

The same resources are available at /public/units/{unitId}/profile, …/services, etc. with header X-Unit-Public-Code. Numeric spa-appearance and seo-document-head without code return 404 unit_public_numeric_route_blocked. Prefer slug routes for new integrations.

CORS (browser integrations)

The API sends CORS headers for approved origins configured server-side (CORS_ALLOWED_ORIGINS, CORS_EXTRA_ORIGINS). Production defaults include:

  • https://gabito.eu, https://www.gabito.eu
  • https://s.gabito.eu, https://www.s.gabito.eu

If you embed fetch calls from a custom domain, that origin must be allowlisted on the API server — contact kontakt@gabito.eu for enterprise CORS setup.

Server-side integrations (Node, PHP, Python cron jobs) are not subject to CORS — call the API directly with the access code header.

Rate limits & HTTP status codes

  • Unit Public API: 120 req/min per IP
  • Units sitemap: 60 req/min per IP

Exceeded limits return 429 Too Many Requests. Validation errors return 422 with field messages. Disabled channels return 403 with an error code in JSON.

Tutorial — build your own services page

Minimal flow for a static or SPA site that lists a salon's services on your domain. Slug routes need no backend proxy — call the API directly from the browser or your server.

Step 1 — configuration

const API_BASE = 'https://apis.gabito.eu/api/v1';
const PUBLIC_SLUG = 'AbCdEfGh12'; // from manager → Public unit API settings
const ACCESS_CODE = process.env.GABITO_UNIT_PUBLIC_CODE; // only for booking/chat links

Step 2 — check channels & about text

const appearance = await fetch(
  `${API_BASE}/public/units/s/${PUBLIC_SLUG}/spa-appearance`
).then(r => r.json());

const channels = appearance.data.channels;
const aboutText = appearance.data.seo?.public_about_text || '';

if (!channels.public_services) {
  // hide services section — channel disabled
}
if (channels.online_booking) {
  // show link: https://s.gabito.eu/public/unit/${PUBLIC_SLUG}/booking?public_code=${ACCESS_CODE}
}

Step 3 — fetch profile and services

const [profile, services] = await Promise.all([
  fetch(`${API_BASE}/public/units/s/${PUBLIC_SLUG}/profile`).then(r => r.json()),
  fetch(`${API_BASE}/public/units/s/${PUBLIC_SLUG}/services`).then(r => r.json()),
]);

Step 4 — render

Use profile.data for contact and gallery. Use aboutText for the “About the salon” block. Iterate services.data.groups and optionally promotion_services / bestseller_services — check services.disclosure.price.enabled before showing prices.

Step 5 — optional reviews block

const stats = await fetch(
  `${API_BASE}/public/units/s/${PUBLIC_SLUG}/customer-reviews/stats`
).then(r => r.json());

Security: slug routes expose no secrets — safe for client-side fetch. Keep the access code server-side and append it only to booking and chat deep links (?public_code=), never to marketing API calls.

Integration scenarios

  • Custom marketing website with live service catalog, promotions, bestsellers, and reviews
  • Embedded service list or review widget on Webflow, Shopify, or any existing website
  • Mobile app showing salon profile, opening hours, gallery, and service menu
  • Partner aggregators syncing read-only catalog and customer ratings
  • “Book now” / “Chat with us” / “Client portal” buttons linking to Gabito-hosted pages

Sitemap for indexable public pages: GET https://apis.gabito.eu/api/v1/public/units-sitemap.xml (lists slug URLs only).

For dedicated CORS origins, higher rate limits, or webhooks — email us.

Test the API on your own unit

Create a trial account, enable Public Unit API in the manager panel at /unit-public-api/settings, copy your public slug, and call the slug endpoints above with curl or Postman.