Planning API
Planning API access requires an active Business workspace and your current workspace permissions. An administrator can grant Read their own schedule through the API on a person’s permissions page. This permission starts off and is independent of team Plan visibility. Administrators and managers with planning management permission retain their existing access.
Open API access in Plan, or Settings → API & Developers → Planning API access. Developers remains available to eligible employees who cannot open Plan. The help shows your usable capabilities and a copyable request with placeholder credentials.
Read your own week
Existing personal read-only sync keys work without rotation. Send your own key and workspace ID:
curl 'https://app.keito.ai/api/v2/planning/me' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Keito-Account-Id: YOUR_WORKSPACE_ID'
The default is the current week in the workspace timezone and week-start setting. All dates are civil dates, including weeks that cross daylight-saving changes. A custom range requires both from and to, valid YYYY-MM-DD dates, and at most 84 inclusive days.
curl 'https://app.keito.ai/api/v2/planning/me?from=2026-11-02&to=2026-11-08&per_page=100' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Keito-Account-Id: YOUR_WORKSPACE_ID'
Only from, to, page, per_page (1–200), and snapshot_version are accepted. Unknown, repeated, blank or identity-selection parameters such as user_id return 400.
The response contains exactly person_id, workspace_timezone, week_start, range, snapshot_version, assignments, availability, capabilities, and pagination. Assignment fields are id, integer revision, project (id, name, code), task (id, name) or null, start_date, end_date, daily_allocations, state, and link. A link is null unless you can open Plan. Daily allocations contain date and hours.
Availability contains one row for every date in the requested range: date, base_hours, available_hours, scheduled_hours, time_off_hours, is_public_holiday, and over_allocated_hours. Capabilities are own_read, manage_assignments, and webhooks; personal sync keys cannot write or register webhooks.
This projection excludes actual hours, notes, financial fields, teammates, leave types, and external leave identifiers. Other directory endpoints keep their existing visibility rules. The own-schedule permission gives no access to other people’s planning data.
Calendar calculations
Results use the same calendar calculations as Plan: effective working patterns, including alternating two-week patterns; weekly-capacity fallback; workspace public holidays; approved time off and partial-day leave; and canonical allocation rounding. Pending and rejected leave do not reduce availability. Approved leave is capped at the day’s base capacity.
Total-hours bookings are spread across their complete booking span before the requested range is clipped. A holiday outside your response range can therefore affect allocations within it. Tentative assignments and tentative projects are visible but do not consume scheduled capacity. Projects excluded from utilisation also do not consume scheduled capacity. Archived projects remain visible when they have an overlapping booking. These rules match the default Plan capacity calculation.
Pagination and reconciliation
Assignments sort by start date, then ID. Every page uses a repeatable-read database snapshot, and availability covers the entire requested range on every page. Use pagination.next_page until it is null. For subsequent pages, preserve the response’s explicit range.from, range.to, your per_page, and the original opaque snapshot_version:
GET /api/v2/planning/me?from=2026-11-02&to=2026-11-08&per_page=100&page=2&snapshot_version=TOKEN_FROM_PAGE_1
If relevant planning, calendar or projection state changes, the server returns 409 with reason: "snapshot_changed". Discard the accumulated pages and restart at page 1. The token detects changes; it does not retain an old historical snapshot. Removal of authorization or Business entitlement returns 403 before snapshot checking. Perform periodic reconciliation even when using events, because calendar configuration changes do not emit assignment events.
Assignment endpoints and roles
| Method | Endpoint | Access |
|---|---|---|
GET |
/api/v2/planning/me |
Your own schedule, with the permission above |
GET, POST |
/api/v2/assignments |
Existing planning administrators and authorized managers |
GET, PATCH, DELETE |
/api/v2/assignments/{id} |
Existing planning administrators and authorized managers |
GET |
/api/v2/placeholders |
Existing planning read access |
GET |
/api/v2/time-off |
Existing planning read access; includes administrative leave metadata |
GET |
/api/v2/capacity |
Existing planning read access |
GET, POST |
/api/v2/webhooks |
Administrator only |
DELETE |
/api/v2/webhooks/{id} |
Administrator only |
Every capability requires Business. Managers retain their project/person boundaries. Viewer access never permits writes. A personal sync credential can call only the own-schedule planning endpoint. No assignment PUT, placeholder mutation, or public time-off mutation is advertised. Consult the OpenAPI specification for filters and exact schemas.
Opt into safe assignment writes
Requests without X-Keito-Planning-Contract keep existing behavior. To opt into contract 1, send X-Keito-Planning-Contract: 1 and an Idempotency-Key of 8–255 letters, numbers, dots, underscores or hyphens. PATCH and DELETE also require the exact strong ETag from item GET in If-Match.
curl 'https://app.keito.ai/api/v2/assignments/ASSIGNMENT_ID' \
-H 'Authorization: Bearer YOUR_FULL_ACCESS_KEY' \
-H 'Keito-Account-Id: YOUR_WORKSPACE_ID' -i
curl -X PATCH 'https://app.keito.ai/api/v2/assignments/ASSIGNMENT_ID' \
-H 'Authorization: Bearer YOUR_FULL_ACCESS_KEY' \
-H 'Keito-Account-Id: YOUR_WORKSPACE_ID' \
-H 'Content-Type: application/json' \
-H 'X-Keito-Planning-Contract: 1' \
-H 'Idempotency-Key: schedule-edit-example-001' \
-H 'If-Match: "planning-ASSIGNMENT_ID-r3"' \
--data '{"hours_per_day":4}'
Use the ETag actually returned by your GET. Item GET and contract-1 mutation responses contain assignment-owned fields and IDs, including revision, with a strong ETag. They omit the joined labels and derived tracked/planned totals in legacy collection responses. Soft-deleted items are tombstones with deleted_at and their final revision. Every assignment update, including web, series, import-related edits, restore and undo, advances the database-managed revision.
Receipts last 24 hours and are scoped to workspace, actor and key. Bind a key to one exact method, route, body, contract and precondition. Concurrent duplicates create one effect. Mutation, audit, event enqueue and completed receipt commit together. Retry an uncertain response with the identical request and key; a replay returns Idempotency-Replayed: true. Current permissions are checked again before replay. After retention expires, reconcile current state before deciding whether to issue a new request.
| Status | Action |
|---|---|
400 |
Correct malformed input, unsupported contract, key or non-specific/weak If-Match |
401 |
Replace an invalid, expired or revoked credential |
403 |
Resolve current entitlement, membership, scope or permission denial; do not retry as an outage |
409 |
Own reads: restart pagination for snapshot_changed. Writes: a key was reused for a different request |
412 |
Fetch current assignment and resolve the revision conflict; use a new key for the new decision |
428 |
Supply the required idempotency key and, for PATCH/DELETE, strong If-Match |
503 |
Safe writes are disabled; the server does not fall back to legacy execution |
Error responses include a stable top-level reason for planning entitlement, permission, contract and conflict failures. Own-read reasons include business_required, permission_required, membership_inactive, api_entitlement_required, machine_credentials_disabled, and personal_reads_disabled. Safe writes distinguish planning_entitlement_required, planning_permission_required, and planning_credential_denied. Downgrade, deactivation, revocation and permission removal take effect on subsequent requests.
Signed version-2 events
Administrators can register a public HTTPS destination through POST /api/v2/webhooks. Choose payload_version: 2 and provide a signing secret of at least 16 characters. Omit the version to preserve the legacy payload. Personal sync keys cannot register endpoints.
{
"url": "https://YOUR_PUBLIC_HOST/keito-events",
"events": ["assignment.created", "assignment.updated", "assignment.deleted"],
"payload_version": 2,
"secret": "YOUR_RANDOM_SIGNING_SECRET"
}
Version 2 contains only:
{
"id": "delivery_id",
"payload_version": 2,
"event_type": "assignment.updated",
"entity_type": "planning_assignment",
"entity_id": "assignment_id",
"revision": 4,
"occurred_at": "2026-11-02T10:00:00.000Z"
}
Assignment revisions start at 1. Time-off events use their existing zero-based version. Supported event types are assignment.created, assignment.updated, assignment.deleted, time_off.created, time_off.approved, time_off.rejected, and time_off.cancelled.
Verify HMAC-SHA256 over X-Keito-Webhook-Timestamp + "." + raw_request_body against the v1 digest in X-Keito-Webhook-Signature (t=TIMESTAMP,v1=HEX_DIGEST), using a constant-time comparison. Enforce a short timestamp tolerance and deduplicate the stable body id. Retries use the same delivery ID and a fresh signing timestamp.
Delivery is at least once, with bounded retries and durable worker leases. A response can be lost after your receiver commits, so duplicate delivery is expected. Do not rely on arrival order: retain the greatest entity revision, ignore older events and fetch current authorized state. A deletion event may arrive before an earlier update; retain its tombstone. Administrative consumers fetch the assignment item; personal consumers reconcile through /planning/me and remove assignments absent from the completed snapshot.
Destinations are checked at registration and every attempt. Unsafe IPv4/IPv6 addresses and DNS answers are rejected; connections bind to validated addresses while preserving TLS hostname verification. Redirects are refused and DNS, connection and response timeouts are enforced. Already queued version-2 delivery pauses while disabled and never silently switches to a legacy payload. New version-2 events are not enqueued while disabled; reconcile current state after recovery.