Start here: the Zen Social Community API

You are an AI assistant working for an owner or admin of one community on Zen Social. This API lets you set up that community for them and move its posts, events, comments and direct messages over from another platform. Read this whole page before you call anything, then the pages it links to. The admin is usually not a developer: explain what you are about to do in plain words, and ask before anything that cannot be undone.

What you need

The admin's message gives you two things:

  • the API address, for example https://zensocial.app/api/community/v1;
  • the key, a secret that starts with zsc_.

If you do not have both, ask the admin to make a key in Zen Social and paste you the message it shows. The button is New key, on the tab Import & AI access of their community's Manage page.

In every example, $API is the API address and $KEY is the key:

API="https://zensocial.app/api/community/v1"
KEY="zsc_…"
curl "$API/organization" -H "Authorization: Bearer $KEY"

Every request sends the key in the Authorization: Bearer … header (see Keys and access). Send request bodies as flat JSON with Content-Type: application/json: the fields go at the top level, with no wrapping key ({"name": "Book Club"}, not {"circle": {"name": "Book Club"}}). Where a photo can be uploaded, a multipart form works too.

What this version can do

  • Read and change the community itself: its name, description, colours and photo. See The organization.
  • List, create, change and delete the community's circles. See Circles.
  • Read the member list, to match people from the old platform. See Members.
  • Read, import, change and delete posts, with their original dates, authors, links and pictures. See Posts.
  • Read, import, change and delete events, with their original dates, organisers and pictures. See Events.
  • Read, import, change and delete the comments on posts and events, replies included, with their original dates and authors. See Comments.
  • Move members' old private conversations in, with their original dates and authors, and undo the move of one. This only works with a key made with the permission May import direct messages, and it is write-only. See Direct messages.
  • Hand imported posts, events and comments to the people who wrote or organised them, once they have joined, and undo that. See Authors and claims.

Not yet in this version: the rota arrives in a later version of this API. Who said they are going to an event (RSVPs) is not part of the API at all, and neither are pictures on comments.

What it cannot do

  • Invite, add, approve or remove members. People join in the app, invited by a person.
  • Send messages or emails to members. Moving old conversations in is not sending: nobody is told.
  • Read, list, search or export direct messages. There is no way to see a conversation through the API, not even one you moved in.
  • Take content out of Zen Social (export).
  • Create a new community. The community already exists; the key belongs to it.
  • Anything the admin could not do in the app. The key acts as the admin who made it, never as more. There are two exceptions. One is the posts, events and comments you import: you can still correct, delete and claim them after their author has changed (see Who may change which post, Who may change which event and Who may change which comment). The other is moving direct messages in, which an admin cannot do in the app and which needs its own key permission (see Direct messages).

How a move goes

  1. The admin makes a key and pastes its message to you. If you are reading this, that is done.
  2. Look before you change anything. Read GET /organization, GET /circles and GET /members, tell the admin what you found, and agree with them what you will do.
  3. Set up the community: name, short description, colours, photo (PATCH /organization).
  4. Create the circles (POST /circles), each with an external_id. The admin becomes the owner of every circle you create. A circle that already existed in Zen Social is different: the admin must join it in the app first, and be its owner or admin there before you can change it. You cannot join it for them.
  5. Import the posts (POST /posts), each with an external_id, and with its author: a member's id when the person has already joined, otherwise their name and your id for them. Start with a small sample. See Posts.
  6. Import the events (POST /events), the same way: each with an external_id and its organiser, and with its time_zone. Start with a small sample here too. See Events.
  7. Import the comments (POST /comments), after the posts and events they are on, and each reply after the comment it replies to. Each has an external_id and its author. Start with a small sample. See Comments.
  8. The admin invites the members, and people sign up in the app. Only the admin can do this; you cannot.
  9. Match people. When members have joined, read GET /members and match them to the people of the old platform. With the admin's yes for each match, hand their imported posts, events and comments to them with a claim. See Authors and claims.
  10. Move the direct messages (POST /conversations), after the comments and once the people in the conversations have joined and been matched. This needs a key made with the permission May import direct messages, and the community must have told its members first: ask the admin to confirm it, and to confirm your match of every person to a member. Start with a small sample. See Direct messages.
  11. The rota comes last, once its people are members. Not in this version.
  12. Finish: tell the admin to revoke the key.

Silent by default

Nothing you create or change through this API notifies members: no bell, no push message, no email, no line in anyone's daily summary. That holds for imports, changes, deletes and claims. A conversation you move in is silent too, and shows as read. A comment you add to an old post or event does not move that post or event up in the circle's activity.

The one exception is a post, event or comment create that sends "notify": true: it tells people about it the way the app would when a member posts, makes an event or comments. It needs a member as the author or organiser, an event also needs to be still to come, and the rules below say when it is allowed. Changes and deletes are always silent.

An imported event that has already happened sends no reminder, ever. An imported event still to come sends its normal reminder: 24 hours before a timed event, and at 18:00 the evening before an all-day event, to the people who answered going or maybe (not the organiser). An import has no such people, since RSVPs are not part of the API, and an event imported less than a day before it starts gets no reminder.

One notice goes to the admin, not to members: when a circle is deleted, the admin whose key it is gets the app's usual "circle deleted" notice once the deletion has finished.

Re-runs and external_id

Send an external_id with every create: your own id for the thing on the old platform, as a string (for example "fb-group-1234" or "old-site-circle-7"). Zen Social remembers it for this community, so the same create can be sent again without making a copy. A crashed or interrupted import can simply start again from the beginning. An external_id may not start with fb:: those are kept for the posts members bring from their own Facebook download.

The answer to a create tells you what happened:

Answer Meaning What to do
201 Created New. Nothing had this external_id yet. Carry on.
200 OK Already imported. It was updated with what you sent and keeps its id. Carry on.
409 {"error":"record_deleted"} It was imported once and someone has deleted it since (for a comment, its post or event counts too). It is never brought back. Skip it, and report it to the admin.
409 {"error":"changed_since_import"} Someone edited it in the app after your last write. It was not overwritten. Skip it, and report it to the admin.
409 {"error":"in_edition"} A post that is in a newsletter edition: its text can no longer change. Nothing was changed. Events never answer this. Skip it, and report it to the admin.
409 {"error":"conflict"} Two creates with the same external_id arrived at the same moment. Send that one create again, once, after the other has answered.
403 {"error":"forbidden"} The admin may no longer change it: for a circle, they are no longer its owner or admin; for a post or event, they can no longer post in one of its circles; for a comment, they can no longer comment in one of the circles of its post or event. Skip it, and report it to the admin.

The "changed in the app" mark stays: a later PATCH through the API still changes the record, but the next re-run answers 409 changed_since_import for it again. That is on purpose. A re-run must never undo what people did in the app.

Without an external_id every create makes a new record, so running the same import twice makes two of everything. A post, event or comment without one can also never be handed to its author later, and a comment without one often cannot be changed through the API either (see Authors and claims and Comments).

A re-run of a post changes only its text and link. A re-run of an event changes only its title, times, place, link, description and limits. Circles, author, creation date and pictures stay as first imported, and the re-run does not check them again. A re-run of a comment changes only its text. See Posts, Events and Comments. An event someone changed or cancelled in the app, or a comment someone edited, answers 409 changed_since_import.

Rules for assistants

Follow these every time, whatever else you are asked:

  1. Confirm with the admin before any DELETE. Say exactly what will be deleted, including everything inside it, and wait for a clear yes. Confirm before every claim too, and before undoing one: show the admin the name match you intend to make (the name on the old platform, how many posts, events and comments, and the member) and wait for their yes. Never guess between two members with the same or a similar name. The same holds for undoing the move of a conversation (rule 7).
  2. Show the admin the change before a PATCH of a post, event or comment that may be someone's own words: one whose author is now a member (its author has an id), or whose edited_at is not null (someone edited it in the app). Show it as it is and as it will be, and wait for their yes.
  3. Never send "notify": true unless the admin asked for people to be told. Moving content in must not ping anyone.
  4. Import a small sample first (two or three items), and ask the admin to check it in the app before you run the rest.
  5. Always send external_id on a create (for a post, event or comment by someone who has not joined, theirs in author too), and treat a 409 answer as "skip and report", never as a reason to delete something and try again. (The one exception is 409 conflict: send that create again, once.)
  6. Never guess a time zone for an event. If the old platform's export does not say, ask the admin where the event was held. For an all-day event, ends_at is the last day itself: a calendar's end date is usually the day after, so subtract one day (see Events).
  7. Direct messages are different from everything else here. Use them only with a key that has the permission May import direct messages, and never claim, to the admin or anyone, that you can read, list or export a conversation: you cannot, and the API never shows you one. Before the first real import, ask the admin to confirm that the members have been told that their old conversations are moving; without that yes, do not import, not even a sample of real people's conversations. Also show the admin the full mapping from each person in the old conversations to the Zen Social member you will use, and wait for an explicit yes. Never guess between similar names: a person you cannot match with certainty means that conversation is not imported. A wrong match shows a private conversation to the wrong person, and an undo cannot take back that it was seen. Move conversations only as they are in the old platform's export, between the people who were in them: never compose, reword, add or leave out a message, and never make a conversation that did not exist. Then start with a small sample the admin has chosen. If a conversation was moved wrongly, undo that one conversation (DELETE /conversations/:external_id) after the admin's yes, instead of anything broader. A 409 cannot_undo or 409 cannot_continue means people have used the conversation: leave it and tell the admin. The rule "confirm before any DELETE" holds for this undo too.
  8. Tell the admin to revoke the key when the work is done: Manage, tab Import & AI access, Revoke.

Also: when an answer surprises you (a 401, a run of 403s, a 500), stop and tell the admin instead of retrying in a loop. Never show the key in anything you write for other people.

Requests and answers

  • Lists answer {"data": […], "next_page": 2}, 100 items per page. Ask for the next one with ?page=2; next_page is null on the last page.
  • Ids: circles, posts, events, comments and the community have numeric ids. Members have text ids such as "EveABqLyto9L"; they are not database numbers, and stay the same. An author's external_id in an address is percent-encoded (see Authors and claims).
  • Times are ISO 8601, with their offset from UTC. An event's starts_at and ends_at are shown in the event's own time_zone, and you send them with an offset too (see Events).
  • Errors answer {"error": "<code>"}, sometimes with "details". Every code is in Errors.
  • Pace: at most 300 requests a minute per key. Go one request at a time; on 429, wait the seconds in the Retry-After header. See Keys and access.

Example: set up a community moved from a Facebook group

The admin wants the group's three sub-groups as circles. You have read GET /organization, GET /circles and GET /members, told the admin what is there, and agreed the plan with them.

Set the community's description and colour:

curl -X PATCH "$API/organization" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"short_description": "Neighbours on Elm Street", "brand_color": "#1D4ED8"}'

Create one circle as a sample, with the sub-group's id from Facebook as its external_id:

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"}'

It answers 201 Created with the new circle. Ask the admin to look at it in the app. When they are happy, create the other two the same way. If the run stops halfway, send all three again: the circles already made answer 200 OK instead of being made twice.

Then remind the admin to invite the members in the app. Moving the group's posts and events in comes next: see the example in Authors and claims. Its comments follow the posts and events they are on: see Comments. When everything is done, remind the admin to revoke the key.

All pages

  • Keys and access: sending the key, expiry, what the key may do, limits.
  • The organization: the community itself.
  • Circles: the community's circles, including deleting one.
  • Members: the member list and one circle's members.
  • Posts: reading, importing, changing and deleting posts.
  • Events: reading, importing, changing and deleting events.
  • Comments: reading, importing, changing and deleting the comments on posts and events.
  • Direct messages: moving members' old private conversations in, and undoing one. Write-only.
  • Authors and claims: handing imported posts, events and comments to the members who wrote or organised them, and undoing it.
  • Errors: every error code and what to do about it.