Start here
Quickstart
Three values, one key, one request. The setup every Cnter integration starts from.
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.
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.
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
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.
Make a request
/api/content/typesread_contentLists the content types in this organization. A good first call: it proves the key works and tells you what this customer actually has.
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.
Read something real
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.
<script src="https://client.cnter.co/client/v1/cnter.js?org=YOUR-ORG" defer></script>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.