{"openapi":"3.1.0","info":{"version":"v1","title":"Chimeling API"},"components":{"securitySchemes":{"Bearer":{"type":"http","scheme":"bearer"}},"schemas":{},"parameters":{}},"paths":{"/api/v1/devices":{"post":{"tags":["Devices"],"description":"Register a new device for the authenticated user.","security":[{"Bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"MacBook Air"},"platform":{"type":"string","enum":["darwin"],"example":"darwin"},"lastSeenAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"}},"required":["name","platform","lastSeenAt"]}}}},"responses":{"201":{"description":"The registered device","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"name":{"type":"string","example":"MacBook Air"},"platform":{"type":"string","enum":["darwin"],"example":"darwin"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"lastSeenAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"userId":{"type":"string","example":"user_6MwZdLSdY7NU2XoxcgkE98m8LJ2"}},"required":["id","name","platform","createdAt","updatedAt","lastSeenAt","userId"]}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"name":{"type":"string","example":"ZodError"},"message":{"type":"string"}},"required":["name","message"]}},"required":["success","error"]}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}}}},"get":{"tags":["Devices"],"description":"List devices of the authenticated user.","security":[{"Bearer":[]}],"responses":{"200":{"description":"The devices of the authenticated user","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"name":{"type":"string","example":"MacBook Air"},"platform":{"type":"string","enum":["darwin"],"example":"darwin"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"lastSeenAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"userId":{"type":"string","example":"user_6MwZdLSdY7NU2XoxcgkE98m8LJ2"}},"required":["id","name","platform","createdAt","updatedAt","lastSeenAt","userId"]}}}}},"401":{"description":"Unauthorized"}}}},"/api/v1/devices/{deviceId}":{"get":{"tags":["Devices"],"description":"Get a device owned by the authenticated user.","security":[{"Bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"required":true,"name":"deviceId","in":"path"}],"responses":{"200":{"description":"The device","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"name":{"type":"string","example":"MacBook Air"},"platform":{"type":"string","enum":["darwin"],"example":"darwin"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"lastSeenAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"userId":{"type":"string","example":"user_6MwZdLSdY7NU2XoxcgkE98m8LJ2"}},"required":["id","name","platform","createdAt","updatedAt","lastSeenAt","userId"]}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"name":{"type":"string","example":"ZodError"},"message":{"type":"string"}},"required":["name","message"]}},"required":["success","error"]}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}},"403":{"description":"Forbidden","content":{"text/plain":{"schema":{"type":"string"},"example":"Forbidden"}}},"404":{"description":"Device not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Device not found"}}}}},"patch":{"tags":["Devices"],"description":"Update a device owned by the authenticated user.","security":[{"Bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"required":true,"name":"deviceId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"MacBook Air"},"lastSeenAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"}}}}}},"responses":{"200":{"description":"The updated device","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"name":{"type":"string","example":"MacBook Air"},"platform":{"type":"string","enum":["darwin"],"example":"darwin"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"lastSeenAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"userId":{"type":"string","example":"user_6MwZdLSdY7NU2XoxcgkE98m8LJ2"}},"required":["id","name","platform","createdAt","updatedAt","lastSeenAt","userId"]}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"name":{"type":"string","example":"ZodError"},"message":{"type":"string"}},"required":["name","message"]}},"required":["success","error"]}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}},"403":{"description":"Forbidden","content":{"text/plain":{"schema":{"type":"string"},"example":"Forbidden"}}},"404":{"description":"Device not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Device not found"}}}}},"delete":{"tags":["Devices"],"description":"Delete a device owned by the authenticated user.","security":[{"Bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"required":true,"name":"deviceId","in":"path"}],"responses":{"204":{"description":"Device deleted"},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"name":{"type":"string","example":"ZodError"},"message":{"type":"string"}},"required":["name","message"]}},"required":["success","error"]}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}},"403":{"description":"Forbidden","content":{"text/plain":{"schema":{"type":"string"},"example":"Forbidden"}}},"404":{"description":"Device not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Device not found"}}}}}},"/api/v1/devices/{deviceId}/apps":{"get":{"tags":["Apps"],"description":"List apps registered on the device.","security":[{"Bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"required":true,"name":"deviceId","in":"path"}],"responses":{"200":{"description":"The apps registered on the device","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"8dc8f923-205d-4c77-a61c-739be43882db"},"name":{"type":"string","example":"Xcode"},"identifier":{"type":"string","example":"com.apple.dt.Xcode"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"lastOpenedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"deviceId":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"iconHash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"}},"required":["id","name","identifier","createdAt","updatedAt","lastOpenedAt","deviceId","iconHash"]}}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}},"403":{"description":"Forbidden","content":{"text/plain":{"schema":{"type":"string"},"example":"Forbidden"}}},"404":{"description":"Device not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Device not found"}}}}},"post":{"tags":["Apps"],"description":"Register an app on the device.","security":[{"Bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"required":true,"name":"deviceId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"Xcode"},"identifier":{"type":"string","example":"com.apple.dt.Xcode"},"lastOpenedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"iconHash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"}},"required":["name","identifier","lastOpenedAt","iconHash"]}}}},"responses":{"201":{"description":"The registered app","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"8dc8f923-205d-4c77-a61c-739be43882db"},"name":{"type":"string","example":"Xcode"},"identifier":{"type":"string","example":"com.apple.dt.Xcode"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"lastOpenedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"deviceId":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"iconHash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"}},"required":["id","name","identifier","createdAt","updatedAt","lastOpenedAt","deviceId","iconHash"]}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"name":{"type":"string","example":"ZodError"},"message":{"type":"string"}},"required":["name","message"]}},"required":["success","error"]}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}},"403":{"description":"Forbidden","content":{"text/plain":{"schema":{"type":"string"},"example":"Forbidden"}}},"404":{"description":"Device or app not found","content":{"text/plain":{"schema":{"type":"string"},"example":"App not found"}}},"422":{"description":"The referenced icon does not exist","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid reference"}},"required":["error"]}}}}}}},"/api/v1/devices/{deviceId}/apps/{appId}":{"get":{"tags":["Apps"],"description":"Get an app registered on the device.","security":[{"Bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"required":true,"name":"deviceId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"8dc8f923-205d-4c77-a61c-739be43882db"},"required":true,"name":"appId","in":"path"}],"responses":{"200":{"description":"The app","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"8dc8f923-205d-4c77-a61c-739be43882db"},"name":{"type":"string","example":"Xcode"},"identifier":{"type":"string","example":"com.apple.dt.Xcode"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"lastOpenedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"deviceId":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"iconHash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"}},"required":["id","name","identifier","createdAt","updatedAt","lastOpenedAt","deviceId","iconHash"]}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"name":{"type":"string","example":"ZodError"},"message":{"type":"string"}},"required":["name","message"]}},"required":["success","error"]}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}},"403":{"description":"Forbidden","content":{"text/plain":{"schema":{"type":"string"},"example":"Forbidden"}}},"404":{"description":"Device or app not found","content":{"text/plain":{"schema":{"type":"string"},"example":"App not found"}}}}},"patch":{"tags":["Apps"],"description":"Update an app registered on the device.","security":[{"Bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"required":true,"name":"deviceId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"8dc8f923-205d-4c77-a61c-739be43882db"},"required":true,"name":"appId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"Xcode"},"identifier":{"type":"string","example":"com.apple.dt.Xcode"},"lastOpenedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"iconHash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"}}}}}},"responses":{"200":{"description":"The updated app","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"8dc8f923-205d-4c77-a61c-739be43882db"},"name":{"type":"string","example":"Xcode"},"identifier":{"type":"string","example":"com.apple.dt.Xcode"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"lastOpenedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"deviceId":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"iconHash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"}},"required":["id","name","identifier","createdAt","updatedAt","lastOpenedAt","deviceId","iconHash"]}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"name":{"type":"string","example":"ZodError"},"message":{"type":"string"}},"required":["name","message"]}},"required":["success","error"]}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}},"403":{"description":"Forbidden","content":{"text/plain":{"schema":{"type":"string"},"example":"Forbidden"}}},"404":{"description":"Device or app not found","content":{"text/plain":{"schema":{"type":"string"},"example":"App not found"}}},"422":{"description":"The referenced icon does not exist","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid reference"}},"required":["error"]}}}}}},"delete":{"tags":["Apps"],"description":"Delete an app registered on the device.","security":[{"Bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"da401463-eb58-4f43-bbd6-dc3a46379e9e"},"required":true,"name":"deviceId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"8dc8f923-205d-4c77-a61c-739be43882db"},"required":true,"name":"appId","in":"path"}],"responses":{"204":{"description":"App deleted"},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"object","properties":{"name":{"type":"string","example":"ZodError"},"message":{"type":"string"}},"required":["name","message"]}},"required":["success","error"]}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}},"403":{"description":"Forbidden","content":{"text/plain":{"schema":{"type":"string"},"example":"Forbidden"}}},"404":{"description":"Device or app not found","content":{"text/plain":{"schema":{"type":"string"},"example":"App not found"}}}}}},"/api/v1/icons":{"post":{"tags":["Icons"],"description":"Register an icon. The body is the raw image bytes; the server derives the SHA-256 hash. Re-registering an existing icon is rejected — check with `HEAD /icons/{hash}` first.","security":[{"Bearer":[]}],"requestBody":{"required":true,"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/gif":{"schema":{"type":"string","format":"binary"}},"image/tiff":{"schema":{"type":"string","format":"binary"}},"image/avif":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"The registered icon","headers":{"Location":{"schema":{"type":"string","example":"/api/v1/icons/dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"},"required":true}},"content":{"application/json":{"schema":{"type":"object","properties":{"hash":{"type":"string","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"},"contentType":{"type":"string","example":"image/png"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"}},"required":["hash","contentType","createdAt","updatedAt"]}}}},"401":{"description":"Unauthorized","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized: missing session token"}}},"409":{"description":"An icon with the same hash already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Conflict"}},"required":["error"]}}}},"413":{"description":"Payload exceeds the size limit","content":{"text/plain":{"schema":{"type":"string"},"example":"Payload too large"}}},"415":{"description":"Unsupported or undetectable media type","content":{"text/plain":{"schema":{"type":"string"},"example":"Unsupported media type"}}}}}},"/api/v1/icons/{hash}":{"head":{"tags":["Icons"],"description":"Check whether an icon exists.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-f]{64}$","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"},"required":true,"name":"hash","in":"path"}],"responses":{"200":{"description":"The icon exists"},"404":{"description":"Icon not found"}}},"get":{"tags":["Icons"],"description":"Get an icon by its hash.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-f]{64}$","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"},"required":true,"name":"hash","in":"path"}],"responses":{"200":{"description":"The icon","content":{"application/json":{"schema":{"type":"object","properties":{"hash":{"type":"string","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"},"contentType":{"type":"string","example":"image/png"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-06-26T20:48:06+08:00"}},"required":["hash","contentType","createdAt","updatedAt"]},"example":{"hash":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f","contentType":"image/png","createdAt":"2026-06-26T20:48:06+08:00","updatedAt":"2026-06-26T20:48:06+08:00"}}}},"404":{"description":"Icon not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Icon not found"}}}}}},"/api/v1/icons/{hash}/image":{"get":{"tags":["Icons"],"description":"Get the image bytes of an icon. May redirect to a public object storage URL.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-f]{64}$","example":"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"},"required":true,"name":"hash","in":"path"}],"responses":{"200":{"description":"The icon image bytes","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/gif":{"schema":{"type":"string","format":"binary"}},"image/tiff":{"schema":{"type":"string","format":"binary"}},"image/avif":{"schema":{"type":"string","format":"binary"}}}},"302":{"description":"Redirect to the public object storage URL"},"304":{"description":"Not modified"},"404":{"description":"Icon not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Icon not found"}}}}}}},"webhooks":{}}