GET /api/v1/sales-availability
Returns the complete sales availability configuration of the organization bound to the API key: call duration, buffers, IANA time zone, the ordered closer rotation with each closer's weekly hours, dated exceptions and daily capacity. Reading it books nothing.
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Conditional — only with a business role below |
| Requirement | Rule for this endpoint |
|---|---|
| Business role | closer scoped to the organization; setter is refused |
| Resource scope | Organization attached to the API key; a closer receives only his own row |
| Participation | None |
| Resource state | A configuration has been saved at least once |
A closer receives the same document with data.closers narrowed to his own
entry: the call rules and time zone of the school, his weekly hours, his
exceptions and his capacity — never another closer's.
| Header | Required | Description |
|---|---|---|
x-api-key | yes | API key created for this organization |
X-Correlation-Id | no | Caller correlation id, returned or generated |
curl "$COHORTE_API_BASE_URL/api/v1/sales-availability" \
-H "x-api-key: $COHORTE_API_KEY"200 OK — the configuration.401 Unauthorized — missing, unknown or expired key; code is unauthorized.403 Forbidden — the bearer is not OWNER or ADMIN and holds no organization-scoped closer responsibility; code is forbidden.404 Not Found — no configuration has been saved for this organization yet; code is not_found.| Field | Type | Nullable | Description |
|---|---|---|---|
success | boolean | no | true for a successful request |
data.organizationId | UUID | no | Organization attached to the API key |
data.timeZone | string | no | School display/default IANA time zone |
data.durationMinutes | integer | no | Call length: 30, 45, 60 or 90 |
data.bufferBeforeMinutes | integer | no | Protected time before a call: 0, 5, 10, 15 or 30 |
data.bufferAfterMinutes | integer | no | Protected time after a call: 0, 5, 10, 15 or 30 |
data.rotationCursor | integer | no | Stable round-robin cursor over the rotation |
data.version | integer | no | Configuration version, increased by every replacement |
data.closers[].id | UUID | no | Rotation entry identifier |
data.closers[].userId | UUID | no | Account of the closer |
data.closers[].name | string | no | Closer display name |
data.closers[].email | string | no | Closer email address |
data.closers[].position | integer | no | Position in the rotation, 1 first |
data.closers[].active | boolean | no | false keeps the closer in the rotation but offers no slot |
data.closers[].dailyCapacity | integer | no | Maximum calls per local day, 1 to 100 |
data.closers[].weekly[].weekday | integer | no | 0 Sunday to 6 Saturday |
data.closers[].weekly[].windows[] | array | no | startMinute and endMinute, local minutes from midnight |
data.closers[].exceptions[].id | UUID | no | Exception identifier |
data.closers[].exceptions[].localDate | string | no | Local calendar date, YYYY-MM-DD |
data.closers[].exceptions[].effect | string | no | unavailable or custom_hours |
data.closers[].exceptions[].windows[] | array | no | Replacement hours for custom_hours; empty for unavailable |
correlationId | UUID | no | Correlation id for support |
{
"success": true,
"data": {
"organizationId": "5e0b7a61-0000-4000-8000-000000000000",
"timeZone": "Europe/Paris",
"durationMinutes": 45,
"bufferBeforeMinutes": 15,
"bufferAfterMinutes": 15,
"rotationCursor": 3,
"version": 7,
"closers": [
{
"id": "c3f1d8a2-0000-4000-8000-000000000000",
"userId": "11111111-1111-4111-8111-111111111111",
"name": "Sophie Laurent",
"email": "sophie@example.com",
"position": 1,
"active": true,
"dailyCapacity": 4,
"weekly": [
{ "weekday": 1, "windows": [{ "startMinute": 540, "endMinute": 720 }] }
],
"exceptions": [
{
"id": "e8a2c4f0-0000-4000-8000-000000000000",
"localDate": "2026-09-22",
"effect": "unavailable",
"windows": []
}
]
}
]
},
"correlationId": "1b9e5d33-0000-4000-8000-000000000000"
}timeZone: 540 is 09:00. They never
carry a UTC offset, so they follow daylight saving time.version before replacing the configuration: the slot preview is tied to
it.Each closer has a required IANA timeZone in the response. Weekly windows, exception dates and daily capacity use that personal zone. The school zone is used for the owner/admin preview; a closer-scoped view uses their own zone.
bookingHorizonDays is a nullable integer from 1 to 365. It limits the start of
new public bookings (page and widget) to strictly before the current instant
plus that many 24-hour days. Booking links inherit this school setting.
A null value means no horizon limit. When replacing availability, omitting the
field preserves the current limit; sending null clears it.
Existing bookings, rescheduling, operator-created bookings and internal previews
are unchanged. Historical responses without a configured limit return null.
Each closer availability accepts optional nullable bookingHorizonDays (integer
1–365). Omission preserves the stored value; null inherits the team limit.
Personal availability reads also expose teamBookingHorizonDays. The effective
public booking limit is the minimum of configured team and closer limits.
Candidates are evaluated independently: a restricted closer does not hide slots
offered by another closer. Both null means unlimited. Existing appointments,
rescheduling and operator bookings are unchanged. Prepared availability keeps
this setting when adopted by an administrator. Existing rights and idempotency apply.