Skip to content
Fluxer API

Memes

A meme is an image, video, or audio asset the current account has saved as a favourite. Every meme is a durable Fluxer copy held as an account-owned attachment, so it keeps working after the URL or message it came from disappears.

The routes here are user-only and operate on the caller’s own collection. Fluxer rejects a bot token with 403 ACCESS_DENIED. Create message sends a stored meme through its favorite_meme_id field, and the GIF provider API searches the configured provider for a GIF to save.

Fluxer caps a collection at the instance-configured max_favorite_memes limit, with a fallback of 50. Both creation operations check the count before they touch the channel, the message, or the source URL, so an account at its limit is refused with 400 MAX_FAVORITE_MEMES even when the rest of the request would have failed. The error body has max_favorite_memes reporting the ceiling reached.

A meme has at most max_favorite_meme_tags tags, with a fallback of 10. A request that exceeds the allowance returns 400 INVALID_FORM_BODY with the validation code MAX_FAVORITE_MEME_TAGS_EXCEEDED at the tags path.

Fluxer normalises and trims a tag before counting it, and the result must be 1 through 30 characters. A tag that normalises to the empty string fails with 400 INVALID_FORM_BODY and STRING_LENGTH_INVALID.

A meme is identified by the content hash of its stored bytes. The same bytes reached through a different URL or a different message are still a duplicate. Saving an asset whose hash already exists in the account’s collection returns 400 INVALID_FORM_BODY with the validation code MEDIA_ALREADY_IN_FAVORITE_MEMES at the media path. The existing meme is not returned and nothing is created.

One media asset owned by one account. Fluxer resolves every media field at creation, and only the name, alt text, and tags change afterwards.

FieldTypeDescription
idsnowflakeThe ID of the meme
user_idsnowflakeThe ID of the owning account
name1stringThe display name of the meme (1-100 characters)
alt_text2?stringThe accessibility description of the media, or null when none is set
tagsarray[string]The search tags stored on the meme, each 1-30 characters
attachment_id3snowflakeThe ID of the attachment holding the durable Fluxer copy
filenamestringThe filename of the stored copy
content_typestringThe MIME type of the stored copy
content_hash?stringThe content hash used for deduplication, or null when it is unavailable
sizeintegerThe stored size of the copy in bytes
width?integerThe width of the media in pixels, or null when inapplicable or unknown
height?integerThe height of the media in pixels, or null when inapplicable or unknown
duration?numberThe duration of the media in seconds, or null when inapplicable or unknown
url3stringThe Fluxer attachment URL of the stored copy
is_gifv4booleanWhether the stored media is animated or a video converted from a GIF
gif_slug5?stringThe provider-issued slug this meme was sourced from, or null
gif_provider5?stringThe name of the GIF provider that issued gif_slug, or null
media6?map[string, GIF media format object]The provider format map for a GIF-sourced meme, or null
placeholder?stringThe compact thumbhash placeholder recorded at save time, or null

1 Required by Save meme from message. Save meme from URL derives it from filename, truncated to 100 characters, when it is omitted or null

2 Save meme from message falls back to the selected attachment’s own description when the body supplies no non-empty alt text

3 url is the media endpoint followed by attachments/{user_id}/{attachment_id}/{filename}

4 Set when the source attachment or embed was already animated, and when the Media Proxy reports the stored copy as animated

5 Both fields are null together. klipy is the only value a new meme records, and tenor appears on legacy records alone

6 Keyed by GIF media format name. Persisted only by Save meme from URL, and null on every message-sourced meme

A message-sourced meme records a slug only when the selected embed is a gifv embed whose URL the configured provider recognises. An attachment selection never records one.

The creation time is the timestamp embedded in id, as defined by Snowflakes.

{
"id": "1501314428688998182",
"user_id": "1501314428688990000",
"name": "party_parrot.gif",
"alt_text": null,
"tags": ["parrot", "party"],
"attachment_id": "1501314428688998183",
"filename": "party_parrot.gif",
"content_type": "image/gif",
"size": 40213,
"width": 320,
"height": 240,
"is_gifv": true,
"gif_slug": "party-parrot-1234",
"gif_provider": "klipy",
"placeholder": "3PcNNYYONWQPFxrsUeUcRh4"
}

One descriptor describes one encoding of one provider GIF. A provider returns several for the same GIF so a client can pick the smallest encoding it can decode.

FieldTypeDescription
srcstringThe direct media URL of this format
proxy_srcstringThe Media Proxy URL of this format
width1integerThe width of this format in pixels
height1integerThe height of this format in pixels

1 A non-negative value of at most 2147483647. A descriptor with an empty src or proxy_src, or a zero dimension, is unusable as a preview and is skipped

A client submitting a format map to Save meme from URL supplies all four members of every descriptor, and a descriptor missing one fails validation with 400 INVALID_FORM_BODY.

ValueDescription
webmFull-size WebM video
mp4Full-size MP4 video
webpFull-size WebP image
gifFull-size GIF image
mediumwebmMedium WebM video
mediummp4Medium MP4 video
mediumwebpMedium WebP image
mediumgifMedium GIF image
tinywebmSmall WebM video
tinymp4Small MP4 video
tinywebpSmall WebP image
tinygifSmall GIF image
nanowebmSmallest WebM video
nanomp4Smallest MP4 video
nanowebpSmallest WebP image
nanogifSmallest GIF image
loopedmp4Looping MP4 video

A map contains only the formats the provider returned, so no key is guaranteed to be present. A client walks a priority list until it finds a key the map has. A client that cannot decode the video keys SHOULD prefer tinywebp, tinygif, mediumwebp, mediumgif, webp, gif, nanowebp, and nanogif, in that order.

One entry describes one URL submitted to Resolve GIF URLs.

FieldTypeDescription
url1stringThe source GIF URL
proxy_urlstringThe signed Media Proxy URL for the preview format
width2integerThe width of the preview format in pixels
height2integerThe height of the preview format in pixels
media3map[string, GIF media format object]The formats available for the URL, keyed by GIF media format name
content_type4stringThe MIME type of the media addressed by proxy_url
placeholder?stringThe compact thumbhash placeholder, or null when the Media Proxy emitted none

1 The provider’s own canonical URL when the submitted URL resolved through the configured GIF provider, and the submitted URL otherwise

2 Zero when the dimensions could not be determined

3 Empty when the URL belongs to no registered GIF provider and no single direct format could be derived from it

4 The empty string means the type is unknown, and a client treats that as image/gif

A direct format is derived only for video/webm, video/mp4, image/webp, and image/gif, and only when both dimensions are positive.

The preview format is the first usable descriptor in the order tinywebm, tinymp4, mediumwebm, mediummp4, webm, mp4, loopedmp4, nanowebm, nanomp4, tinywebp, tinygif, mediumwebp, webp, mediumgif, gif, nanowebp, nanogif. When no key in that list is usable, Fluxer takes the first usable descriptor in map order. When the map holds nothing usable, it takes the provider’s own top-level media.

GET/v1/users/@me/memes

Returns every meme object the current account owns, in descending meme ID order, so the most recently saved comes first.

The account’s collection limit bounds how many entries the array holds.

StatusBodyCondition
200array[meme object]Collection was returned, possibly as an empty array
403error responseCaller is a bot or presents a bearer credential and the request returns ACCESS_DENIED
403error responseThe account has an outstanding required action and the request returns ACCOUNT_SUSPICIOUS_ACTIVITY

60 requests per 10 seconds for each authenticated user, on the favorite_meme:list bucket.

POST/v1/users/@me/memes

Fetches an absolute media URL, stores a durable Fluxer copy, and returns the created meme object. Emits a Favorite Meme Create Gateway event.

FieldTypeDescription
urlstringThe absolute media URL to fetch and store
name?1?stringThe display name of the meme (1-100 characters)
alt_text??stringThe accessibility description of the media (0-500 characters)
tags?2?array[string]The search tags to store, each 1-30 characters
gif_slug?3?stringThe provider-issued slug or slug-id token for the GIF (1-300 characters)
gif_provider?3?stringThe stable name of the provider that issued gif_slug (1-32 characters)
media?4?map[string, GIF media format object]The provider format map captured by the client at save time

1 When omitted or null, the name is the stored filename truncated to 100 characters

2 An omitted or null value is an empty tag list

3 Honoured together only when gif_provider names the configured provider. Otherwise Fluxer extracts a slug from url with the configured provider

4 Persisted only when the request resolved to a provider GIF and the map is non-empty, and discarded otherwise

The stored filename is the last path segment of url after percent-decoding. Fluxer appends an extension inferred from the resolved content type when that segment has no dot, then normalises the result so whitespace becomes _ and every character outside letters, digits, combining marks, _, ., and - is dropped. A URL path with no last segment yields media.{extension}. A segment that normalises to nothing, or to only dots and underscores, yields unnamed, and the extension is bin when the content type maps to none.

The stored gif_provider is always the configured provider’s own name, so new provider GIFs are sourced from KLIPY even when the request names another provider.

Fluxer fetches the asset and resolves its metadata before it stores anything. A URL that resolves to no usable media fails with 400 MEDIA_METADATA_ERROR. When the request resolves to a provider GIF, the provider’s canonical share URL is unfurled and the video content hash it reports replaces the fetched hash for deduplication.

StatusBodyCondition
201meme objectMeme was created
400error responseBody fails validation with INVALID_FORM_BODY5
400error responseThe URL resolves to no usable media with MEDIA_METADATA_ERROR
400error responseThe collection is at its limit with MAX_FAVORITE_MEMES
403error responseCaller is a bot or presents a bearer credential and the request returns ACCESS_DENIED
403error responseThe account has an outstanding required action and the request returns ACCOUNT_SUSPICIOUS_ACTIVITY

5 Including MAX_FAVORITE_MEME_TAGS_EXCEEDED at the tags path and MEDIA_ALREADY_IN_FAVORITE_MEMES at the media path

The meme is created against a new attachment of its own holding the fetched bytes.

The operation emits one Favorite Meme Create with the created object to the caller’s own sessions.

20 requests per minute for each authenticated user, on the favorite_meme:create:url bucket.

POST/v1/channels/{channel_id}/messages/{message_id}/memes

Copies one attachment or one embed asset from a readable message into the caller’s own collection and returns the created meme object. Requires VIEW_CHANNEL in a guild channel. Emits a Favorite Meme Create Gateway event.

A guild caller without READ_MESSAGE_HISTORY can select media only from a message on or after the guild’s message history cutoff. Attachments and embeds in the message’s forwarded snapshots are selectable alongside its own.

FieldTypeDescription
channel_idsnowflakeThe ID of the channel containing the message
message_idsnowflakeThe ID of the message containing the selected media
FieldTypeDescription
namestringThe display name of the meme (1-100 characters)
attachment_id?1?snowflakeThe ID of an attachment in the message or one of its snapshots
embed_index?1 2?integerThe zero-based index of an embed in the message or one of its snapshots
alt_text?3?stringThe accessibility description of the media (0-500 characters)
tags??array[string]The search tags to store, each 1-30 characters

1 At least one of the two keys must be present, and a body with neither fails validation. Both may be present as null, which selects nothing explicitly

2 embed_index takes precedence when both have a value, and an index outside the combined embed list fails with EMBED_INDEX_OUT_OF_BOUNDS

3 Taken from the selected attachment’s own description whenever the supplied value is omitted, null, empty, or whitespace only

When neither key selects anything, Fluxer takes the message’s first attachment. It takes the first usable embed when the message has no attachment, or when that attachment has no supported media type. An embed selection has no alt text of its own.

Only an image/*, video/*, or audio/* asset can be saved. attachment_id is matched only when the message or one of its snapshots has at least one attachment, and an ID matching none of them fails with ATTACHMENT_ID_NOT_FOUND_IN_MESSAGE at the attachment_id path. When the message has no attachment at all, Fluxer ignores the ID and takes the first usable embed. Only the selected attachment is tried, so a message whose chosen attachment has an unsupported type falls through to its embeds. A selection that resolves to no supported media fails with NO_VALID_MEDIA_IN_MESSAGE at the media path.

An embed selection uses the embed’s image, video, or thumbnail, in that order. A URL the instance’s own media endpoint serves is copied without a fetch. Any other URL is fetched over the network, and a fetch that resolves no metadata fails with 400 MEDIA_METADATA_ERROR.

StatusBodyCondition
201meme objectMeme was created
400error responseBody or selection fails validation with INVALID_FORM_BODY4
400error responseExternal media metadata cannot be resolved with MEDIA_METADATA_ERROR
400error responseThe collection is at its limit with MAX_FAVORITE_MEMES
403error responseCaller is a bot or presents a bearer credential and the request returns ACCESS_DENIED
403error responseThe account has an outstanding required action and the request returns ACCOUNT_SUSPICIOUS_ACTIVITY
403error responseThe caller is not a member of the guild or lacks VIEW_CHANNEL and the request returns MISSING_PERMISSIONS
403error responseThe channel requires age verification the account has not completed and the request returns NSFW_CONTENT_AGE_RESTRICTED
404error responseChannel is unavailable or the caller is not a recipient of it with UNKNOWN_CHANNEL
404error responseThe message is unavailable or unreadable with UNKNOWN_MESSAGE

4 The validation code is ATTACHMENT_ID_NOT_FOUND_IN_MESSAGE, EMBED_INDEX_OUT_OF_BOUNDS, NO_VALID_MEDIA_IN_MESSAGE, MEDIA_ALREADY_IN_FAVORITE_MEMES, or MAX_FAVORITE_MEME_TAGS_EXCEEDED

Fluxer copies the selected asset into the account’s own attachment space under a new attachment ID and creates the meme record against that attachment. Reading the source message renews its attachment decay deadline when attachment decay is enabled. The source message, its attachments, and its embeds are untouched.

The operation emits one Favorite Meme Create with the created object to the caller’s own sessions.

10 requests per minute for each authenticated user, on the favorite_meme:create:message bucket.

GET/v1/users/@me/memes/{meme_id}

Returns one meme object owned by the current account.

FieldTypeDescription
meme_idsnowflakeThe ID of the meme
StatusBodyCondition
200meme objectMeme was returned
403error responseCaller is a bot or presents a bearer credential and the request returns ACCESS_DENIED
403error responseThe account has an outstanding required action and the request returns ACCOUNT_SUSPICIOUS_ACTIVITY
404error responseMeme is unknown or belongs to another account with UNKNOWN_FAVORITE_MEME

100 requests per 10 seconds for each authenticated user, on the favorite_meme:get bucket.

PATCH/v1/users/@me/memes/{meme_id}

Changes the name, alt text, or tags of one meme owned by the current account and returns the updated meme object. Emits a Favorite Meme Update Gateway event.

FieldTypeDescription
meme_idsnowflakeThe ID of the meme
FieldTypeDescription
name?stringThe replacement display name (1-100 characters)
alt_text?1?stringThe replacement accessibility description (0-500 characters), or null to clear it
tags?2?array[string]The replacement tags, each 1-30 characters

1 An omitted field leaves the stored value unchanged, and an explicit null clears it

2 An omitted or null value leaves the stored tags unchanged

Fluxer checks the tag allowance against the resulting list, so a request omitting tags re-checks the tags already stored. A meme that exceeds a since-lowered allowance therefore fails with 400 INVALID_FORM_BODY and MAX_FAVORITE_MEME_TAGS_EXCEEDED until the caller supplies a shorter list.

StatusBodyCondition
200meme objectMeme was updated
400error responsePath, name, alt text, or tag length fails validation with INVALID_FORM_BODY3
403error responseCaller is a bot or presents a bearer credential and the request returns ACCESS_DENIED
403error responseThe account has an outstanding required action and the request returns ACCOUNT_SUSPICIOUS_ACTIVITY
404error responseMeme is unknown or belongs to another account with UNKNOWN_FAVORITE_MEME

3 Including MAX_FAVORITE_MEME_TAGS_EXCEEDED at the tags path

The operation changes the supplied fields and leaves every omitted field unchanged. It emits one Favorite Meme Update with the updated object to the caller’s own sessions.

30 requests per 10 seconds for each authenticated user, on the favorite_meme:update bucket.

DELETE/v1/users/@me/memes/{meme_id}

Deletes one meme owned by the current account and returns 204 with an empty body. Emits a Favorite Meme Delete Gateway event.

An unknown meme, or one owned by another account, is a successful no-op.

FieldTypeDescription
meme_idsnowflakeThe ID of the meme
StatusBodyCondition
204emptyMeme was deleted, or no owned meme existed
403error responseCaller is a bot or presents a bearer credential and the request returns ACCESS_DENIED
403error responseThe account has an outstanding required action and the request returns ACCOUNT_SUSPICIOUS_ACTIVITY

The operation removes the meme record and permanently deletes its stored copy. A storage failure is logged and does not fail the request, so the record is removed either way.

It emits one Favorite Meme Delete with meme_id to the caller’s own sessions. A request naming a meme the account does not own emits nothing.

30 requests per 10 seconds for each authenticated user, on the favorite_meme:delete bucket.

POST/v1/users/@me/favorite-gifs/resolve

Resolves a batch of GIF URLs into resolved GIF entry objects with signed Media Proxy URLs and preview metadata.

A client uses it to turn stored URL-only favourite GIFs into renderable picker entries without saving anything.

FieldTypeDescription
Accept-Language?1stringThe preferred locale used for a GIF provider lookup

1 Resolved against the supported locale registry, superseded by the account’s own stored locale, and defaulting to en-US

Fluxer derives a two-letter country from the requesting address by geolocation, with US as the fallback.

FieldTypeDescription
urlsarray[string]The absolute GIF URLs to resolve (1-200)
FieldTypeDescription
entries1array[resolved GIF entry object]One entry for each submitted URL

1 Exactly one entry per submitted URL, in the submitted order, so a client may index the result positionally against its request

Fluxer resolves a URL in three stages. It offers the URL to the configured GIF provider, then reads it as direct external media, and then unfurls it as a page when the direct read produced no renderable image or video. A failure in any stage downgrades that URL’s entry alone. A URL that none of the three stages resolves still returns an entry, with its signed proxy URL, an empty media map, and whatever the direct read reported. Dimensions are zero and content_type is the empty string when the direct read reported nothing.

StatusBodyCondition
200response bodyEvery URL was resolved
403error responseCaller is a bot or presents a bearer credential and the request returns ACCESS_DENIED
403error responseThe account has an outstanding required action and the request returns ACCOUNT_SUSPICIOUS_ACTIVITY

30 requests per minute for each authenticated user, on the favorite_gif:resolve bucket.