Skip to content
ILY

Family Admin API · v1

Automate your family space.

Publish photos, manage the calendar, and post to chat with one admin key — the same privacy boundaries as the ILY app.

Album · Calendar · Chat — included with Premium Max

Overview

Built for family life

Photo Album, Calendar, and Chat under one admin key on Premium Max (2,100 GB). Base URL:

http
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 a key in the ILY app

Settings → Family Admin API (owner). 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):

url
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):

prompt
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

Family Admin API keys

One rotatable key per family. Active Premium Max required. Keys may suspend with premium_max_required if Max lapses. Today a single key can upload photos, manage calendar, and post to chat. Named keys with narrower scopes (for example album-write-only or calendar-only) are planned so a leaked integration key has less blast radius.

Request headers

Headers

NameTypeDescription
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.

http
Authorization: Bearer ily_fam_KEY

Alternative:

http
X-ILY-Family-Api-Key: ily_fam_KEY

Key lifecycle

  • Create in Settings → Family Admin API (owner only)
  • Email OTP on create and regenerate
  • Plaintext secret shown once; regenerate invalidates the old secret

Example

bash
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. Exceeding a limit returns HTTP 429 with {"error":"…","code":"rate_limited"}. There is no Retry-After header today — retry with exponential backoff (start ~1s, cap ~60s).

Request limits (per rolling 60s window)

BucketLimitApplies to
Per key 120 / min Every authenticated /v1 request
Per family 300 / min All keys for that family 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

BudgetLimitNotes
Per key / hour 200 MB claimed Sum of file_size_bytes + archive_size_bytes at asset-create time
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

Upload into the album

Open an upload, add JPEG assets with signed PUT URLs, PUT to S3, then complete once to verify and publish. Photos in v1 are publish-only — no album timeline or download URLs via API.

v1 media policy

  • JPEG only — display (≤1536×2048, ≤8 MB) + thumb (exactly 1000×1000, ≤2 MB)
  • Optional archive (≤60 MB) when upload_full_res is set on the upload (all-or-nothing)
  • 1–250 assets per upload · 12h TTL · multiple open uploads allowed
  • Complete verifies JPEG headers in parallel, then publishes passers

content_hash & sizes

  • content_hash — SHA-256 of the exact display JPEG bytes you will PUT (lowercase hex, 64 characters). Hash after you finish resizing / encoding that display file. If you strip EXIF/metadata, hash the bytes after stripping. Do not hash the thumb or archive. Used for family-level dedupe and S3 keying; the server does not re-hash the object on complete.
  • file_size_bytes — byte length of the display JPEG only (claimed against upload budgets). Thumbnail size is not declared — keep thumbs ≤2 MB.
  • archive_size_bytes — required when upload_full_res; archive JPEG size only.

Open upload

POST …/uploads → upload_id

Add assets

POST …/uploads/{id}/assets (1–250) → all signed PUT URLs

PUT to S3

Upload display + thumb (+ archive). Re-sign incomplete via status if needed.

Complete

POST …/uploads/{id}/complete verifies and publishes.

POST /families/{family_id}/uploads

Open a new upload. Set upload_full_res: true to require an archive for every asset.

Example

bash
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

json
{
  "upload_id": "UPLOAD_ID",
  "upload_full_res": false,
  "status": "open",
  "expires_in": 43200
}
GET /families/{family_id}/uploads/{upload_id}/status

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}.

bash
curl -sS "https://api.ily.chat/v1/families/FAMILY_ID/uploads/UPLOAD_ID/status?include_put_urls=true" \
  -H "Authorization: Bearer ily_fam_KEY"
POST /families/{family_id}/uploads/{upload_id}/assets

Add 1–250 assets and receive all signed PUT URLs. Declare display dims ≤1536×2048. When upload_full_res, include archive_size_bytes for each asset.

Body fields (per asset)

NameTypeDescription
content_hashreqstringSHA-256 (lowercase hex) of display JPEG bytes.
file_size_bytesreqintegerDisplay JPEG size in bytes (≤8 MB). Not thumb/archive.
width_pxreqintegerDisplay width ≤1536.
height_pxreqintegerDisplay height ≤2048.
idempotency_keyreqstringStable client key; re-issue returns the same pending asset.
taken_atoptdate-timeCapture time (RFC3339).
archive_size_bytescondintegerRequired when upload_full_res; archive size ≤60 MB.
bash
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

NameTypeDescription
asset_iduuidId for status / complete results.
display_put_urluriPUT JPEG display master.
thumb_put_urluriPUT JPEG 1000×1000 thumb (size not declared; keep ≤2 MB).
archive_put_urluri | nullPresent when upload_full_res.
GET /families/{family_id}/uploads/{upload_id}/assets/{asset_id}

Single-asset status; optional include_put_urls for incomplete only.

POST /families/{family_id}/uploads/{upload_id}/complete

Verifies all pending assets (HEAD + JPEG header Range GET), publishes passers into the album, and leaves failures retryable. Idempotent for already published assets. Closes the upload when all succeed. Always inspect the per-asset assets[] array — one call can mix successes and failures.

bash
curl -sS -X POST https://api.ily.chat/v1/families/FAMILY_ID/uploads/UPLOAD_ID/complete \
  -H "Authorization: Bearer ily_fam_KEY"

Response 200 · partial success possible

json
{
  "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

statusMeaning
publishedNewly verified and written into the album this call.
already_publishedIdempotent — already in the album (same content_hash or prior complete).
failedVerification failed; see error. Fix bytes / re-PUT, then complete again.
pendingStill 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.

POST /families/{family_id}/uploads/{upload_id}/cancel

Abandon the upload and clean up staging.

bash
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-DD is 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 from…to is inclusive on both ends (starts_at >= from && starts_at <= to). A YYYY-MM-DD to only includes events at that day’s 00:00:00Z — pass an end-of-day RFC3339 if you need the whole day.
  • 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.
GET /families/{family_id}/events

List calendar events in a date range.

Path parameters

NameTypeDescription
family_idrequuidFamily id.

Query parameters

NameTypeDescription
fromreqstringInclusive range start — RFC3339 or YYYY-MM-DD (→ 00:00:00Z).
toreqstringInclusive range end — RFC3339 or YYYY-MM-DD (→ 00:00:00Z).

Example

bash
curl -sS "https://api.ily.chat/v1/families/FAMILY_ID/events?from=2026-03-01&to=2026-03-31" \
  -H "Authorization: Bearer ily_fam_KEY"

Response 200 · Event[]

json
[
  {
    "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"
  }
]
POST /families/{family_id}/events

Create a calendar event. Response includes source: "api".

Path parameters

NameTypeDescription
family_idrequuidFamily id.

Body (JSON · EventWrite)

NameTypeDescription
titlereqstringEvent title.
event_typereqstringEvent type key or id (see event-types).
starts_atreqstringRFC3339 or YYYY-MM-DD (→ midnight UTC).
ends_atoptstring | nullEnd time / date when applicable.
all_dayoptbooleanDefault true. Anchored to UTC date, not a family TZ.
notesoptstring | nullFree-text notes.
child_idsoptuuid[]Related children.
attendee_user_idsoptuuid[]Attending members.
recurrence_freqoptstring | nulldaily · weekly · monthly · yearly.
recurrence_intervaloptintegerDefault 1 (every N periods).
recurrence_by_weekdayoptinteger[] | nullISO weekdays: 1=Monday … 7=Sunday. Values outside 1–7 are ignored.
recurrence_untiloptstring | nullRecurrence end bound (UTC).
recurrence_countoptinteger | nullOccurrence count limit (1–999).

Example

bash
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

json
{
  "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"
}
GET /families/{family_id}/events/{event_id}

Fetch a single calendar event.

Path parameters

NameTypeDescription
family_idrequuidFamily id.
event_idrequuidEvent id.

Example

bash
curl -sS https://api.ily.chat/v1/families/FAMILY_ID/events/EVENT_ID \
  -H "Authorization: Bearer ily_fam_KEY"

Response 200 · Event

json
{
  "id": "EVENT_ID",
  "title": "Soccer practice",
  "event_type": "activity",
  "starts_at": "2026-03-12T00:00:00Z",
  "all_day": true,
  "source": "api"
}
PUT /families/{family_id}/events/{event_id}

Update a calendar event. Preserves source: "api" when the event originated from the API. Body uses the same EventWrite fields as create.

Path parameters

NameTypeDescription
family_idrequuidFamily id.
event_idrequuidEvent id.

Example

bash
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

json
{
  "id": "EVENT_ID",
  "title": "Soccer practice (rescheduled)",
  "starts_at": "2026-03-13T00:00:00Z",
  "source": "api"
}
DELETE /families/{family_id}/events/{event_id}

Delete a calendar event. Returns an empty body.

Path parameters

NameTypeDescription
family_idrequuidFamily id.
event_idrequuidEvent id.

Example

bash
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
HTTP/1.1 204 No Content
GET /families/{family_id}/calendar/event-types

List calendar event types (system + family custom).

Path parameters

NameTypeDescription
family_idrequuidFamily id.

Example

bash
curl -sS https://api.ily.chat/v1/families/FAMILY_ID/calendar/event-types \
  -H "Authorization: Bearer ily_fam_KEY"

Response 200 · EventType[]

json
[
  {
    "key": "activity",
    "label": "Activity",
    "color": "#1a5fc4",
    "sort_order": 10
  }
]

Response fields

NameTypeDescription
keystringStable key used in event_type.
labelstringDisplay label.
colorstringUI color.
sort_orderintegerOrdering hint.

Chat

Channels & messages

Membership-scoped. Private channels that exclude the admin stay hidden. Posted messages use source = api / sender API.

GET /families/{family_id}/channels

List chat channels the family admin can access.

Path parameters

NameTypeDescription
family_idrequuidFamily id.

Example

bash
curl -sS https://api.ily.chat/v1/families/FAMILY_ID/channels \
  -H "Authorization: Bearer ily_fam_KEY"

Response 200 · Channel[]

json
[
  {
    "id": "CHANNEL_ID",
    "name": "Family",
    "is_default": true,
    "color": null
  }
]
GET /channels/{channel_id}

Get one channel the admin is a member of.

Path parameters

NameTypeDescription
channel_idrequuidChannel id.

Example

bash
curl -sS https://api.ily.chat/v1/channels/CHANNEL_ID \
  -H "Authorization: Bearer ily_fam_KEY"

Response 200 · Channel

json
{
  "id": "CHANNEL_ID",
  "name": "Family",
  "is_default": true
}
GET /channels/{channel_id}/messages

List messages in a channel. Attached assets are metadata only (no image bytes).

Path parameters

NameTypeDescription
channel_idrequuidChannel id.

Query parameters

NameTypeDescription
limitoptintegerPage size 1–200 (default 50).
beforeoptstringRFC3339 timestamp — return messages strictly older than this created_at.

The response is a bare Message[] (newest page first). There is no next_before / has_more field — continue by setting before to the oldest created_at in the page. Stop when the page is empty or shorter than limit.

Example

bash
curl -sS "https://api.ily.chat/v1/channels/CHANNEL_ID/messages?limit=50" \
  -H "Authorization: Bearer ily_fam_KEY"

# next older page — use oldest created_at from the previous response
curl -sS "https://api.ily.chat/v1/channels/CHANNEL_ID/messages?limit=50&before=2026-03-12T16:00:00Z" \
  -H "Authorization: Bearer ily_fam_KEY"

Response 200 · Message[]

json
[
  {
    "id": "MESSAGE_ID",
    "source": "api",
    "author_display_name": "API",
    "body": "Practice moved to 4pm",
    "created_at": "2026-03-12T16:00:00Z",
    "deleted": false
  }
]
POST /channels/{channel_id}/messages

Send a message. Attributed as API (source: "api"). Provide body and/or asset_id.

Path parameters

NameTypeDescription
channel_idrequuidChannel id.

Body (JSON)

NameTypeDescription
bodyoptstringMessage text (default empty). Required if no asset.
asset_idoptuuid | nullAttach a previously uploaded image (metadata in reads).
reply_to_message_idoptuuid | nullReply target.

Example

bash
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

json
{
  "id": "MESSAGE_ID",
  "source": "api",
  "author_display_name": "API",
  "body": "Practice moved to 4pm",
  "created_at": "2026-03-12T16:00:00Z",
  "deleted": false
}
GET /channels/{channel_id}/messages/{message_id}

Fetch a single message.

Path parameters

NameTypeDescription
channel_idrequuidChannel id.
message_idrequuidMessage id.

Example

bash
curl -sS https://api.ily.chat/v1/channels/CHANNEL_ID/messages/MESSAGE_ID \
  -H "Authorization: Bearer ily_fam_KEY"

Response 200 · Message

json
{
  "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_required402Not on Premium Max / Max lapsed
invalid_api_key401Missing or unknown key
api_key_revoked401Key revoked
api_key_suspended403Key suspended
forbidden403Not allowed / membership
locked403Family locked
not_found404Unknown resource
bad_request400Validation error
plan_limit402Storage / plan quota
rate_limited429Request or upload-byte budget exceeded (no Retry-After; backoff)
conflict409Conflicting state
internal500Unexpected server error

Example error body

json
{
  "error": "Premium Max is required for the Family Admin API",
  "code": "premium_max_required"
}