Chainies
Chainies represent long-term objectives that group related chains together. For inviting other workspace members onto a Chainy, see Chainy Collaborators.
Endpoints
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /chainies | List all chainies | JWT | 60/min |
| GET | /chainies/:id | Get a single chainy | JWT | 60/min |
| GET | /chainies/:id/share | Get sharing info (visibility, share URL) | JWT (owner only) | 60/min |
| POST | /chainies | Create a chainy | JWT + Entitlement | 30/min |
| PATCH | /chainies/:id | Update a chainy | JWT | 30/min |
| DELETE | /chainies/:id | Delete a chainy | JWT | 30/min |
| PATCH | /chainies/:id/status | Update chainy status | JWT | 30/min |
| POST | /chainies/:id/archive | Archive a chainy | JWT | 30/min |
| POST | /chainies/:id/restore | Restore an archived chainy | JWT | 30/min |
| GET | /c/:urlShort | Resolve a shared chainy by short URL | None (public) or JWT | 60/min |
Create Chainy
Entitlement: productivity.chainy.create
Request
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Chainy title (1–150 chars) |
description | string | No | Long-form description (max 500 chars) |
category | string | No | One of: career, health, learning, relationships, finance, creativity, spirituality, other |
status | string | No | One of: active, paused, completed, abandoned, archived |
isPublic | boolean | No | Whether the chainy is publicly discoverable (default true) |
visibility | string | No | private (default) | team | public. Setting public enables share URL generation on first PATCH. |
colorHex | string | No | Hex color code in #RRGGBB format (default #10B981) |
targetDate | string | No | ISO 8601 target completion date |
instruction | string | No | Custom AI instruction for this Chainy (max 2000 chars). Injected into the AI prompt only at session start. |
Chainy responses also include an optional workspaceId field when the Chainy belongs to a workspace. Chainy Collaborators — inviting other workspace members onto this Chainy with a role — is only available when workspaceId is set.
Response
{
"data": {
"id": "cm5abc123",
"title": "Learn Spanish",
"description": "Achieve B2 fluency in Spanish by end of year",
"colorHex": "#4A90D9",
"status": "active",
"visibility": "private",
"urlShort": null,
"targetDate": "2026-12-31T00:00:00.000Z",
"createdAt": "2026-03-17T10:00:00.000Z"
}
}Save data.id — every other endpoint below refers to it as $CHAINY_ID.
Code Examples
curl -X POST https://api.chainabit.com/api/v1/chainies \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Learn Spanish",
"description": "Achieve B2 fluency in Spanish by end of year",
"colorHex": "#4A90D9",
"targetDate": "2026-12-31T00:00:00Z"
}'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const response = await fetch(`${BASE_URL}/chainies`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "Learn Spanish",
description: "Achieve B2 fluency in Spanish by end of year",
colorHex: "#4A90D9",
targetDate: "2026-12-31T00:00:00Z",
}),
});
const { data } = await response.json();import requests, os
BASE_URL = os.environ["BASE_URL"]
TOKEN = os.environ["TOKEN"]
response = requests.post(
f"{BASE_URL}/chainies",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"title": "Learn Spanish",
"description": "Achieve B2 fluency in Spanish by end of year",
"colorHex": "#4A90D9",
"targetDate": "2026-12-31T00:00:00Z",
},
)
data = response.json()["data"]Update Chainy Status
Request
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Yes | One of: active, paused, completed, abandoned, archived |
Use the id from Create Chainy's response (data.id) as $CHAINY_ID.
Response
{
"data": {
"id": "cm5abc123",
"title": "Learn Spanish",
"status": "completed",
"createdAt": "2026-03-17T10:00:00.000Z"
}
}Code Examples
curl -X PATCH https://api.chainabit.com/api/v1/chainies/$CHAINY_ID/status \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "status": "completed" }'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAINY_ID = process.env.CHAINY_ID; // from Create Chainy's data.id
const response = await fetch(`${BASE_URL}/chainies/${CHAINY_ID}/status`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "completed" }),
});
const { data } = await response.json();import requests, os
response = requests.patch(
f"{os.environ['BASE_URL']}/chainies/{os.environ['CHAINY_ID']}/status",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
json={"status": "completed"},
)
data = response.json()["data"]List Chainies
Request
Response
{
"data": [
{
"id": "cm5abc123",
"title": "Learn Spanish",
"status": "active",
"colorHex": "#4A90D9",
"createdAt": "2026-03-17T10:00:00.000Z"
}
],
"meta": { "total": 1 }
}Code Examples
curl https://api.chainabit.com/api/v1/chainies \
-H "Authorization: Bearer $TOKEN"const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const response = await fetch(`${BASE_URL}/chainies`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();import requests, os
response = requests.get(
f"{os.environ['BASE_URL']}/chainies",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]Archive / Restore
Request
Response
{
"data": {
"id": "cm5abc123",
"status": "archived",
"createdAt": "2026-03-17T10:00:00.000Z"
}
}Code Examples
# Archive
curl -X POST https://api.chainabit.com/api/v1/chainies/$CHAINY_ID/archive \
-H "Authorization: Bearer $TOKEN"
# Restore
curl -X POST https://api.chainabit.com/api/v1/chainies/$CHAINY_ID/restore \
-H "Authorization: Bearer $TOKEN"const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAINY_ID = process.env.CHAINY_ID; // from Create Chainy's data.id
// Archive
await fetch(`${BASE_URL}/chainies/${CHAINY_ID}/archive`, {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}` },
});
// Restore
await fetch(`${BASE_URL}/chainies/${CHAINY_ID}/restore`, {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}` },
});import requests, os
headers = {"Authorization": f"Bearer {os.environ['TOKEN']}"}
# Archive
requests.post(f"{os.environ['BASE_URL']}/chainies/{os.environ['CHAINY_ID']}/archive", headers=headers)
# Restore
requests.post(f"{os.environ['BASE_URL']}/chainies/{os.environ['CHAINY_ID']}/restore", headers=headers)Sharing a Chainy
Chainies support three visibility levels:
visibility | Who can access via share URL |
|---|---|
private (default) | Owner only (requires JWT) |
team | Workspace members only (requires JWT) |
public | Anyone — no authentication required |
When you set visibility to public, a short URL is generated: https://chainabit.com/c/:urlShort. Setting visibility back to private or team permanently deactivates the token — a new one is generated on next publish.
Make a Chainy Public
Set visibility: "public" in a PATCH request. A urlShort token is generated automatically on first publish.
Request
- Example body:json
{ "visibility": "public" }
Response
The response includes urlShort in the chainy object:
{
"data": {
"id": "cm5abc123",
"title": "Learn Spanish",
"visibility": "public",
"urlShort": "a3f2b9c1e4",
"createdAt": "2026-03-17T10:00:00.000Z"
}
}Code Examples
curl -X PATCH https://api.chainabit.com/api/v1/chainies/$CHAINY_ID \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "visibility": "public" }'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAINY_ID = process.env.CHAINY_ID; // from Create Chainy's data.id
const response = await fetch(`${BASE_URL}/chainies/${CHAINY_ID}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ visibility: "public" }),
});
const { data } = await response.json();
// data.urlShort — use this in your share URLimport requests, os
response = requests.patch(
f"{os.environ['BASE_URL']}/chainies/{os.environ['CHAINY_ID']}",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
json={"visibility": "public"},
)
data = response.json()["data"]
# data["urlShort"]Get Share Info
Retrieve the current visibility, short token, and full share URL for a chainy you own.
Request
Response
{
"data": {
"visibility": "public",
"urlShort": "a3f2b9c1e4",
"shareUrl": "https://chainabit.com/c/a3f2b9c1e4"
}
}Code Examples
curl https://api.chainabit.com/api/v1/chainies/$CHAINY_ID/share \
-H "Authorization: Bearer $TOKEN"const response = await fetch(`${BASE_URL}/chainies/${CHAINY_ID}/share`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();
console.log(data.shareUrl); // https://chainabit.com/c/a3f2b9c1e4import requests, os
response = requests.get(
f"{os.environ['BASE_URL']}/chainies/{os.environ['CHAINY_ID']}/share",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]
print(data["shareUrl"])Resolve a Shared Chainy
Access a shared chainy using the short URL. No authentication required for public chainies.
Request
Response
{
"data": {
"id": "cm5abc123",
"title": "Learn Spanish",
"description": "Achieve B2 fluency in Spanish by end of year",
"visibility": "public",
"urlShort": "a3f2b9c1e4",
"status": "active",
"colorHex": "#4A90D9",
"createdAt": "2026-03-17T10:00:00.000Z"
}
}Private and team chainies return 404 Not Found when accessed without a valid JWT or without the appropriate workspace membership. This applies even when the URL is correct — existence is never confirmed to unauthorized callers.
Code Examples
# Public chainy — no auth needed
curl https://api.chainabit.com/api/v1/c/a3f2b9c1e4
# Private/team chainy — JWT required
curl https://api.chainabit.com/api/v1/c/a3f2b9c1e4 \
-H "Authorization: Bearer $TOKEN"// Public
const response = await fetch(`${BASE_URL}/c/a3f2b9c1e4`);
const { data } = await response.json();
// Private/team
const response = await fetch(`${BASE_URL}/c/a3f2b9c1e4`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();import requests, os
# Public
response = requests.get(f"{os.environ['BASE_URL']}/c/a3f2b9c1e4")
# Private/team
response = requests.get(
f"{os.environ['BASE_URL']}/c/a3f2b9c1e4",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]Regenerate the Share URL
If you want to invalidate the current share URL and create a new one, pass regenerateSlug: true. The previous URL stops working immediately.
Request
- Example body:json
{ "regenerateSlug": true }
Response
Warning: Regenerating the slug immediately invalidates the previous share URL. Anyone who saved the old link will get a 404. Notify collaborators before regenerating.
Code Examples
curl -X PATCH https://api.chainabit.com/api/v1/chainies/$CHAINY_ID \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "regenerateSlug": true }'await fetch(`${BASE_URL}/chainies/${CHAINY_ID}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ regenerateSlug: true }),
});