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

Returned by imsg chats, imsg group, and embedded in nested chat references in messages.

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.

#Message

Returned by imsg history, 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.

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

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

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