# 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, in later versions, move its content 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:

```sh
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](keys.md)). 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](organization.md).
- List, create, change and delete the community's circles. See [Circles](circles.md).
- Read the member list, to match people from the old platform. See [Members](members.md).

Not yet in this version: posts, events, comments and the rota arrive in later versions of this API.

## 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, or read direct messages.
- 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.

## 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 content.** Not in this version (see above).
6. **The admin invites the members**, and people sign up in the app. Only the admin can do this; you cannot.
7. **Match people.** When members have joined, read `GET /members` and match them to the people of the old platform.
   Handing old content back to the person who wrote it arrives with posts, in a later version.
8. **The rota** comes last, once its people are members. Not in this version.
9. **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. In this version imports never notify anyone, and no endpoint accepts a `notify` setting. Later
versions let a create send `"notify": true` to tell people the way the app would; see the rules below for when that is
allowed.

The one notice this version sends 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.

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. 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":"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 is no longer an owner or admin of that circle. | 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.

## 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 claiming content for a member too (when claims arrive): show the admin the name
   match you intend to make and wait for their yes.
2. **Never send `"notify": true` unless the admin asked for people to be told.** Moving content in must not ping
   anyone.
3. **Import a small sample first** (two or three items), and ask the admin to check it in the app before you run the
   rest.
4. **Always send `external_id`** on a create, 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.)
5. **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 `403`s, 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 and the community have numeric ids. Members have text ids such as `"EveABqLyto9L"`; they are not
  database numbers, and stay the same.
- **Times** are ISO 8601, with their offset from UTC.
- **Errors** answer `{"error": "<code>"}`, sometimes with `"details"`. Every code is in [Errors](errors.md).
- **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](keys.md).

## 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:

```sh
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`:

```sh
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, and to revoke the key when everything is done.

## All pages

- [Keys and access](keys.md): sending the key, expiry, what the key may do, limits.
- [The organization](organization.md): the community itself.
- [Circles](circles.md): the community's circles, including deleting one.
- [Members](members.md): the member list and one circle's members.
- [Errors](errors.md): every error code and what to do about it.
