CnterDocumentationSign in

Start here

  • What Cnter is
  • Core concepts
  • Quickstart

Platform

  • Authentication
  • Conventions
  • Errors
  • Organization
  • The dashboard

Modules

  • Content Management
  • Online Ordering
  • Restaurant Ordering
  • Promotions
  • Reservations
  • Bookings
  • Contact Form

Payments

  • AgnusPAY
  • Tax

Services

  • Address autocomplete
  • Video
  • Email

Reference

  • API reference

Platform

Organization

The business itself — address, contact details and opening hours — read without a key.

A full brief for whoever builds against this.

Every business has an address, a phone number and opening hours, whatever software it runs. This endpoint serves them. It is the one thing on a customer site that should never be typed into a template: the person who knows the address has changed is the person who can change it, and this is how that change reaches the site.

The endpoint#

GET/api/client/v1/{organizationId}/organizationPublic

Business details and opening hours. No key, any origin, cached for 60 seconds.

Keyless on purpose. This data is public — it is on the front door of the building — and a footer renders in the browser on every page, so it could not keep a key secret anyway. Editing these values needs a signed-in dashboard user; reading them does not.

json
{
  "ok": true,
  "contract": 1,
  "data": {
    "organization": {
      "id": "0f2c…",
      "name": "Trattoria Roma",
      "timezone": "Europe/Amsterdam"
    },
    "profile": {
      "legal_name": "Trattoria Roma B.V.",
      "email": "hallo@trattoriaroma.nl",
      "phone": "+31 20 123 4567",
      "website": "https://trattoriaroma.nl",
      "address": {
        "street": "Dam", "house_number": "1", "address_line2": "2nd floor",
        "postal_code": "1012JS", "city": "Amsterdam", "region": "Noord-Holland",
        "country_code": "NL"
      },
      "social_links": { "instagram": "https://instagram.com/trattoriaroma" },
      "legal": { "registration_number": "84100826", "vat_number": "NL001234567B01" }
    },
    "opening_hours": {
      "timezone": "Europe/Amsterdam",
      "weekly": {
        "monday": { "enabled": true, "open": "09:00", "close": "17:00" },
        "saturday": { "enabled": false, "open": "09:00", "close": "17:00" }
      },
      "exceptions": [ { "date": "2026-12-25", "mode": "closed", "label": "Christmas" } ],
      "open_now": true,
      "today": { "date": "2026-08-22", "enabled": true, "open": "09:00", "close": "17:00" }
    }
  }
}

Absent means not shared#

Check every field before you render it

Keys are omitted rather than set to null. A merchant who has not filled in a field and a merchant who chose not to publish one look identical to you, and that is deliberate — the second is nobody else's business. Write if (profile.phone), never a label with an empty value beside it.

The whole profile object can be empty, and opening_hours can be null, for an organization that has filled in nothing. That is answered as a normal 200. Your footer still has to render — a page that throws because the owner has not got round to entering a phone number is a worse failure than a footer without one.

Which fields appear is the merchant's choice, made per field under Settings then Organization then General. Address, phone, email, socials and opening hours are published by default. The registration and VAT numbers are not: they are paperwork, not a shop window, so publishing them is an explicit decision. Code against the shape, never against what happens to be present today.

Putting it on the page#

Three places want this data and they want different parts of it. A footer takes the name, address, phone, email and social links. A contact page takes the same plus the opening hours. Invoices and terms pages take legal.registration_number and legal.vat_number, and nothing else here.

Never print a label the value cannot fill

The legal block is opt-in and absent by design for most merchants — it is paperwork, not a shop window. A page that renders "VAT:" with nothing after it looks like a broken site rather than a business that has not published its number, and the merchant is the last person to find out.

address_line2 is free text the merchant types — "Unit 4", "2nd floor". Give it its own line, and drop the line entirely when it is absent rather than rendering an empty one.

country_code is ISO 3166-1 alpha-2: NL, BE, DE. It is a code, not a label. If a visitor should see a country name, map it yourself in their language — printing NL in a footer is the sort of detail that makes a site look unfinished.

A footer, with every field checked before it is rendered
const cnter = await window.cnter.ready
const { data } = await cnter.organization.get()

const profile = data?.profile ?? {}
const address = profile.address

<footer>
  <p>{data?.organization?.name}</p>
  {address && (
    <p>
      {[address.street, address.house_number].filter(Boolean).join(' ')}<br />
      {[address.postal_code, address.city].filter(Boolean).join(' ')}
    </p>
  )}
  {profile.phone && <a href={`tel:${profile.phone}`}>{profile.phone}</a>}
  {profile.email && <a href={`mailto:${profile.email}`}>{profile.email}</a>}
  {profile.legal?.vat_number && <p>VAT {profile.legal.vat_number}</p>}
</footer>

Opening hours#

A weekly schedule plus a list of exception dates. Resolving a given calendar date takes two steps, in this order.

  1. 01

    Look for an exception on that date

    Mode closed means closed, whatever the weekday says. Mode open uses that entry's own open and close, whatever the weekday says. An exception always wins — that is what it is for: "we are normally closed on Sunday, but not this Sunday" has to be expressible.

  2. 02

    Otherwise use the weekday

    Take weekly[weekday]. When enabled is false, the venue is closed that day. The open and close times are still there — a closed day keeps its times so reopening does not mean retyping them — so do not read them as opening hours.

Times are wall-clock in the venue timezone

Not UTC, and not the visitor's zone. A widget using the browser's timezone shows an Amsterdam venue's 18:00 as 12:00 to a visitor in New York, and picks the wrong weekday entirely for anyone far enough east or west. Resolve the calendar date in timezone first, then look up the day.

open_now is computed server-side and is accurate to within the 60-second cache, which is fine for an "We're open" badge. If you need it exact — a button that must stop working one minute past closing — compute it yourself from weekly, exceptions and timezone. All three are in the response for exactly that reason.

Closing time is exclusive

At 17:00 with a 17:00 close, the venue is closed. This was a sentence at the end of the paragraph above until someone pointed out that a site still showing "open" at 17:00:00 is a bug nobody reports — the visitor assumes they misread the sign, and the venue never hears about it. It is the same rule for an exception entry.

Modules and their own hours#

These are the organization's hours. A module may keep a narrower schedule of its own — a kitchen open until 23:00 that only accepts reservations until 21:00 — and in that case the module's own endpoint returns the resolved result. Read the hours from the module you are integrating, not from here, and do not try to combine the two yourself.

Exception dates work the other way round: a closure entered here applies to every module, including one running its own weekly schedule. A public holiday is a fact about the business, so the merchant enters it once. A module can still override a single date by adding its own entry for it.

Opening hours moved here in August 2026

They used to live on the reservations settings endpoint as business_hours, which meant a venue without that module could not record them at all. That field is gone. GET /api/reservation-settings now returns an opening_hours object with the resolved schedule; a request still sending business_hours or time_slot_config.availability_date_overrides is rejected with a message naming the new home rather than being silently ignored.

Errors and limits#

StatusWhenWhat to do
400The organization id is not a uuid.A configuration mistake. Check CNTER_ORG_ID.
404No organization with that id.Same — the id is wrong, not the request.
429More than 60 requests a minute from one IP.Cache the response instead of fetching it per component.

The response carries Cache-Control: public, max-age=60. Respect it. A footer that fetches on every render will hit the limit on a busy page, and nothing here changes minute to minute.

ReservationsTable reservations, and how a narrower bookable window resolves.BookingsAppointments against the same opening hours.Restaurant orderingFulfillment, delivery areas and whether the kitchen is open.
PreviousErrorsNextThe dashboard

On this page

  • The endpoint
  • Absent means not shared
  • Putting it on the page
  • Opening hours
  • Modules and their own hours
  • Errors and limits