Overview
Built for import & automation
Publish into Photo Album, Calendar, and Chat with named scoped keys on Premium Max (2,100 GB). This API does not serve album timelines or media download URLs. Base URL:
https://api.ily.chat/v1
- Chat mirrors membership — private channels without the admin stay invisible.
- Album uploads: open an upload, add JPEG assets, complete once to verify and publish.
- API-created calendar events and chat messages show source = API.
Examples on this page
All curl samples use the placeholder key ily_fam_KEY
and path ids like FAMILY_ID,
UPLOAD_ID,
ASSET_ID, and CHANNEL_ID.
Replace them with your real values.
Create named keys in the ILY app
Settings → Family Admin API (owner). Choose name + scopes. Email OTP required. Secret shown once.
Call /v1 with the key
Bearer ily_fam_KEY — never send app JWTs to /v1.
Use cases
What can you build?
The Family Admin API is for Premium Max households that want to automate their private family space — without giving a third party the full app login.
- Automatically upload from a NAS or home server
- Send daycare or school photos into the family album
- Convert school emails into calendar events
- Post household automation alerts into family chat
- Import scanned historical family photos
- Connect a digital photo frame or camera
- Let a nanny or preschool system publish updates without receiving full family access
AI coding tools
Build with AI
If you’re using Cursor, ChatGPT, Claude, Copilot, or a similar assistant, give it the OpenAPI specification — the same machine-readable contract this page is based on. That beats pasting random curls: the model sees every path, parameter, and response shape in one file.
1. Grab the OpenAPI file
Download or link either format (they’re equivalent):
https://ily.chat/openapi/family-admin-v1.yaml
https://ily.chat/openapi/family-admin-v1.json
2. Hand it to your AI
- In the IDE — drop the file into the project (e.g.
openapi/family-admin-v1.yaml) and @-mention it, or paste the URL and ask the agent to fetch it. - In a chat tool — attach the YAML/JSON upload, or paste the raw URL and tell the model to treat it as the source of truth.
- Keep secrets out of the chat — never paste a real
ily_fam_…key. Use placeholders and put the real key in env vars on your machine.
3. Starter prompt
Copy something like this (swap in your goal):
Use this OpenAPI as the only source of truth for the ILY Family Admin API:
https://ily.chat/openapi/family-admin-v1.yaml
Base URL: https://api.ily.chat/v1
Auth: Authorization: Bearer ily_fam_KEY (Premium Max family admin key)
Build a small script that:
1) opens an upload (POST /families/{family_id}/uploads)
2) adds JPEG assets with content_hash = SHA-256 of the display bytes
3) PUTs display + 1000×1000 thumb to the signed URLs
4) calls POST …/complete and handles partial failures in assets[]
Follow the OpenAPI field names and error codes exactly. Do not invent endpoints.
Tip: if the assistant drifts, point it back at the OpenAPI file and this docs page
(https://ily.chat/docs/api) for human-readable detail on
rate limits, content_hash, and calendar UTC behavior.
Auth
Named scoped API keys
Create up to 10 named keys per family, each with its own
scopes. Active Premium Max required. All keys for a family suspend together if Max lapses
(premium_max_required / api_key_suspended).
Available scopes: photos:write,
videos:write,
calendar:read, calendar:write,
chat:read, chat:write.
Missing a required scope returns HTTP 403 with
insufficient_scope. There is no media-read scope —
this API never returns album browse or signed view/download URLs.
Request headers
Headers
| Name | Type | Description |
|---|---|---|
| Authorizationreq* | string | Bearer ily_fam_KEY — preferred. |
| X-ILY-Family-Api-Keyalt | string | Same key without Bearer, if your client cannot set Authorization. |
| Content-Typebody | string | application/json on POST/PUT with a body. |
* Provide Authorization or X-ILY-Family-Api-Key.
Authorization: Bearer ily_fam_KEY
Alternative:
X-ILY-Family-Api-Key: ily_fam_KEY
Key lifecycle
- Create in Settings → Family Admin API (owner only) with a name and scopes
- Email OTP on create, rotate, and revoke
- Plaintext secret shown once; rotating a key keeps its id and invalidates the old secret
Example
curl -sS https://api.ily.chat/v1/families/FAMILY_ID/calendar/event-types \
-H "Authorization: Bearer ily_fam_KEY"
Limits
Rate limits & budgets
Limits are fixed-window counters
per family (all keys combined) — not rolling windows.
Each window is 60 seconds from the first hit in that bucket. Exceeding a limit returns
HTTP 429 with
{"error":"…","code":"rate_limited"} and a
Retry-After header (seconds). Honor
Retry-After; you may still use exponential backoff as a fallback.
Request limits (fixed 60s window, all keys combined)
| Bucket | Limit | Applies to |
|---|---|---|
| Per family | 120 / min | Every authenticated /v1 request (all keys combined) |
| Upload open / add assets | 20 / min | POST …/uploads, POST …/assets |
| Complete / cancel | 40 / min | POST …/complete, POST …/cancel |
| Metadata reads | 120 / min | Upload/asset status, message list, channel reads |
| Chat post | 60 / min | POST …/messages |
Upload byte budgets
| Budget | Limit | Notes |
|---|---|---|
| Per family / hour | 10 GB claimed | All keys combined. Sum of file_size_bytes + archive_size_bytes at asset-create time. Sized so sustained imports can approach the daily budget. |
| Per family / UTC day | 100 GB claimed | Same claimed sizes; resets at UTC midnight |
| Family storage | plan quota | Premium Max is 2,100 GB; over-quota returns plan_limit |
Photos & videos
Upload into the album
Open an upload, add photo and/or video assets with signed PUT URLs, PUT to S3, then complete once
to verify and publish. Media in v1 is publish-only
— no album timeline or download URLs via API. Photos need
photos:write; videos need
videos:write.
v1 media policy
- Photos — display JPEG (≤1536×2048, ≤8 MB) + thumb (exactly 1000×1000, ≤2 MB)
- Optional photo archive (≤60 MB) when
upload_full_resis set (all-or-nothing; photos only) - Videos —
video/*, recommend ≤720p long-edge, ≤2 GiB; poster thumb exactly 1000×1000 JPEG - Videos ≥16 MiB use S3 multipart (
mode: multipart+ part URLs) - 1–250 assets per upload · 12h TTL · multiple open uploads allowed
- Complete verifies pending assets in parallel, then publishes passers
content_hash & sizes
-
content_hash— Photos: SHA-256 of the exact display JPEG bytes. Videos: SHA-256 of the exact poster thumb JPEG bytes (lowercase hex, 64 characters). Verified on complete (mismatch →content_hash_mismatch). -
file_size_bytes— Photos: display JPEG size. Videos: encoded video size (claimed against upload budgets). Keep thumbs ≤2 MB. -
mime_type— defaultimage/jpeg; usevideo/*for videos (requiresduration_ms). -
archive_size_bytes— required whenupload_full_res; photo archive JPEG size only.
Open upload
POST …/uploads → upload_id
Reconcile signatures
POST …/uploads/{id}/reconcile with content_hash + metadata → soft_complete for already-imported items, PUT URLs only for needs_upload
PUT to S3
Upload only needs_upload items. Soft-complete items skip this step entirely.
Complete
POST …/uploads/{id}/complete verifies staged assets and publishes. Soft-complete items never enter the upload session.
Open a new upload. Set upload_full_res: true to require an archive for every asset.
Example
curl -sS -X POST https://api.ily.chat/v1/families/FAMILY_ID/uploads \
-H "Authorization: Bearer ily_fam_KEY" \
-H "Content-Type: application/json" \
-d '{"upload_full_res": false}'
Response 200
{
"upload_id": "UPLOAD_ID",
"upload_full_res": false,
"status": "open",
"expires_in": 43200
}
Roll-up + per-asset status. Optional ?include_put_urls=true
re-signs PUT URLs for incomplete assets only (overwrite allowed until published).
Alias: GET …/uploads/{upload_id}.
curl -sS "https://api.ily.chat/v1/families/FAMILY_ID/uploads/UPLOAD_ID/status?include_put_urls=true" \
-H "Authorization: Bearer ily_fam_KEY"
Preferred re-import step. Send content_hash + metadata for the files you intend to upload.
Hashes already in the album return soft_complete (no staging, no PUT URLs, no budget claim).
New hashes return needs_upload with signed PUT URLs.
Response fields
| Name | Type | Description |
|---|---|---|
| soft_complete | integer | Count already published — skip upload. |
| needs_upload | integer | Count with PUT URLs to upload. |
| assets[].status | string | soft_complete or needs_upload. |
| assets[].album_asset_id | uuid | Present when soft_complete. |
Add 1–250 assets and receive signed PUT URLs for new content. Known
content_hash values return soft_complete
(prefer /reconcile for bulk preflight). Declare display dims ≤1536×2048.
When upload_full_res, include archive_size_bytes for each photo asset.
Body fields (per asset)
| Name | Type | Description |
|---|---|---|
| content_hashreq | string | Photos: SHA-256 of display JPEG. Videos: SHA-256 of poster thumb JPEG. |
| file_size_bytesreq | integer | Photos: display JPEG ≤8 MB. Videos: video bytes ≤2 GiB. |
| width_pxreq | integer | Photo display width ≤1536; video width. |
| height_pxreq | integer | Photo display height ≤2048; video height. |
| idempotency_keyreq | string | Stable client key; re-issue returns the same pending asset. |
| mime_typeopt | string | Default image/jpeg; use video/* for videos. |
| duration_mscond | integer | Required for videos. |
| taken_atopt | date-time | Capture time (RFC3339). |
| archive_size_bytescond | integer | Required when upload_full_res (photos only); archive size ≤60 MB. |
curl -sS -X POST https://api.ily.chat/v1/families/FAMILY_ID/uploads/UPLOAD_ID/assets \
-H "Authorization: Bearer ily_fam_KEY" \
-H "Content-Type: application/json" \
-d '{
"assets": [{
"content_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"file_size_bytes": 420000,
"width_px": 1536,
"height_px": 2048,
"idempotency_key": "photo-001"
}]
}'
Response fields
| Name | Type | Description |
|---|---|---|
| asset_id | uuid | Id for status / complete results. |
| mode | string | single or multipart (large videos). |
| display_put_url | uri | Photos: PUT JPEG display master. |
| original_put_url | uri | Videos (single): PUT video bytes. |
| thumb_put_url | uri | PUT JPEG 1000×1000 thumb/poster (keep ≤2 MB). |
| parts | array | Videos (multipart): [{part_number, url}]. |
| archive_put_url | uri | null | Present when upload_full_res (photos). |
Single-asset status; optional include_put_urls for incomplete only (re-signs multipart parts too).
Verifies all pending assets, publishes passers into the album,
and leaves failures retryable. Idempotent for already published assets. Closes the upload when all succeed.
For multipart videos, send part ETags in the body.
Always inspect the per-asset assets[] array — one call can mix successes and failures.
curl -sS -X POST https://api.ily.chat/v1/families/FAMILY_ID/uploads/UPLOAD_ID/complete \
-H "Authorization: Bearer ily_fam_KEY" \
-H "Content-Type: application/json" \
-d '{
"assets": [{
"asset_id": "VIDEO_ASSET_ID",
"parts": [{"part_number": 1, "etag": "\"etag-from-s3\""}]
}]
}'
Response 200 · partial success possible
{
"upload_id": "UPLOAD_ID",
"status": "open",
"published": 1,
"already_published": 1,
"failed": 1,
"pending": 2,
"assets": [
{
"asset_id": "ASSET_OK",
"status": "published",
"album_asset_id": "ALBUM_ASSET_ID"
},
{
"asset_id": "ASSET_DUP",
"status": "already_published",
"album_asset_id": "EXISTING_ALBUM_ASSET"
},
{
"asset_id": "ASSET_FAIL",
"status": "failed",
"error": {
"code": "invalid_jpeg",
"message": "display object is not a valid JPEG"
}
},
{
"asset_id": "ASSET_RETRY",
"status": "pending"
}
]
}
Per-asset status
| status | Meaning |
|---|---|
| published | Newly verified and written into the album this call. |
| already_published | Idempotent — already in the album (same content_hash or prior complete). |
| failed | Verification failed; see error (including content_hash_mismatch). Fix bytes / re-PUT, then complete again. |
| pending | Still incomplete (missing object, not ready). Re-PUT if needed, then complete again. |
Upload status is completed only when every asset is published;
otherwise it stays open so you can retry.
Abandon the upload and clean up staging.
curl -sS -X POST https://api.ily.chat/v1/families/FAMILY_ID/uploads/UPLOAD_ID/cancel \
-H "Authorization: Bearer ily_fam_KEY"
Calendar
Events
Full read/write. API-created events keep source = api; the admin can still edit them in the app.
Timezone & dates
-
All datetimes are stored and returned in UTC.
A bare
YYYY-MM-DDis parsed as midnight UTC that day (so"2026-03-12"→2026-03-12T00:00:00Z). - All-day events are not anchored to a family timezone — use the UTC calendar date you intend. Prefer RFC3339 with an explicit offset when the local civil date matters across DST.
-
List range is half-open:
starts_at >= from && starts_at < to. For all of March usefrom=2026-03-01&to=2026-04-01. A bareYYYY-MM-DDtois midnight UTC that day (exclusive). - Recurrence expansion uses fixed UTC weekday numbers; daylight-saving transitions do not shift all-day UTC midnights. Timed recurring events keep their UTC clock time across DST.
List calendar events in a date range.
Path parameters
| Name | Type | Description |
|---|---|---|
| family_idreq | uuid | Family id. |
Query parameters
| Name | Type | Description |
|---|---|---|
| fromreq | string | Inclusive range start — RFC3339 or YYYY-MM-DD (→ 00:00:00Z). |
| toreq | string | Exclusive range end — RFC3339 or YYYY-MM-DD (→ 00:00:00Z). |
Example
curl -sS "https://api.ily.chat/v1/families/FAMILY_ID/events?from=2026-03-01&to=2026-04-01" \
-H "Authorization: Bearer ily_fam_KEY"
Response 200 · Event[]
[
{
"id": "EVENT_ID",
"title": "Soccer practice",
"event_type": "activity",
"starts_at": "2026-03-12T00:00:00Z",
"ends_at": null,
"all_day": true,
"notes": "Bring water bottle",
"recurrence_freq": null,
"recurrence_interval": 1,
"child_ids": [],
"attendee_user_ids": [],
"source": "api"
}
]
Create a calendar event. Response includes source: "api".
Path parameters
| Name | Type | Description |
|---|---|---|
| family_idreq | uuid | Family id. |
Body (JSON · EventWrite)
| Name | Type | Description |
|---|---|---|
| titlereq | string | Event title. |
| event_typereq | string | Event type key or id (see event-types). |
| starts_atreq | string | RFC3339 or YYYY-MM-DD (→ midnight UTC). |
| ends_atopt | string | null | End time / date when applicable. |
| all_dayopt | boolean | Default true. Anchored to UTC date, not a family TZ. |
| notesopt | string | null | Free-text notes. |
| child_idsopt | uuid[] | Related children. |
| attendee_user_idsopt | uuid[] | Attending members. |
| recurrence_freqopt | string | null | daily · weekly · monthly · yearly. |
| recurrence_intervalopt | integer | Default 1 (every N periods). |
| recurrence_by_weekdayopt | integer[] | null | ISO weekdays: 1=Monday … 7=Sunday. Values outside 1–7 return 400 bad_request. |
| recurrence_untilopt | string | null | Recurrence end bound (UTC). |
| recurrence_countopt | integer | null | Occurrence count limit (1–999). |
Example
curl -sS -X POST https://api.ily.chat/v1/families/FAMILY_ID/events \
-H "Authorization: Bearer ily_fam_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Soccer practice",
"event_type": "activity",
"starts_at": "2026-03-12",
"all_day": true,
"notes": "Bring water bottle"
}'
Response 200 · Event
{
"id": "EVENT_ID",
"title": "Soccer practice",
"event_type": "activity",
"starts_at": "2026-03-12T00:00:00Z",
"ends_at": null,
"all_day": true,
"notes": "Bring water bottle",
"recurrence_interval": 1,
"child_ids": [],
"attendee_user_ids": [],
"source": "api"
}
Fetch a single calendar event.
Path parameters
| Name | Type | Description |
|---|---|---|
| family_idreq | uuid | Family id. |
| event_idreq | uuid | Event id. |
Example
curl -sS https://api.ily.chat/v1/families/FAMILY_ID/events/EVENT_ID \
-H "Authorization: Bearer ily_fam_KEY"
Response 200 · Event
{
"id": "EVENT_ID",
"title": "Soccer practice",
"event_type": "activity",
"starts_at": "2026-03-12T00:00:00Z",
"all_day": true,
"source": "api"
}
Update a calendar event. Preserves source: "api" when the event originated from the API. Body uses the same EventWrite fields as create.
Path parameters
| Name | Type | Description |
|---|---|---|
| family_idreq | uuid | Family id. |
| event_idreq | uuid | Event id. |
Example
curl -sS -X PUT https://api.ily.chat/v1/families/FAMILY_ID/events/EVENT_ID \
-H "Authorization: Bearer ily_fam_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Soccer practice (rescheduled)",
"event_type": "activity",
"starts_at": "2026-03-13",
"all_day": true,
"notes": "Field B"
}'
Response 200 · Event
{
"id": "EVENT_ID",
"title": "Soccer practice (rescheduled)",
"starts_at": "2026-03-13T00:00:00Z",
"source": "api"
}
Delete a calendar event. Returns an empty body.
Path parameters
| Name | Type | Description |
|---|---|---|
| family_idreq | uuid | Family id. |
| event_idreq | uuid | Event id. |
Example
curl -sS -X DELETE https://api.ily.chat/v1/families/FAMILY_ID/events/EVENT_ID \
-H "Authorization: Bearer ily_fam_KEY" \
-w "\n%{http_code}\n"
Response 204 No Content
HTTP/1.1 204 No Content
List calendar event types (system + family custom).
Path parameters
| Name | Type | Description |
|---|---|---|
| family_idreq | uuid | Family id. |
Example
curl -sS https://api.ily.chat/v1/families/FAMILY_ID/calendar/event-types \
-H "Authorization: Bearer ily_fam_KEY"
Response 200 · EventType[]
[
{
"key": "activity",
"label": "Activity",
"color": "#1a5fc4",
"sort_order": 10
}
]
Response fields
| Name | Type | Description |
|---|---|---|
| key | string | Stable key used in event_type. |
| label | string | Display label. |
| color | string | UI color. |
| sort_order | integer | Ordering hint. |
Chat
Channels & messages
Membership-scoped. Private channels that exclude the admin stay hidden. Posted messages use source = api / sender API.
List chat channels the family admin can access.
Path parameters
| Name | Type | Description |
|---|---|---|
| family_idreq | uuid | Family id. |
Example
curl -sS https://api.ily.chat/v1/families/FAMILY_ID/channels \
-H "Authorization: Bearer ily_fam_KEY"
Response 200 · Channel[]
[
{
"id": "CHANNEL_ID",
"name": "Family",
"is_default": true,
"color": null
}
]
Get one channel the admin is a member of.
Path parameters
| Name | Type | Description |
|---|---|---|
| channel_idreq | uuid | Channel id. |
Example
curl -sS https://api.ily.chat/v1/channels/CHANNEL_ID \
-H "Authorization: Bearer ily_fam_KEY"
Response 200 · Channel
{
"id": "CHANNEL_ID",
"name": "Family",
"is_default": true
}
List messages in a channel. Attached assets are metadata only (no image bytes).
Path parameters
| Name | Type | Description |
|---|---|---|
| channel_idreq | uuid | Channel id. |
Query parameters
| Name | Type | Description |
|---|---|---|
| limitopt | integer | Page size 1–200 (default 50). |
| cursoropt | string | Opaque cursor from next_cursor (preferred; encodes time + message id). |
| beforeopt | string | Deprecated. Prefer cursor. Accepts opaque cursor, {rfc3339}~{message_id}, or RFC3339-only. |
Response shape:
{"messages":[…],"next_cursor":"…"} (newest page first).
Continue with cursor=<next_cursor> until
next_cursor is omitted. Opaque cursors avoid skipping
messages that share the same created_at.
Example
curl -sS "https://api.ily.chat/v1/channels/CHANNEL_ID/messages?limit=50" \
-H "Authorization: Bearer ily_fam_KEY"
# next older page — pass next_cursor from the previous response
curl -sS "https://api.ily.chat/v1/channels/CHANNEL_ID/messages?limit=50&cursor=CURSOR" \
-H "Authorization: Bearer ily_fam_KEY"
Response 200 · MessageListResponse
{
"messages": [
{
"id": "MESSAGE_ID",
"source": "api",
"author_display_name": "API",
"body": "Practice moved to 4pm",
"created_at": "2026-03-12T16:00:00Z",
"deleted": false
}
],
"next_cursor": "CURSOR"
}
Send a message. Attributed as API (source: "api"). Provide body and/or asset_id.
Path parameters
| Name | Type | Description |
|---|---|---|
| channel_idreq | uuid | Channel id. |
Body (JSON)
| Name | Type | Description |
|---|---|---|
| bodyopt | string | Message text (default empty). Required if no asset. |
| asset_idopt | uuid | null | Attach a previously uploaded image (metadata in reads). |
| reply_to_message_idopt | uuid | null | Reply target. |
Example
curl -sS -X POST https://api.ily.chat/v1/channels/CHANNEL_ID/messages \
-H "Authorization: Bearer ily_fam_KEY" \
-H "Content-Type: application/json" \
-d '{"body":"Practice moved to 4pm"}'
Response 200 · Message
{
"id": "MESSAGE_ID",
"source": "api",
"author_display_name": "API",
"body": "Practice moved to 4pm",
"created_at": "2026-03-12T16:00:00Z",
"deleted": false
}
Fetch a single message.
Path parameters
| Name | Type | Description |
|---|---|---|
| channel_idreq | uuid | Channel id. |
| message_idreq | uuid | Message id. |
Example
curl -sS https://api.ily.chat/v1/channels/CHANNEL_ID/messages/MESSAGE_ID \
-H "Authorization: Bearer ily_fam_KEY"
Response 200 · Message
{
"id": "MESSAGE_ID",
"source": "api",
"author_display_name": "API",
"body": "Practice moved to 4pm",
"created_at": "2026-03-12T16:00:00Z",
"deleted": false
}
Errors
Error codes
Errors return JSON with error (human message) and
code (stable machine code).
| Code | HTTP | When |
|---|---|---|
| premium_max_required | 402 | Not on Premium Max / Max lapsed |
| invalid_api_key | 401 | Missing or unknown key |
| api_key_revoked | 401 | Key revoked |
| api_key_suspended | 403 | Key suspended |
| insufficient_scope | 403 | Key missing a required scope |
| forbidden | 403 | Not allowed / membership |
| locked | 403 | Family locked |
| not_found | 404 | Unknown resource |
| bad_request | 400 | Validation error |
| plan_limit | 402 | Storage / plan quota |
| rate_limited | 429 | Request or upload-byte budget exceeded; includes Retry-After |
| conflict | 409 | Conflicting state |
| internal | 500 | Unexpected server error |
Example error body
{
"error": "Premium Max is required for the Family Admin API",
"code": "premium_max_required"
}