← All posts

Problem and fix

Concrete CMS has a REST API: what it can and cannot do

A developer smiling at his desk, code on the screen behind him and a coffee beside the keyboard.

Concrete CMS ships a REST API in core, it does more than its reputation suggests, and the obvious way to connect to it is the wrong one. A client-credentials token — an ID and a secret, no browser — returns a signed token that reads the site perfectly and then refuses every single write with a 403. The route that works is the one where you approve the integration on your own site. Everything below was found by installing Concrete 9.5.5 on a real address and driving its API; every line was a request and an answer.

·

This is the most useful thing we have written about any platform, because it is the one where the documentation and the behaviour part company most sharply — and because the fix is not a workaround. The supported route is also the better one.

The grant that works, and the one that looks like it does

Concrete offers four OAuth grants. Client credentials is the natural choice for a product talking to a site without a person present, and it appears to work: the token is issued, it is signed, and it reads public pages happily. Then every write comes back with a permission error about adding pages beneath a parent page, which reads like a configuration problem and is not.

The reason is in Concrete's own middleware: it attaches a user to the request only when the token carries a user id, and a client-credentials token has no user on it — Concrete's OAuth client entity has no service-account field to bind one to. So the request runs as the guest, who may read public pages and nothing else. That is Concrete working as designed, not a misconfiguration, and no amount of permission-tinkering will change it. The supported answer is the authorization code grant: the site owner signs into their own Concrete, approves the integration, and the token is bound to them. Their permissions become its permissions, no password reaches us, and they can revoke it from their own dashboard.

  1. In the Concrete dashboard, go to System & Settings → API and switch the API on.
  2. On the same screen, tick Authorization Code and Refresh Token. Not Client Credentials — it will appear to work and then refuse every change.
  3. Under API → Integrations, add an integration with the redirect address shown to you, and copy the Client ID and Client Secret. The secret is shown once.
  4. Paste both in. You are sent to your own site to press Approve, and brought straight back.

Two smaller traps on the way in. The API prefix is `/ccm/api/1.0` — not `v1`, which costs an hour of 404s that look exactly like the API being switched off. And scopes must be asked for explicitly when the token is requested: a token issued with no scope parameter is refused at every endpoint with a message about the endpoint being out of scope, which reads like a permissions problem and is really an empty request.

A Concrete page has no body

This is the part that reshapes everything. A Concrete page has a name, a description, a type and a template — and no body field. The content lives in blocks inside named areas, so writing text means adding a content block to an area, which on a themed page is usually the one called Main. Anything built for Concrete that assumes a body field will not work, and the failure will look like a field name being wrong.

And every content write opens a draft. Adding a block to a published page does not change the published page: it creates a new unapproved version and leaves the old one serving. The same is true of a title change. Reading the page back immediately afterwards returns the approved version, so a successful write looks as though nothing happened at all until you ask for the recent version specifically. Publishing is a separate call that approves the version. That maps onto draft-then-publish more honestly than most platforms manage — and it means your unpublished work is visible to us and ours to you, which is the right way round.

What our Concrete adapter will attempt

7 of 14, which is the lowest on our list alongside Strapi, and they are the ones that matter most. Both halves below are printed from the adapter when this page loads.

What Concrete CMS lets us ask for

  • Read content
  • Create content
  • Edit content
  • Publish content
  • Manage images and files
  • Custom content types
  • Site information

What Concrete CMS has no way to do

  • Schedule content
  • Manage categories and tags
  • Edit SEO information
  • Manage menus
  • Bulk changes
  • Manage redirects
  • Administrative information

Both lists are generated from that platform's adapter when the page loads, so they are what the software will attempt rather than what we would like to say. Bulk work is the one row that is empty for every platform: no adapter claims it yet, and the automation page says so too.

What we ran, in one session

Concrete 9.5.5 on PHP 8.3, on an address of our own. A page created, written to, retitled, published, read on the public web, and deleted again.

What we asked for What came back
Ask the site what it is Concrete 9.5.5, PHP 8.3.35.
List pages, then create one The sitemap came back newest first, paged. The new page was created.
Write a paragraph into its Main area A content block was added — and it opened version 2, unapproved. The live page was untouched.
Approve that version Published. The sentence we wrote was in the page a visitor gets.
Upload a file, then delete the page Uploaded, with an address back. The page went.

What Concrete's API does not have

Honest limit: one Concrete bug is worth naming, because anything built against this API has to work around it. A page created with a description comes back with the description empty — the create call accepts the field, answers 200, and drops it, because the controller puts it under the wrong key for the function that saves it. A second call straight afterwards works. So our adapter creates and then updates rather than trusting the create, and the capability stays verified because the end state is right.

Questions we get asked about this

Do I need a plugin for Concrete?
No. Concrete's API is in core — you switch it on, make an integration, and approve it on your own site. Nothing of ours is installed.
Why does a write seem to do nothing?
Because it opened a draft version and the published page kept serving. Read the recent version rather than the approved one and the change is there, waiting for somebody to approve it.
Can an assistant change my Concrete menus?
No. Concrete builds its menus from where pages sit in the hierarchy, and its API has no way to move a page. That row is grey and will stay grey until Concrete adds one.

Where to go next