Messages
Get messages in a conversation
Requires Authorization.
Invoking this endpoint does not mark messages as read.
GET /v5/chat/conversation/{conversationId}/messageQuery (optional):
pageKey— string, optional, return messages with IDs before specified value, optionalprofile— boolean (profile=true|profile=+ any other value), optional
Response:
lastReadTimestamp— unix timestamp of recipient's side in milliseconds ornull; is not affected by your calls to mark messages as readmessages— array of Messagemetadata— objecttranslate— booleanhasSharedAlbums— booleanisInAList— booleansummary— object ornull, Wingman AI conversation summary (contactDetailsandsummariesarrays)
profile— object ifprofilequery parameter is set totrueornull
Errors:
403—{"type":"urn:gr:err:unauthorized_action","title":"Action not permitted","status":403}when the conversation is no longer reachable — the other profile blocked you, or their account was deleted.
Get a single message in a conversation
Requires Authorization.
GET /v4/chat/conversation/{conversationId}/message/{messageId}Response:
message— Message
Send a message to a conversation
Requires Authorization.
Please don't use this for spam. Be civil.
When replyToMessageId is used in the HTTP API, it appears to cause a 400 Bad Request error.
See also: Send a message to a conversation via ws
POST /v4/chat/message/sendBody:
type— Message typetarget— MessageTargetbody— object with what you may send ornull
Response:
HTTP status 201. Message object.
Unsend a message
Requires Authorization.
Turns a message in chat into "This message was unsent."
Repeated requests are completed without errors.
POST /v4/chat/message/unsendBody:
conversationId— Conversation IDmessageId— string, must be sent by you
Response:
Empty.
Errors:
402— Returns for free-tier users. Unsending is gated server-side behind a paid subscription. The body is a TieredFeatureProblemResponse withfeatureValue=UnsentMessage.500— Internal Error if conversation or message was not found or if it wasn't sent by you
Delete a message
Requires Authorization.
Deletes a message on your side. Does not delete message for other chat participant.
Repeated requests are completed without errors.
POST /v4/chat/message/deleteBody:
conversationId— Conversation IDmessageId— string, must be sent by you
Response:
Empty.
Errors:
500— Internal Error if conversation or message was not found
Send typing indicator, WIP
NOTE
This endpoint hasn't been researched yet
Requires Authorization.
WIP, does not seem to work.
POST /v4/chatstatus/typingBody:
conversationId— Conversation IDstatus— string, either"Typing"or"Cleared"
Response:
Empty.
Errors:
403— Action not permitted if conversation not found
React to a message
Requires Authorization.
There is no discovered way to undo the reaction as of yet.
Repeated requests are completed without errors.
POST /v4/chat/message/reactionBody:
conversationId— Conversation IDmessageId— stringreactionType— integer, (1is "🔥")
Response:
Empty.
Errors:
500— Internal Error if conversation or message was not found
MessageReaction
profileId— integerreactionType— integer, (1is "🔥")
Message type
Message type discriminator. There also appears to be a related chat1Type, which could be a legacy type.
"Album""AlbumContentReaction""AlbumContentReply""Audio""ExpiringAlbum""ExpiringAlbumV2""ExpiringImage""Video""Gaymoji""Generative""Giphy""Image""Location""PrivateVideo""ProfileLink""ProfilePhotoReply""Retract""RightNowRequest""Text""Unknown""NonExpiringVideo""VideoCall"
Chat1MessageType
Legacy message type.
"map""image""expiring_album""expiring_image""private_video""expiring_video""gaymoji""giphy""audio""video_call""video_call_v3""audio_call""text""unknown""retracted""retracted_location""album_share""album_react""album_content_reaction""album_content_reply"
AlbumMessageBody
Body for "Album" message type. Includes everything from AlbumPreview and AlbumExpiration.
- everything from AlbumPreview
- everything from AlbumExpiration
coverUrl— string ornullownerProfileId— number ornullif album has expired or was lockedisViewable— booleanhasVideo— booleanhasPhoto— booleanviewableUntil— number ornull
AlbumContentReactionBody
Body for "AlbumContentReaction" message type. Implies "🔥" reaction as there does not appear to be any choice.
albumId— integerownerProfileId— integer ornullif album has expired or was lockedalbumContentId— integerpreviewUrl— string ornullif album has expired or was locked, see Signed CDN files -> Chat mediaexpiresAt— unix timestamp in milliseconds ornullviewable— boolean
AlbumContentReplyBody
Body for "AlbumContentReply" message type. Extends AlbumContentReaction.
- everything from AlbumContentReactionBody
albumContentReply— stringcontentType— string ornullif album has expired or was locked
AudioMessageBody
Body for "Audio" message type.
mediaId— numbermediaHash— string, See Media -> Public CDN files -> Profile Images ornullurl— string, see Signed CDN files -> Chat mediacontentType— string, e.g.audio/aaclength— number in milliseconds (1/1000th of a second) ornullexpiresAt— unix timestamp in milliseconds, 15 minutes
VideoMessageBody
Body for "Video" message type. Additionally, for expiring videos viewsRemaining is set; capped at 2147483647 for "unlimited" views.
mediaId— number ornullurl— string ornullfileCacheKey— stringcontentType— string ornulllength— numbermaxViews— integer ornulllooping— boolean ornullviewsRemaining— integer, optional
PrivateVideoBody
Body for "PrivateVideo" message type. Extends Video.
- everything from VideoMessageBody
viewCount— integer
GaymojiBody
Body for "Gaymoji" message type.
imageHash— string
GiphyBody
Body for "Giphy" message type. URLs point at https://media0.giphy.com.
id— stringurlPath— full URL to gif filestillPath— single frame, URL to gif filepreviewPath— stringwidth— integerheight— integerimageHash— string
ImageMessageBody
Body for "Image" message type. Additional fields are present only for regular (non-expiring) images.
mediaId— numberwidth— integer ornullheight— integer ornullurl— stringimageHash— stringtakenOnGrindr— boolean ornullcreatedAt— unix timestamp in milliseconds ornull
ChatImageBody
"ChatImage" message body.
mediaId— numberurl— stringexpiresAt— unix timestamp in millisecondstakenOnGrindr— booleancreatedTs— unix timestamp in milliseconds
ExpiringImageBody
Body for "ExpiringImage" message type. Unlike ImageMessageBody this carries no imageHash, takenOnGrindr or createdAt, and adds the expiry fields below.
To get a single-use expiring image URL, you need to call Get a single message in a conversation. After one call, the url in the message body will be replaced with null and the image will no longer be viewable.
Both the viewing time (duration) and the view budget (viewsRemaining) are assigned server-side — in practice 10 seconds and a single view. A sender only chooses whether an image expires.
mediaId— number, optionalurl— string ornull, single-use signed CDN URL — replaced withnullonce the image has been consumed, optionalwidth— integer ornull, optionalheight— integer ornull, optionalduration— integer, how long the recipient's client shows the image once opened, in milliseconds — observed to be10000(10 seconds). Chosen by the server, a sender cannot set it (see SendExpiringImageBody);-1is a sentinel meaning already expired, and the official app treats any value below100the same way, optionalviewsRemaining— integer ornull, remaining views — starts at1, as an expiring photo is allowed exactly one view. The image is viewable while this is greater than0; chosen by the server, likeduration, optionalexpiresAt— unix timestamp in milliseconds ornull, optionalviewed— boolean ornull, optional
LocationMessageBody
Body for "Location" message type.
lat— numberlon— number
ProfilePhotoReplyBody
Body for "ProfilePhotoReply" message type. Unknown, WIP.
imageHash— stringphotoContentReply— string
RetractMessageBody
Body for "Retract" message type. Unknown, WIP.
targetMessageId— string
RightNowRequestMedia
Element of medias in RightNowRequestBody. Unrelated to RightNowMedia, which is the Right Now feed object.
mediaHash— stringisNsfw— boolean
RightNowRequestBody
Body for "RightNowRequest" message type. Receive-only — there is no send payload for it.
requestId— long integerrequestCreatedAt— unix timestamp in millisecondsrequestUpdatedAt— unix timestamp in millisecondspostStatus— string ornull. Opaque: the chat body carries it as a plain string, and nothing compares it against the Right Now post statuses (AWAITING_PAYMENT,AWAITING_MODERATION,ACTIVE,EXPIRED,HIDDEN), so reusing those values is a suspicion rather than a fact.postId— long integer ornullmedias— array of RightNowRequestMedia
TextMessageBody
Body for "Text" message type.
text— string
VideoCallMessageBody
Body for "VideoCall" message type. WIP. Only for "status" messages.
result— string ornull, appears to have the following values:SUCCESSFUL,Duration:,Busy,BUSY,Cancelled,Declined,DECLINED,Missed,AB_Unsupported,No_Answer,UNANSWERED,Lite_UnsupportvideoCallDuration— number ornull
UnknownMessageBody
Empty type.
The official client renders both unrecognized message types and retracted messages as a locally generated "system message" row. SystemMessage is therefore a client-side UI concept and never a wire value — it has no message type constant, no body type and no adapter.
Message contents
- AlbumMessageBody
- AlbumContentReactionBody
- AlbumContentReplyBody
- AudioMessageBody
- VideoMessageBody
- PrivateVideoBody
- GaymojiBody
- GiphyBody
- ImageMessageBody
- ExpiringImageBody
- LocationMessageBody
- ProfilePhotoReplyBody
- RetractMessageBody
- RightNowRequestBody
- TextMessageBody
- VideoCallMessageBody
- UnknownMessageBody
- ExpiringAlbumBody
- ExpiringAlbumV2Body
- NonExpiringVideoBody
- GenerativeBody
- ProfileLinkBody
Message
messageId— string, appears to be a unix timestamp in milliseconds and UUIDv4 separated by:, e.g."1774296692000:843daee8-1e93-47d6-bc7f-3d981925a393"conversationId— string, see ConversationsenderId— numbertimestamp— unix timestamp in milliseconds, appears to be same as inmessageIdunsent— boolean, if this is true,bodyis set tonullreactions— array of MessageReactiontype— string, see Message typebody— object with Message contents ornullreplyToMessage— object, see Message, ornulldynamic— boolean, unknown purpose, WIPchat1Type— string, see Message typereplyPreview— unknown ornull
ConversationMessagesResponse
lastReadTimestamp— unix timestamp of recipient's side in milliseconds ornull; is not affected by your calls to mark messages as readmessages— array of Messagemetadata— objecttranslate— booleanhasSharedAlbums— booleanisInAList— booleansummary— object ornull, Wingman AI conversation summary (contactDetailsandsummariesarrays)
profile— object ifprofilequery parameter is set totrueornull
MessageTarget
type— string,Direct,Group,HumanWingmantargetId— integer
SendImageBody
Request body for sending an "Image" message. mediaId is the only field sent — the rest of ImageMessageBody (url, imageHash, width, height, takenOnGrindr, createdAt) is filled in server-side and appears only on the message you receive back.
mediaId— number
SendExpiringImageBody
Request body for sending an "ExpiringImage" message.
A sender chooses only whether the image expires. The viewing time (duration) and the view budget (viewsRemaining) seen on ExpiringImageBody are assigned by the server and cannot be set here — the recipient gets 10 seconds and a single view.
mediaId— numberexpiring— boolean, alwaystruein the official app — setting it is what makes the image expiring
SendAudioBody
Request body for sending an "Audio" message. url, contentType, length and expiresAt of AudioMessageBody are server-populated.
mediaId— number
SendVideoBody
Request body for sending a "Video" message. Unlike the other media types the sender does control playback here — looping and maxViews are chosen client-side. Everything else on VideoMessageBody is server-populated.
mediaId— numberlooping— booleanmaxViews— integer
SendAlbumContentReactionBody
Request body for sending an "AlbumContentReaction" message. ownerProfileId, previewUrl, expiresAt and viewable of AlbumContentReactionBody are server-populated.
albumId— integeralbumContentId— integer
SendAlbumContentReplyBody
Request body for sending an "AlbumContentReply" message. contentType, ownerProfileId, previewUrl, expiresAt and viewable of AlbumContentReplyBody are server-populated.
albumId— integeralbumContentId— integeralbumContentReply— string
SendMessageBody
What you may put in body when sending a message. This is not the same shape you get back: server-owned fields (signed urls, hashes, dimensions, timestamps, expiry counters) exist only on Message contents.
Five types send exactly what they receive — Text, Location, Gaymoji, Giphy, ProfilePhotoReply — and are reused as-is below. The media and album-reaction types send a strict subset.
Types absent from this list are receive-only: Album/ExpiringAlbum are shared through Share an album, and Retract, VideoCall, PrivateVideo, Generative, ProfileLink, NonExpiringVideo, Unknown arrive as server notifications.
- TextMessageBody
- LocationMessageBody
- GaymojiBody
- GiphyBody
- ProfilePhotoReplyBody
- SendImageBody
- SendExpiringImageBody
- SendAudioBody
- SendVideoBody
- SendAlbumContentReactionBody
- SendAlbumContentReplyBody
SendMessageRequest
type— Message typetarget— MessageTargetbody— object with what you may send ornull
Additional body fields for websocket only:
ref— string, client-generated correlation reference; echoed back asrefValueornullreplyToMessageId— string ornull
MessageMutationRequest
conversationId— Conversation IDmessageId— string, must be sent by you
TieredFeatureProblemResponse
RFC-7807 problem body returned on a 402 when the requested feature is not part of the account's subscription tier.
type— string, error URN —urn:gr:err:tiered_feature, optionaltitle— string, optionalstatus— integer,402, optionaltraceId— string, optionaldetail— string, the principal and the feature, optionalprofileId— long integer, optionalfeatureValue— string, e.g.UnsentMessagefor Unsend a message, optional
TypingIndicatorRequest
conversationId— Conversation IDstatus— string, either"Typing"or"Cleared"
MessageReactionRequest
conversationId— Conversation IDmessageId— stringreactionType— integer, (1is "🔥")
ExpiringAlbumBody
Body for "ExpiringAlbum" message type. Extends Album.
- everything from AlbumMessageBody
ExpiringAlbumV2Body
Body for "ExpiringAlbumV2" message type. For AlbumExpirationType = ONCE but might have other values if expiration settings were changed later. Extends ExpiringAlbum.
- everything from ExpiringAlbumBody
NonExpiringVideoBody
NOTE
This type hasn't been researched yet
Body for "NonExpiringVideo" message type. Unknown, WIP.
GenerativeBody
NOTE
This type hasn't been researched yet
Body for "Generative" message type. Unknown, WIP.
ProfileLinkBody
NOTE
This type hasn't been researched yet
Body for "ProfileLink" message type. Unknown, WIP.