POST /api/v1/programs
Creates a program in the school the key belongs to. It is born a draft — it
is not visible to members until you set it active — and its slug, the URL
identity it will keep, is derived from the name unless you send one. A collision
inside the school is resolved with a numeric suffix; you never have to check
first.
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Refused |
| Requirement | Rule for this endpoint |
|---|---|
| Business role | None — composing content is an organization-role decision |
| Resource scope | The school attached to the API key |
| Resource state | Not applicable — nothing exists yet |
| Header | Required | Description |
|---|---|---|
x-api-key | yes | API key created for this school |
Idempotency-Key | yes | Stable key for this creation intention |
X-Correlation-Id | no | Caller correlation id, returned or generated |
| Field | Type | Required | Nullable | Description |
|---|---|---|---|---|
name | string | yes | no | 3 to 100 characters |
slug | string | no | yes | Up to 100 characters. Omitted, it is derived from the name |
description | string | no | yes | Up to 500 characters |
transformation | string | no | yes | Up to 500 characters — the promise the program delivers |
status is not a field of this command: a program is always born a draft.
organizationId is not one either — the school comes from the key, and sending
one changes nothing.
{
"name": "Agentic AI Developer",
"description": "Twelve weeks to ship agents in production.",
"transformation": "Ship one agent to production every week."
}curl -X POST "$COHORTE_API_BASE_URL/api/v1/programs" \
-H "x-api-key: $COHORTE_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"name":"Agentic AI Developer","transformation":"Ship one agent to production every week."}'201 Created — the program exists.200 OK — the same Idempotency-Key replayed the original creation; replayed is true.400 Bad Request — missing Idempotency-Key, invalid JSON, a name shorter than 3 characters or a field over its limit.401 Unauthorized — the key is missing, invalid, or no longer attached to a member.403 Forbidden — the bearer is not OWNER or ADMIN of this school.| Field | Type | Nullable | Description |
|---|---|---|---|
success | boolean | no | true for a successful request |
replayed | boolean | no | true when the idempotency key replayed a creation |
data.id | UUID | no | Program identifier |
data.name | string | no | Program name |
data.slug | string | no | URL identity, derived or given |
data.description | string | yes | Free description |
data.transformation | string | yes | The promise the program delivers |
data.status | string | no | draft on creation |
data.unlockMode | string | no | immediate until you change it |
data.createdAt | ISO 8601 string | no | Creation time, in UTC |
data.updatedAt | ISO 8601 string | no | Last change, in UTC |
correlationId | UUID | no | Correlation id for support |
{
"success": true,
"replayed": false,
"data": {
"id": "9f2c0a1e-4b7d-4c1a-9e3f-2a6b8c0d1e2f",
"name": "Agentic AI Developer",
"slug": "agentic-ai-developer",
"description": null,
"transformation": "Ship one agent to production every week.",
"status": "draft",
"unlockMode": "sequential",
"createdAt": "2026-09-01T09:00:00.000Z",
"updatedAt": "2026-09-01T09:00:00.000Z"
},
"correlationId": "0f6b1d92-5c3e-4a8b-9d71-3e2f4a6b8c10"
}active when the content is ready — members see nothing before that.Idempotency-Key returns the original program and
creates no second one.program.created event are written in one
transaction.