Circles
A community is made of circles: groups for a topic, a team or a place, each with its own members and content. These endpoints cover the community's circles. The community itself is the organization, not a circle here.
The circle object
{
"id": 412,
"slug": "garden-swap",
"name": "Garden Swap",
"short_description": "Seeds, plants and tools",
"accessibility": "guarded",
"parent_id": 41,
"created_at": "2026-10-18T11:30:00.000+02:00",
"external_id": "fb-group-1234",
"photo_url": null
}
| Field | Type | Meaning |
|---|---|---|
id |
number | The circle's id. Use it in /circles/:id. |
slug |
text | The circle's short name in its web address. Made from the name when the circle is created. |
name |
text | The circle's name. |
short_description |
text or null |
One line under the name. |
accessibility |
text | Who can find and join it: open, guarded or invitation_only (below). |
parent_id |
number | The organization's id. |
created_at |
time | When the circle was made. |
external_id |
text or null |
Your id from the old platform, when the circle was created through the API with one. null for every other circle. |
photo_url |
text or null |
The circle's photo, as an address. null while it shows a stock photo. |
accessibility, as the app describes it:
open: public. Anyone can join. Choose this only when the admin wants the circle public.guarded: anyone can see the circle's presentation and ask to join; an admin approves. The default.invitation_only: completely hidden. People join only by invitation.
List them: GET /circles
curl "$API/circles" -H "Authorization: Bearer $KEY"
Answers 200 OK with {"data": [<circle>, …], "next_page": null}: every circle of the community, oldest first, 100 per
page (?page=2 for the next). It includes circles the admin is not a member of.
Read one: GET /circles/:id
curl "$API/circles/412" -H "Authorization: Bearer $KEY"
Answers 200 OK with the circle, or 404 not_found if it is not one of this community's circles.
Create one: POST /circles
curl "$API/circles" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"external_id": "fb-group-1234", "name": "Garden Swap", "short_description": "Seeds, plants and tools", "accessibility": "guarded"}'
| Field | Rules |
|---|---|
name |
Required. Unique on all of Zen Social, regardless of capitals, and so is the web address made from it: if "Book Club" exists anywhere, "Book Club!" is taken too. On a clash, ask the admin for a more specific name (for example with the community's or the town's name in it). |
short_description |
Optional text. |
accessibility |
open, guarded or invitation_only. Default guarded. |
external_id |
Your id for this circle on the old platform. Always send it (see re-runs). |
photo / photo_url |
Optional photo, exactly as for the organization. |
The slug, the kind of circle and the parent are not offered: the slug follows the name, and every circle you create belongs to this community.
The admin who made the key becomes the circle's owner, and its only member. Nobody else is added and nobody is told: members join in the app.
Answers:
201 Createdwith the new circle.200 OKwith the circle, when a circle with thisexternal_idalready exists. It is updated with what you sent and keeps its id.409 record_deleted: the circle with thisexternal_idwas deleted. It is not made again. Skip it and report it.409 changed_since_import: someone changed that circle in the app since your last write. It was not overwritten. Skip it and report it.409 conflict: the same create was sent twice at once. Send it again, once.403 forbidden: the circle with thisexternal_idexists, but the admin is no longer its owner or admin.422 invalid, with the reasons indetails(for example{"name": ["has already been taken"]}). Nothing was made.
Change one: PATCH /circles/:id
Send only the fields you want to change: name, short_description, accessibility, photo or photo_url, with the
same rules as for a create. The slug does not change with the name, and the external_id cannot be changed.
curl -X PATCH "$API/circles/412" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"short_description": "Seeds, seedlings and garden tools"}'
Answers 200 OK with the circle, 422 invalid with the reasons, or 403 forbidden when the admin is not this circle's
owner or admin. Being an admin of the community is not enough: they must be an owner or admin of the circle itself, as
in the app. Ask them to join it in the app first.
A circle someone changed in the app keeps that mark after your PATCH, so a later re-run of its create still answers
409 changed_since_import.
Delete one: DELETE /circles/:id
Warning: this deletes the circle and everything in it, for good. Its posts, events, comments, documents, rota and other tools, and its member list all go. There is no bin and no undo.
Never send a DELETE without the admin's clear yes. First tell them the circle's name and that everything in it will be deleted, then wait for their answer.
The request must name the circle back: confirm must be the circle's slug, exactly.
curl -X DELETE "$API/circles/412" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"confirm": "garden-swap"}'
Answers:
202 Accepted, with no body: the deletion has started and finishes shortly. The circle may still show for a moment. The admin gets the app's usual notice when it is done; nobody else is told.422 confirmation_required:confirmwas missing or was not the circle's slug. Nothing was deleted.422 multiple_owners: the circle does not have exactly one owner. As in the app, only a circle with a single owner can be deleted; its owners sort that out in the app first.403 forbidden: the admin is not this circle's owner or admin.404 not_found: not one of this community's circles. The community itself cannot be deleted through the API.
A deleted circle is never brought back: a later create with its external_id answers 409 record_deleted.
Its members: GET /circles/:id/members
The members of one circle. See "One circle's members" in Members.