Integrate

JSON output

Every read command supports --json. Output is newline-delimited JSON (NDJSON): one self-contained JSON object per line. This shape works equally well for streaming consumers and for batch readers that pipe through jq -s to materialize an array.

imsg chats --json | jq -s
imsg history --chat-id 42 --json | jq -s
imsg watch --chat-id 42 --json

Human progress, prompts, and warnings are written to stderr, not stdout. Stdout is reserved for parseable JSON so pipelines stay clean.

#Chat list item

Returned by imsg chats --json and JSON-RPC chats.list.

FieldTypeNotes
idintchat.ROWID. Stable within one DB. Preferred routing handle.
namestringDisplay name, contact match, or raw handle fallback.
display_namestringGroup title from chat.display_name. Empty for direct chats without a custom name.
contact_namestringResolved Contacts name (when permission granted).
identifierstringchat.chat_identifier. Portable.
guidstringchat.guid. Portable.
servicestringiMessage, SMS, etc.
last_message_atISO8601Newest activity time.
is_groupboolTrue when identifier or guid contains ;+;.
participantsarray of stringsExternal handles only; local user implicit.
account_idstringRouting diagnostic. Read-only.
account_loginstringRouting diagnostic. Read-only.
last_addressed_handlestringRouting diagnostic. Read-only.
unread_countintCount of unread inbound messages in the chat. Omitted on older database schemas without read state.

#Message

Returned by imsg history, imsg search, imsg watch, and the JSON-RPC messages.history and watch.subscribe notifications.

FieldTypeNotes
idintrowid. Use as the --since-rowid cursor in watch.
chat_idintAlways present. Preferred routing handle.
chat_identifierstringPortable handle.
chat_guidstringPortable GUID.
chat_namestringDisplay name for the chat.
participantsarrayExternal handles.
is_groupboolTrue for group threads.
guidstringMessage GUID. Stable across machines.
reply_to_guidstringWhen set, this message is an inline reply to that GUID.
destination_caller_idstringOutgoing only — which of your numbers Messages routed through.
balloon_bundle_idstringRaw Messages message.balloon_bundle_id, when present. URL preview rows use com.apple.messages.URLBalloonProvider, which lets consumers recognize link-preview payload rows without inferring from message text.
url_previewobjectPresent when imsg folds an Apple URL-preview balloon row into its originating text row. The outer message keeps the text row's id, guid, text, and created_at.
senderstringRaw handle. Empty for some self-sent messages.
sender_namestringResolved Contacts name when permission granted.
is_from_meboolTrue for outbound.
textstringPlain text. Recovered from attributedBody when text column is empty.
created_atISO8601Message timestamp.
attachmentsarrayPresent when --attachments is set. See below.
thread_originator_guidstringFor inline-reply threads.
pollobjectPresent for native Apple Messages Polls creation and vote rows. See below.
is_readboolInbound only — omitted when is_from_me is true.
date_readISO8601Inbound only — present when is_read is true.

Read state is a database snapshot. A watch notification includes the state present when that message row is emitted; reading it later does not generate a second message notification.

#Scheduled message

Returned by imsg scheduled list --json and in the JSON-RPC messages.scheduled result. Only future outbound rows with native scheduling state are included.

FieldTypeNotes
idintScheduled message rowid.
guidstringNative message GUID.
chat_idintLocal chat rowid.
chat_identifierstringPortable chat identifier.
chat_guidstringPortable chat GUID.
chat_namestringDisplay name or identifier fallback.
textstringScheduled message body.
servicestringMessage service, normally iMessage.
scheduled_atISO8601Requested delivery time.
schedule_typeintRaw Messages scheduling type.
schedule_stateintRaw Messages scheduling state.

#Chat background status

Returned by imsg chat-background status --chat-id <id> --json. This surface is read-only and does not require the IMCore bridge.

FieldTypeNotes
chat_idintLocal chat rowid.
chat_guidstringPortable chat GUID.
background_setboolWhether the chat properties contain a background channel.
background_channel_guidstringBackground channel identifier; omitted when no background is set.
asset_urlstringRaw background asset URL when present.
asset_idstringRaw background asset identifier when present.
object_idstringRaw background object identifier when present.
file_sizeintRaw asset size when present.
poster_versionintRaw PosterKit version when present.
communication_safety_stateintRaw communication-safety state when present.
versionintRaw background metadata version when present.
cache_pathstringCache path next to the selected Messages database.
cache_existsboolWhether cache_path exists.
watch_background_pathstringWatch-background cache path when a background channel exists.
watch_background_existsboolWhether watch_background_path exists.
latest_eventobjectNewest background set/clear event by message date, with row_id, guid, action, and date.

#URL preview coalescing

Messages may store a link send as two rows: the user's text row and a later com.apple.messages.URLBalloonProvider preview row. history, search, watch, messages.history, and watch.subscribe coalesce those rows into one logical message when the preview immediately follows a same-chat/same-sender text row containing the preview URL. In batch reads the coalesced message includes:

FieldTypeNotes
idintPreview rowid that was folded into the outer text message.
guidstringPreview row GUID.
balloon_bundle_idstringcom.apple.messages.URLBalloonProvider.
created_atISO8601Preview row timestamp.

Live watch calls do not delay the text message waiting for a preview. If the preview row arrives in a later poll after the text row was already emitted, imsg suppresses the preview row so consumers still receive one notification.

#Reaction extensions

Present on imsg watch --reactions events:

FieldTypeNotes
is_reactionbooltrue for tapback events.
reaction_typestringlove, like, dislike, laugh, emphasis, question, or a custom emoji marker.
reaction_emojistringCustom emoji, when present.
is_reaction_addbooltrue for add, false for remove.
reacted_to_guidstringThe message guid this tapback targets.

history deliberately hides reaction rows so they don't duplicate the reacted message. Reaction events only surface in the live watch stream.

#Native poll extension

Native Apple Messages polls are emitted as normal messages with a poll object. Existing message fields stay present and unchanged; poll rows often have an empty text field because the useful data is stored in the Messages extension payload. When a native created-poll payload has no title, imsg may backfill poll.question from the earliest clean caption row that replies to the poll.

FieldTypeNotes
kindstringcreated, vote, or unknown.
eventstringRoute-friendly value: imessage.poll.created, imessage.poll.voted, or imessage.poll.unknown.
poll_guidstringThe poll's source message GUID when known.
questionstringPoll title or question when decoded. For native created polls with an empty payload title, this may be backfilled from the poll's plain caption row.
optionsarrayPoll options, each with id and text.
voteobjectFirst decoded vote update, with option_id, participant, and event_type when present.
votesarrayAll decoded vote entries when the payload carries more than one.
original_guidstringFor vote rows, the original poll message GUID from associated_message_guid.
creatorstringCreator handle when the payload includes it. Creation rows may fall back to the sender handle.
participantsarrayHandles seen in decoded poll metadata.
metadataobjectRaw-safe diagnostics only: bundle id, payload byte counts, URL scheme/host, query keys, and associated message type. Raw private payload bytes are never emitted.

Example:

{
  "poll": {
    "kind": "created",
    "event": "imessage.poll.created",
    "poll_guid": "A1B2",
    "question": "Dinner?",
    "options": [
      { "id": "opt-1", "text": "Pizza" },
      { "id": "opt-2", "text": "Sushi" }
    ]
  }
}

#Attachment

Inside the attachments array on a message:

FieldTypeNotes
filenamestringStored filename.
transfer_namestringOriginal filename as sent.
utistringApple UTI.
mime_typestringBest-effort MIME.
byte_sizeintSize in bytes.
is_stickerboolSticker-pack attachments.
missingboolUnderlying file not on disk.
pathstringResolved absolute path.
converted_pathstringPresent with --convert-attachments.
converted_mime_typestringPresent with --convert-attachments.

#Statistics aggregate

Returned as one object by imsg stats --json and as the result of JSON-RPC messages.stats.

FieldTypeNotes
total_messagesintLogical messages after reaction exclusion and URL-preview coalescing.
sent_messagesintOutbound logical messages.
received_messagesintInbound logical messages.
time_zonestringResolved timezone used for dates.
chatsarrayCounts grouped by chat.
sendersarrayInbound counts grouped by sender handle.
servicesarrayCounts grouped by message service.
datesarrayCounts grouped by local YYYY-MM-DD date.
mediaobjectPresent only when --media / include_media is requested. Distinct attachment totals plus type/chat groups.

See Statistics for filtering, ordering, and timezone behavior.

#Conventions

  • Every numeric field is a JSON number. id, chat_id, and byte_size are integers; nothing requires 64-bit JSON-string encoding.
  • Times are ISO 8601 with explicit timezone (typically Z).
  • Strings that aren't applicable are omitted, not set to null. Test with field in obj, not obj.field === null.
  • Booleans are explicit true / false, never 0/1.
  • Arrays are always present when documented (possibly empty).

#Stability

The JSON schema is treated as a public API. Field renames or removals are tracked in CHANGELOG.md with a "change" or "deprecation" note and gated to a minor release.

The 0.2.0 → 0.3.0 cycle did one large rename (camelCase → snake_case). Since 0.3.0 the schema has been additive only.