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

Start here

Quickstart

Three values, one key, one request. The setup every Cnter integration starts from.

A full brief for whoever builds against this.

Most integrations begin the same way, whichever module you are wiring up: a base URL, an organization id and an API key. Get those three right and the rest is reading endpoints.

The business details need no key at all

If all you are doing is putting an address, a phone number or opening hours on a site, skip the key entirely — see Organization. You still need the base URL and the organization id, and they are the same two values used everywhere else.

Create an API key#

Your customer creates the key, not you. In the dashboard: Settings then Organization then API Keys. They pick the scopes and hand you the key.

The key is shown once

It is stored as a SHA-256 hash, so nobody can read it back out of the dashboard afterwards, support included. If it is lost, the only way forward is a new key.

Ask for one key carrying every scope the modules you use need, rather than a key per module. They all point at the same organization and there is nothing to gain from splitting them. See Authentication for the full scope list.

Set the environment#

.env
CNTER_BASE_URL=https://app.cnter.com
CNTER_ORG_ID=00000000-0000-0000-0000-000000000000
CNTER_API_KEY=...

These three are the whole configuration. If you are handed a second brief later, for ordering or reservations, it names the same base URL and the same organization id. Do not add a second set.

The key is server-side only

Anything with NEXT_PUBLIC_, VITE_ or a similar prefix ends up in the JavaScript bundle, where anyone can read it. A leaked key can read this customer's content and, depending on scopes, write their orders. Fetch on the server and render the result.

Make a request#

GET/api/content/typesread_content

Lists the content types in this organization. A good first call: it proves the key works and tells you what this customer actually has.

bash
curl "$CNTER_BASE_URL/api/content/types" \
  -H "X-API-Key: $CNTER_API_KEY" \
  -H "x-organization-id: $CNTER_ORG_ID"

A 401 means the key is wrong or missing. A 403 means the key is valid but does not carry the scope, or the module is switched off for this organization. Both are configuration, not code.

Do not copy that address into your code

It is the one address written out on these pages, and it is a check rather than a pattern: it proves the three values above are right before you install anything. Every example after it goes through the client, which reads the address from the manifest — so when an endpoint moves, and one did in August, your code does not. An address in your repository is a copy of where things were on the day you wrote it.

Read something real#

Next.js, server-side
import { createCnterClient } from '@cnter/client/server'

// The key must never reach the browser, so this runs on your server. The
// client knows one address — the manifest — and reads the rest from it, which
// is why nothing below names a path.
const cnter = createCnterClient({
  baseUrl: process.env.CNTER_BASE_URL!,
  organizationId: process.env.CNTER_ORG_ID!,
  apiKey: process.env.CNTER_API_KEY!,
})

export default async function Page() {
  const client = await cnter.ready()

  let items = []
  try {
    const answer = await client.content.items({ content_type: 'news', limit: 50 })
    items = answer.data ?? []
  } catch (error) {
    // Render the page without this section. A content read that fails is not a
    // reason to fail the page.
    console.error('Cnter content fetch failed:', error)
  }

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          <h2>{item.title}</h2>
          {item.data.image && <img src={item.data.image} alt="" />}
        </li>
      ))}
    </ul>
  )
}

The endpoints that need no key#

Storefront reads that a customer's browser has to make anyway are public and CORS-open. They carry the organization id in the path and expose nothing private, so they need no key and no proxy.

One line in the page, and the keyless reads have names
<script src="https://client.cnter.co/client/v1/cnter.js?org=YOUR-ORG" defer></script>
js
const cnter = await window.cnter.ready

const business = await cnter.organization.get()
const methods = await cnter.ordering.paymentMethods()

Which is which is listed per endpoint in the API reference, and every endpoint on these pages carries a chip saying whether it is public or keyed.

Where to go next#

AuthenticationScopes, headers, rate limits and expiry.Content ManagementContent types, fields and the item endpoints.ErrorsEvery status this API returns and what to do about it.
PreviousCore conceptsNextAuthentication

On this page

  • Create an API key
  • Set the environment
  • Make a request
  • Read something real
  • The endpoints that need no key
  • Where to go next