Skip to content

Chains

A Chain is an executable workflow: a graph of Bits (its steps — plain tasks or agent/twin/tool nodes) that runs when a trigger fires. Streaks, periods, signatures, and the puzzle/consistency views are run history — a successful run signs the period and bumps the streak. Configure when a chain runs with chain triggers; configure how it runs with the execution policy and its Bit-tree DAG.

Endpoints

MethodPathDescriptionAuthRate Limit
GET/chainsList all chainsJWT60/min
GET/chains/:idGet a chain (supports include* flags)JWT60/min
GET/chains/:id/streakGet current streak infoJWT60/min
POST/chainsCreate a chainJWT + Entitlement30/min
PATCH/chains/:idUpdate a chainJWT30/min
DELETE/chains/:idDelete a chainJWT30/min

Create Chain

Entitlement: productivity.chain.create

Request

FieldTypeRequiredDescription
titlestringYesChain title (1–150 chars)
descriptionstringNoDescription of the chain
chainyIdstringNoParent chainy ID
protocolTypestringNoExecution protocol: scheduled, constraint, event_driven, ai_triggered, adaptive, one_shot
requiredVerificationLevelstringNoMinimum verification level for bit completions to count: self_attested, note_provided, artifact_backed, peer_validated, ai_verified, external_source, cryptographic
targetPerPeriodintegerNoTarget completions per period (min 1)
startDatestringNoISO 8601 start date
endDatestringNoISO 8601 end date
colorHexstringNoHex color code in #RRGGBB format
isVisiblebooleanNoWhether the chain is visible (default true)
habitTypestringNoDeprecated — use protocolType instead

If you pass chainyId, use the id from Create Chainy's response as $CHAINY_ID.

Response

json
{
  "data": {
    "id": "cm5chain01",
    "title": "Daily Vocabulary Practice",
    "description": "Learn 20 new Spanish words every day",
    "chainyId": "cm5abc123",
    "protocolType": "scheduled",
    "colorHex": "#27AE60",
    "isVisible": true,
    "currentStreak": 0,
    "longestStreak": 0,
    "createdAt": "2026-03-17T10:30:00.000Z",
    "updatedAt": "2026-03-17T10:30:00.000Z"
  }
}

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/chains \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Daily Vocabulary Practice",
    "description": "Learn 20 new Spanish words every day",
    "chainyId": "'$CHAINY_ID'",
    "protocolType": "scheduled",
    "colorHex": "#27AE60"
  }'
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAINY_ID = process.env.CHAINY_ID; // from Create Chainy's response

const response = await fetch(`${BASE_URL}/chains`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Daily Vocabulary Practice",
    description: "Learn 20 new Spanish words every day",
    chainyId: CHAINY_ID,
    protocolType: "scheduled",
    colorHex: "#27AE60",
  }),
});
const { data } = await response.json();
python
import requests, os

chainy_id = os.environ["CHAINY_ID"]  # from Create Chainy's response

response = requests.post(
    f"{os.environ['BASE_URL']}/chains",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
    json={
        "title": "Daily Vocabulary Practice",
        "description": "Learn 20 new Spanish words every day",
        "chainyId": chainy_id,
        "protocolType": "scheduled",
        "colorHex": "#27AE60",
    },
)
data = response.json()["data"]

Get Chain with Computed Fields

GET /chains/:id accepts a set of boolean flags that enrich the chain object with computed statistics, period state, signature history, and streak data. Each flag is passed as its own query parameter — there is no combined include parameter.

Request

ParameterTypeDefaultDescription
includeStatsbooleantrueAdd aggregate counters (totalBits, completedBits, currentPeriodCompleted, hasTodayBit, hasCurrentWeekBit)
includeConsistencybooleantrueAdd consistency metrics. Only applied when includeStats is true.
includePeriodStatebooleanfalseAdd the currentPeriod object
includeSignatureSummarybooleanfalseAdd the periodSignaturesSummary object
includeStreakbooleanfalseAdd currentStreak, longestStreak, and lastSignedAt
fromstringISO 8601 start date to bound consistency/period computation
tostringISO 8601 end date to bound consistency/period computation

Use the id from Create Chain's response (data.id) as $CHAIN_ID.

Response

json
{
  "data": {
    "id": "cm5chain01",
    "title": "Daily Vocabulary Practice",
    "colorHex": "#27AE60",
    "status": "active",
    "totalBits": 7,
    "completedBits": 6,
    "currentPeriodCompleted": false,
    "currentPeriod": {
      "id": "cm5per001",
      "scheduleId": "cm5sch001",
      "periodStart": "2026-03-16T00:00:00.000Z",
      "periodEnd": "2026-03-16T23:59:59.000Z",
      "requiredCount": 1,
      "completedCount": 0,
      "status": "in_progress",
      "evaluatedAt": null
    },
    "currentStreak": 5,
    "longestStreak": 12,
    "lastSignedAt": "2026-03-16T18:00:00.000Z"
  }
}

To retrieve the chain's Bits, use the dedicated GET /chains/:id/bit-tree endpoint — Bits are not embedded in the chain detail response.

Code Examples

bash
curl "https://api.chainabit.com/api/v1/chains/$CHAIN_ID?includePeriodState=true&includeStreak=true" \
  -H "Authorization: Bearer $TOKEN"
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAIN_ID = process.env.CHAIN_ID; // from Create Chain's response

const params = new URLSearchParams({
  includePeriodState: "true",
  includeStreak: "true",
});

const response = await fetch(
  `${BASE_URL}/chains/${CHAIN_ID}?${params}`,
  { headers: { Authorization: `Bearer ${TOKEN}` } }
);
const { data } = await response.json();
python
import requests, os

chain_id = os.environ["CHAIN_ID"]  # from Create Chain's response

response = requests.get(
    f"{os.environ['BASE_URL']}/chains/{chain_id}",
    params={"includePeriodState": "true", "includeStreak": "true"},
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]

Get Chain Streak

Request

Reuses $CHAIN_ID from Create Chain's response.

Response

json
{
  "data": {
    "current": 5,
    "longest": 12,
    "lastCompletedAt": "2026-03-16T18:00:00.000Z",
    "startedAt": "2026-03-12T07:00:00.000Z"
  }
}

Code Examples

bash
curl https://api.chainabit.com/api/v1/chains/$CHAIN_ID/streak \
  -H "Authorization: Bearer $TOKEN"
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAIN_ID = process.env.CHAIN_ID; // from Create Chain's response

const response = await fetch(`${BASE_URL}/chains/${CHAIN_ID}/streak`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();
python
import requests, os

chain_id = os.environ["CHAIN_ID"]  # from Create Chain's response

response = requests.get(
    f"{os.environ['BASE_URL']}/chains/{chain_id}/streak",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]

Chain Periods

Periods divide a chain into time-bounded segments for tracking.

Endpoints

MethodPathDescriptionAuthRate Limit
GET/chains/:chainId/periodsList periods for a chainJWT60/min
GET/chains-periods/:idGet a specific periodJWT60/min

Reuses $CHAIN_ID from Create Chain's response.

bash
curl https://api.chainabit.com/api/v1/chains/$CHAIN_ID/periods \
  -H "Authorization: Bearer $TOKEN"
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAIN_ID = process.env.CHAIN_ID; // from Create Chain's response

const response = await fetch(`${BASE_URL}/chains/${CHAIN_ID}/periods`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();
python
import requests, os

chain_id = os.environ["CHAIN_ID"]  # from Create Chain's response

response = requests.get(
    f"{os.environ['BASE_URL']}/chains/{chain_id}/periods",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]
json
{
  "data": [
    {
      "id": "cm5per001",
      "chainId": "cm5chain01",
      "startDate": "2026-03-11T00:00:00.000Z",
      "endDate": "2026-03-17T23:59:59.000Z",
      "completionRate": 0.85,
      "totalBits": 7,
      "completedBits": 6
    }
  ],
  "meta": { "total": 1 }
}

Chain Calendar

A calendar summary view for a chain.

Endpoints

MethodPathDescriptionAuthRate Limit
GET/chains/:chainId/calendarCalendar summaryJWT30/min

Reuses $CHAIN_ID from Create Chain's response.

bash
curl https://api.chainabit.com/api/v1/chains/$CHAIN_ID/calendar \
  -H "Authorization: Bearer $TOKEN"
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAIN_ID = process.env.CHAIN_ID; // from Create Chain's response

const response = await fetch(`${BASE_URL}/chains/${CHAIN_ID}/calendar`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();
python
import requests, os

chain_id = os.environ["CHAIN_ID"]  # from Create Chain's response

response = requests.get(
    f"{os.environ['BASE_URL']}/chains/{chain_id}/calendar",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]
json
{
  "data": {
    "month": "2026-03",
    "days": {
      "2026-03-01": { "completed": 1, "total": 1, "streak": true },
      "2026-03-02": { "completed": 0, "total": 1, "streak": false },
      "2026-03-03": { "completed": 1, "total": 1, "streak": true }
    }
  }
}

Chain Signatures

Signatures capture the behavioral fingerprint of a chain period.

Endpoints

MethodPathDescriptionAuthRate Limit
GET/chains/:chainId/signaturesList signatures for a chainJWT60/min
GET/chains-signatures/by-period/:periodIdGet signature by periodJWT60/min

Reuses $CHAIN_ID from Create Chain's response.

bash
curl https://api.chainabit.com/api/v1/chains/$CHAIN_ID/signatures \
  -H "Authorization: Bearer $TOKEN"
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAIN_ID = process.env.CHAIN_ID; // from Create Chain's response

const response = await fetch(`${BASE_URL}/chains/${CHAIN_ID}/signatures`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();
python
import requests, os

chain_id = os.environ["CHAIN_ID"]  # from Create Chain's response

response = requests.get(
    f"{os.environ['BASE_URL']}/chains/{chain_id}/signatures",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]
json
{
  "data": [
    {
      "id": "cm5sig001",
      "chainId": "cm5chain01",
      "periodId": "cm5per001",
      "consistencyScore": 0.87,
      "averageCompletionTime": "14:30",
      "preferredDays": ["monday", "wednesday", "friday"],
      "createdAt": "2026-03-17T00:00:00.000Z"
    }
  ]
}

Chain Schedules

Define recurrence schedules for chains.

Endpoints

MethodPathDescriptionAuthRate Limit
GET/chains/:chainId/schedulesList schedulesJWT60/min
POST/chains/:chainId/schedulesCreate a scheduleJWT30/min
PATCH/chains-schedules/:idUpdate a scheduleJWT30/min
POST/chains-schedules/:id/deactivateDeactivate a scheduleJWT30/min

Create Schedule

Request

FieldTypeRequiredDescription
typestringYesdaily, weekly, custom
daysOfWeeknumber[]NoDays of week (0=Sun, 6=Sat) for weekly type
timeOfDaystringNoPreferred time in HH:mm format
timezonestringNoIANA timezone string

Reuses $CHAIN_ID from Create Chain's response.

Response

json
{
  "data": {
    "id": "cm5sched01",
    "chainId": "cm5chain01",
    "type": "weekly",
    "daysOfWeek": [1, 3, 5],
    "timeOfDay": "09:00",
    "timezone": "Europe/Istanbul",
    "active": true,
    "createdAt": "2026-03-17T10:00:00.000Z"
  }
}

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/chains/$CHAIN_ID/schedules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "weekly",
    "daysOfWeek": [1, 3, 5],
    "timeOfDay": "09:00",
    "timezone": "Europe/Istanbul"
  }'
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAIN_ID = process.env.CHAIN_ID; // from Create Chain's response

const response = await fetch(`${BASE_URL}/chains/${CHAIN_ID}/schedules`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    type: "weekly",
    daysOfWeek: [1, 3, 5],
    timeOfDay: "09:00",
    timezone: "Europe/Istanbul",
  }),
});
const { data } = await response.json();
python
import requests, os

chain_id = os.environ["CHAIN_ID"]  # from Create Chain's response

response = requests.post(
    f"{os.environ['BASE_URL']}/chains/{chain_id}/schedules",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
    json={
        "type": "weekly",
        "daysOfWeek": [1, 3, 5],
        "timeOfDay": "09:00",
        "timezone": "Europe/Istanbul",
    },
)
data = response.json()["data"]

Deactivate Schedule

Request

Use the id from Create Schedule's response (data.id) as $SCHEDULE_ID.

Response

json
{
  "data": {
    "id": "cm5sched01",
    "active": false,
    "updatedAt": "2026-03-17T15:00:00.000Z"
  }
}

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/chains-schedules/$SCHEDULE_ID/deactivate \
  -H "Authorization: Bearer $TOKEN"
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const SCHEDULE_ID = process.env.SCHEDULE_ID; // from Create Schedule's response

const response = await fetch(`${BASE_URL}/chains-schedules/${SCHEDULE_ID}/deactivate`, {
  method: "POST",
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();
python
import requests, os

schedule_id = os.environ["SCHEDULE_ID"]  # from Create Schedule's response

response = requests.post(
    f"{os.environ['BASE_URL']}/chains-schedules/{schedule_id}/deactivate",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]

Chain Templates

Browse, fork, and publish chain templates from the public catalog.

Endpoints

MethodPathDescriptionAuthRate Limit
GET/chains-templatesList templatesJWT60/min
GET/chains-templates/:idGet a templateJWT60/min
POST/chains-templates/:id/forkFork a template into your chainsJWT + Entitlement10/min
POST/chains-templates/:chainId/publish-templatePublish a chain as templateJWT + Entitlement10/min

Fork Template

Request

Use the id of a template from List Templates as $TEMPLATE_ID. chainyId reuses $CHAINY_ID from Create Chainy's response.

Response

json
{
  "data": {
    "id": "cm5chain02",
    "title": "Morning Routine - Forked",
    "chainyId": "cm5abc123",
    "templateId": "cm5tmpl01",
    "createdAt": "2026-03-17T10:00:00.000Z"
  }
}

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/chains-templates/$TEMPLATE_ID/fork \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "chainyId": "'$CHAINY_ID'"
  }'
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const TEMPLATE_ID = process.env.TEMPLATE_ID; // from the public templates catalog
const CHAINY_ID = process.env.CHAINY_ID; // from Create Chainy's response

const response = await fetch(`${BASE_URL}/chains-templates/${TEMPLATE_ID}/fork`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ chainyId: CHAINY_ID }),
});
const { data } = await response.json();
python
import requests, os

template_id = os.environ["TEMPLATE_ID"]  # from the public templates catalog
chainy_id = os.environ["CHAINY_ID"]  # from Create Chainy's response

response = requests.post(
    f"{os.environ['BASE_URL']}/chains-templates/{template_id}/fork",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
    json={"chainyId": chainy_id},
)
data = response.json()["data"]

Publish as Template

Request

Reuses $CHAIN_ID from Create Chain's response — the chain you're publishing.

Response

json
{
  "data": {
    "id": "cm5tmpl02",
    "title": "Daily Spanish Practice",
    "sourceChainId": "cm5chain01",
    "category": "learning",
    "published": true,
    "createdAt": "2026-03-17T10:00:00.000Z"
  }
}

Code Examples

bash
curl -X POST https://api.chainabit.com/api/v1/chains-templates/$CHAIN_ID/publish-template \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Daily Spanish Practice",
    "description": "A structured daily routine for learning Spanish vocabulary and grammar",
    "category": "learning",
    "tags": ["language", "spanish", "daily"]
  }'
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const CHAIN_ID = process.env.CHAIN_ID; // from Create Chain's response

const response = await fetch(`${BASE_URL}/chains-templates/${CHAIN_ID}/publish-template`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Daily Spanish Practice",
    description: "A structured daily routine for learning Spanish vocabulary and grammar",
    category: "learning",
    tags: ["language", "spanish", "daily"],
  }),
});
const { data } = await response.json();
python
import requests, os

chain_id = os.environ["CHAIN_ID"]  # from Create Chain's response

response = requests.post(
    f"{os.environ['BASE_URL']}/chains-templates/{chain_id}/publish-template",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
    json={
        "title": "Daily Spanish Practice",
        "description": "A structured daily routine for learning Spanish vocabulary and grammar",
        "category": "learning",
        "tags": ["language", "spanish", "daily"],
    },
)
data = response.json()["data"]

Built with purpose.