Skip to content

API Reference

Every endpoint requires X-API-Token. Routes that read decrypted artifact data also require the backup to be in the DECRYPTED state. A live unlock session (X-Backup-Session) is required only by the manifest and file-browser routes; on the artifact attachment and extract routes it is optional, and those fall back to the on-disk decrypted data when it is absent. The backend listens on port 8080 by default, and serves interactive docs at /docs unless the environment is production.

MethodPathDescription
GET/healthzReturns {"status":"ok"}. No auth. Useful for readiness probes.
GET/Returns {"name":"apple-juicer","status":"ok"}. No auth, hidden from the schema.
GET/favicon.icoReturns null. Hidden from the schema.

Base path /backups. The router requires X-API-Token on every route below.

Returns a DiscoverResponse ({ backups: [...], base_directory }). If the database holds no backups yet, the handler runs a filesystem refresh first, then returns the persisted summaries (each a BackupSummaryModel).

Rescans the filesystem via BackupRegistry.refresh(), upserting metadata for each backup under backup_paths.base_path. Returns a DiscoverResponse; refreshed entries report decryption_status = PENDING.

Body: DecryptRequest ({ "password": "<iTunes passphrase>" }). Marks the backup DECRYPTING, enqueues decrypt_backup_job with result_ttl=0 so the password does not linger in Redis, and returns immediately with a DecryptStatusResponse. Poll /decrypt-status for completion. Returns 404 if the backup is unknown.

Returns a DecryptStatusResponse (backup_id, decryption_status, decrypted_at, error). Returns 404 if the backup is unknown.

Deletes the on-disk decrypted files and truncates the indexed artifacts, then resets the backup to PENDING. Returns 204 No Content. Errors: 404 if unknown, 400 if the backup is not currently DECRYPTED, 500 if the filesystem deletion fails.

Body: UnlockRequest ({ "password": "<iTunes passphrase>" }). Response: UnlockResponse ({ "session_token": "...", "ttl_seconds": ... }). Errors: 404 if unknown, 400 if the passphrase is wrong.

Requires X-Backup-Session. Revokes the session token and returns {"status":"ok"}. Idempotent — a missing or expired token is swallowed.

Lists manifest entries for a DECRYPTED backup. Response: FileListResponse ({ items: [ManifestEntryModel], limit, offset }). Errors: 404 if unknown, 400 if not decrypted or the decrypted data is missing.

ParamInTypeDefaultDescription
domainquerystringFilter by manifest domain (e.g. AppDomain-com.apple.MobileSMS).
path_likequerystringSQL LIKE pattern for relative_path (use % wildcards).
limitqueryint100Page size.
offsetqueryint0Pagination offset.

Returns DomainListResponse ({ domains: [...] }) for the unlocked or decrypted backup. Errors: 404 if unknown, 400 if not decrypted.

Streams the contents of a single manifest entry. The handler extracts the file to a temp sandbox, serves it via FileResponse (application/octet-stream), and deletes the sandbox after the response completes. Errors: 404 if the backup or file ID is unknown, 400 if not decrypted.

Every artifact router uses the /backups prefix and requires X-API-Token. They all call get_decrypted_backup(), which returns 404 if the backup is unknown and 400 if it is not DECRYPTED. List and detail endpoints read from the indexed database and do not require X-Backup-Session.

The attachment-download and extract endpoints accept an optional X-Backup-Session. When present, the live unlock session’s filesystem is used; when absent, the on-disk decrypted data is.

MethodPathResponseNotes
GET/backups/{id}/artifacts/whatsapp/chatsWhatsAppChatListResponseChats ordered by last message.
GET/backups/{id}/artifacts/whatsapp/chats/{chat_guid}WhatsAppChatDetailResponseChat plus all messages and attachments. 404 if the chat is unknown.
GET/backups/{id}/artifacts/whatsapp/attachmentFileResponseDownload an attachment by required relative_path. Optional X-Backup-Session.
POST/backups/{id}/extract/whatsapp/{chat_guid}{ extracted_files, extracted_bytes }Copies a chat’s attachments into the decrypted dir. Optional X-Backup-Session.

The attachment endpoint tries WHATSAPP_FALLBACK_DOMAINS when the manifest lookup fails.

MethodPathResponseNotes
GET/backups/{id}/artifacts/messages/conversationsMessageConversationListResponseConversations ordered by last message.
GET/backups/{id}/artifacts/messages/conversations/{conversation_guid}MessageConversationDetailResponseConversation plus all messages and attachments. 404 if unknown.
GET/backups/{id}/artifacts/messages/attachmentFileResponseDownload by required relative_path. Optional X-Backup-Session.
POST/backups/{id}/extract/messages/{conversation_guid}{ extracted_files, extracted_bytes }Copies a conversation’s attachments into the decrypted dir. Optional X-Backup-Session.

Attachment resolution strips a leading ~ and falls back to MESSAGE_FALLBACK_DOMAINS (MediaDomain, HomeDomain).

MethodPathResponseNotes
GET/backups/{id}/artifacts/photosPhotoListResponseAssets ordered by taken_at.
GET/backups/{id}/artifacts/photos/fileFileResponse or JPEGDownload an image, or a JPEG thumbnail when ?thumb=N. Optional X-Backup-Session.
ParamInTypeDefaultDescription
limitqueryint1000List page size.
offsetqueryint0List pagination offset.
relative_pathquerystringrequiredPhoto path, for the file endpoint.
thumbqueryintReturns a JPEG thumbnail clamped to 32–1024 px. Non-image input yields 415.

Notes, Calendar, Contacts, Calls, Safari, Locations, Voicemail

Section titled “Notes, Calendar, Contacts, Calls, Safari, Locations, Voicemail”

Each is a single list endpoint returning items ordered by their natural timestamp.

MethodPathResponseOrder
GET/backups/{id}/artifacts/notesNoteListResponselast_modified_at
GET/backups/{id}/artifacts/calendar/eventsCalendarEventListResponsestarts_at, joined to its calendar
GET/backups/{id}/artifacts/contactsContactListResponselast / first name
GET/backups/{id}/artifacts/callsCallListResponseoccurred_at
GET/backups/{id}/artifacts/safariSafariVisitListResponsevisited_at (limit 1000)
GET/backups/{id}/artifacts/locationsLocationListResponserecorded_at (limit 2000)
GET/backups/{id}/artifacts/voicemailVoicemailListResponsereceived_at

Cross-artifact substring search over the backup’s artifact_search_index (search_text ILIKE %q%). Requires a DECRYPTED backup. Returns a SearchResponse ({ query, items: [SearchResultModel] }). A blank q short-circuits to an empty result without touching the index.

ParamInTypeDefaultDescription
qquerystring""Search term.
limitqueryint100Max results.

A merged, reverse-chronological view across nine timestamped artifact types (WhatsApp, iMessage, calls, calendar events, photos, Safari visits, locations, voicemails, notes). Each type contributes up to 400 candidates, which are merged, sorted newest-first, and paginated. Requires a DECRYPTED backup. Returns a TimelineResponse ({ items: [TimelineEventModel] }).

ParamInTypeDefaultDescription
limitqueryint200Page size, applied after the global merge.
offsetqueryint0Pagination offset.

Returns a MapResponse ({ items: [MapPointModel], total, capped }) — significant locations and geotagged photos unioned into one flat list of { kind: "location" | "photo", latitude, longitude, label, timestamp }. Both sources are filtered to rows that actually carry coordinates; for photos that means latitude/longitude presence, not media type, so geotagged videos are included. Requires a DECRYPTED backup. The list is capped at 5000 points (capped flags when the cap is hit).

Contact-centric correlation over the communication artifacts. Each record’s counterparty identifier (WhatsApp JID, iMessage handle, dialled number, voicemail sender) is normalized to one key, so a person’s activity groups across artifacts. Both routes require a DECRYPTED backup.

Returns a PersonListResponse ({ items: [PersonSummaryModel] }), most recent first. Each summary carries the resolved display_name, an is_contact flag, the raw identifiers seen, and per-type counts (whatsapp_count, message_count, call_count, voicemail_count, total_events).

The key is the normalized kind:value form from the list (e.g. phone:5550001111). Returns a PersonDetailResponse ({ person, contact, events, whatsapp_chat_guid, conversation_guid }): the person summary, the matching ContactModel if one exists, the merged PersonEventModel events newest first, and the linked 1:1 thread guids for deep-linking into the WhatsApp/Messages views. 404 if the key has no correlated activity.

Events cover the person’s full 1:1 WhatsApp and iMessage threads in both directions (each event carries is_from_me) plus the full call and voicemail log. Matching pivots on the thread container — a 1:1 WhatsApp chat whose counterparty JID normalizes to the key, or an iMessage conversation with a single matching handle — so outgoing replies (which have no sender) are included. Group chats are excluded.

Generates a one-page PDF (device metadata plus a count per artifact type) with FPDF. Requires a DECRYPTED backup. Returns the raw PDF (application/pdf) with a Content-Disposition: attachment; filename="report-<id>.pdf" header.

CSV export is a frontend feature — the tables generate CSV in the browser — so there is no CSV endpoint. The report router exposes only the PDF above.

Response shapes live in api/schemas.py. The ones you’ll meet most:

ModelKey fields
BackupSummaryModelid, display_name, device_name, product_version, is_encrypted, status, decryption_status, last_indexed_at, decrypted_at, size_bytes, last_modified_at, indexing_progress, indexing_total, indexing_artifact
DiscoverResponsebackups, base_directory
UnlockResponsesession_token, ttl_seconds
DecryptStatusResponsebackup_id, decryption_status, decrypted_at, error
ManifestEntryModelfile_id, domain, relative_path, size, mtime
FileListResponseitems, limit, offset
SearchResponsequery, items (artifact_type, artifact_ref, display_text, payload)
TimelineResponseitems (timestamp, artifact_type, title, subtitle)

Each artifact type has a matching *ListResponse (and, for WhatsApp and Messages, a *DetailResponse). The extract endpoints return ad-hoc JSON ({ extracted_files, extracted_bytes }) rather than a declared schema.

HeaderRequiredDescription
X-API-TokenAlwaysMatches settings.security.api_token. Set it with APPLE_JUICER_SECURITY__API_TOKEN. An invalid token returns 401.
X-Backup-SessionManifest/file routes; optional on attachment and extract routesIssued by /unlock. Required on /lock, /files, /domains, and /file/{file_id}; optional elsewhere.
  • 400 — invalid unlock password, backup not decrypted, decrypted data missing, or a delete on a backup that is not DECRYPTED.
  • 401 — missing or invalid X-API-Token, or an unknown session token where one is required.
  • 403 — the session token belongs to a different backup.
  • 404 — missing backup, chat, conversation, manifest entry, attachment, or file.
  • 415?thumb=N requested on a non-image photo.
  • 500 — a filesystem deletion failure or other unexpected worker/filesystem error.