API Guide

Last Updated: April 13th 2026

[ Introduction ]

The gifuu API is publicly accessible and requires no authentication for read operations. We recommend that you have the client make requests on their own instead of proxying requests for them to avoid issues with our rate limits.

Use the following URLs for HTTP requests:

            https://api.gifuu.pancakz.net/                     // Base URL for API Requests
            https://cdn.gifuu.pancakz.net/                     // Base URL for CDN Requests

            https://cdn.gifuu.pancakz.net/{id}/preview.avif    // Up to 240px at 16fps
            https://cdn.gifuu.pancakz.net/{id}/standard.avif   // Up to 720px at 60fps
            https://cdn.gifuu.pancakz.net/{id}/alpha.webm      // See "Transparency" section below
            https://cdn.gifuu.pancakz.net/{id}/standard.ogg    // See "Audio" section below

Ratelimit headers are provided with each request:

            X-Ratelimit-Category        // Endpoint Category  (a.k.a Bucket)
            X-Ratelimit-Reset           // Seconds until reset (float string)
            X-Ratelimit-Limit           // Requests allowed per period
            X-Ratelimit-Remaining       // Requests left before 429 errors appear

[ Transparency ]

gifuu stores animations (also referred to as Art) as AVIF a very new and modern format. We sacrifice compatibility with older devices to gain massive efficiency in file size and visual quality. Unfortunately AVIF doesn't natively support transparency, so we use a stacked video technique to encode the alpha channel (transparency) alongside the color data.

The `alpha.webm` file is a double-height video where the top half contains the color data and the bottom half contains the alpha channel encoded as a grayscale luma (brightness) map. White pixels signify opaque, black pixels signify transparent.

To render this correctly in the browser you must use a WebGL fragment shader to composite the two halves together. The following shader can be used as a reference:

            // Sample color from top half, alpha from bottom half
            vec2 colorUV = vec2(vUV.x, vUV.y * 0.5);
            vec2 alphaUV = vec2(vUV.x, 0.5 + vUV.y * 0.5);

            // Apply colors to a texture
            vec4 color   = texture2D(uFrame, colorUV);
            float alpha  = texture2D(uFrame, alphaUV).r;
            gl_FragColor = vec4(color.rgb, alpha);

This technique was pioneered by Jake Archibald. If you don't want to implement this yourself, you can embed our player by clicking the `EMBED` button while viewing any animation.

[ Audio ]

You can check for audio by reading the `audio` field on Art objects, or by requesting it from the CDN and checking for a `404 Not Found` response.

Content is encoded with the Opus codec inside an Ogg container for compatibility with Apple devices.

[ Object Types ]

The following types are returned as responses across API endpoints:

Object: Tag

            {
                "id":         string        // Tag ID (snowflake string)
                "label":      string        // Tag Name
                "usage":      number        // Number of animations using this tag
            }

Object: Art

            {
                "id":         string        // Animation ID (snowflake string)
                "created":    string        // ISO 8601 Timestamp
                "sticker":    boolean       // Is Static?
                "audio":      boolean       // Has Audio?
                "framerate":  number        // Approximate Framerate
                "width":      number        // Approximate Width
                "height":     number        // Approximate Height
                "rating":     string        // NSFW Rating (string float, range: 0.0 - 1.0)
                "title":      string        // Associated Title
                "tags":       Tag[]         // Associated Tags
            }

[ Special ]

GET /limits

Returns upload constraints and validation rules for all user input fields. The regex patterns and normalizer rules here are authoritative. You must sanitize your inputs against them before submitting or the server will reject your request.

            Response Body:
            {
                "upload": {
                    "input_width_min":  number      // Minimum input width  (64px)
                    "input_height_min": number      // Minimum input height (64px)
                    "video_width_max":  number      // Maximum video width  (3840px)
                    "video_height_max": number      // Maximum video height (2160px)
                    "image_width_max":  number      // Maximum image width  (7680px)
                    "image_height_max": number      // Maximum image height (4320px)
                    "duration":         number      // Maximum duration in seconds (62s)
                    "filesize":         number      // Maximum file size in bytes
                    "mime_types":       string[]    // Accepted MIME types
                }
                "title": {
                    "normalizers": NormalizerRule[] // Apply before validating
                    "matcher":     string           // Regex pattern
                    "max_length":  number           // 80
                    "min_length":  number           // 1
                }
                "tag": {
                    "normalizers": NormalizerRule[]
                    "matcher":     string           // ^[\p{L}\p{N}_]{1,32}$
                    "max_length":  number           // 32
                    "min_length":  number           // 1
                }
                "comment": {
                    "normalizers": NormalizerRule[]
                    "matcher":     string
                    "max_length":  number           // 240
                    "min_length":  number           // 10
                }
                "report": {
                    "values": [                     // Valid report reason types
                        { "id": number, "title": string, "description": string }
                    ]
                    "normalizers": NormalizerRule[]
                    "matcher":     string
                    "max_length":  number           // 240
                    "min_length":  number           // 10
                }
            }

            Object: NormalizerRule
            {
                "match":   string   // Regex pattern to find
                "replace": string   // Replacement string
                "comment": string   // Human-readable description
            }

GET /challenge

Returns a fresh Proof of Work challenge. You select the difficulty, the server enforces a minimum of `18`. Challenges expire after 5 minutes. They are consumed immediately upon use even if the request fails.

Provide your completed counter and given nonce to endpoints that require PoW via the `X-Pow-Counter` and `X-Pow-Nonce` headers respectively.

            Query Parameters:
                difficulty  number      // Desired difficulty (minimum: 18)

            Response Body:
            {
                "nonce":      string    // Hex-encoded nonce
                "difficulty": number    // Confirmed difficulty
                "expires":    number    // Expiry as UNIX timestamp
            }

Some endpoints enforce a higher minimum difficulty than the global floor. Request at least the required difficulty for the endpoint you intend to call or it will be rejected:

            Endpoint                    Minimum Difficulty
            ------------------------    ------------------
            POST /uploads               20

Example Solver (JavaScript):

            const { nonce, difficulty } = await fetch("/challenge?difficulty=18").then(r => r.json())
            const encoder = new TextEncoder()
            let counter = 0
            while (true) {
                const data = encoder.encode(nonce + counter)
                const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", data))
                let zeroBits = 0
                for (const byte of hash) {
                    if (byte === 0) { zeroBits += 8 }
                    else { zeroBits += Math.clz32(byte) - 24; break }
                }
                if (zeroBits >= difficulty) break
                counter++
            }

            // Submit nonce + counter with your upload via X-Pow-Nonce and X-Pow-Counter headers

[ Tags ]

gifuu uses tags to make its database queryable. Sanitize tag strings against the rules provided by `limits.tags` or the server will reject your request.

GET /tags/popular

Returns the most popular tags (highest usage) on the platform.

            Query Parameters:
                limit   number      // Amount of results to return (range 1-100)

            Response Body:
                Tag[]

GET /tags/autocomplete

Search for tags with a similar spelling using word similarity ranking.

            Query Parameters:
                query   string      // Search query — must pass validation rules from 'limits.tag'
                limit   number      // Amount of results to return (range 1-100)

            Response Body:
            Tag[]

[ Art ]

Content is processed and served as AVIF files for efficiency. Most modern web browsers and operating systems support this format.

GET /art/latest

Returns the most recently uploaded animations, newest first.

            Query Parameters:
                limit   number      // Amount of results to return (range 1-100)
                after   string?     // Cursor for pagination — snowflake ID of last seen item (optional)

            Response Body:
            Art[]

GET /art/search

Returns animations matching all provided tags (AND logic). At least one `tag` parameter is required.

            Query Parameters:
                tag     string      // Tag ID to filter by (snowflake string) — repeat for multiple tags
                limit   number      // Amount of results to return (range 1-100)
                after   string?     // Cursor for pagination — snowflake ID of last seen item (optional)

            // Example: /art/search?tag=123&tag=456&limit=20

            Response Body:
            Art[]

GET /art/{id}

Returns metadata for a single animation.

            Response Body:
            Art

DELETE /art/{id}

Deletes an animation. Requires the edit token returned at upload time, passed as a query parameter. Responds with `204 No Content` on success.

            Query Parameters:
                token   string      // Edit token from upload response

POST /art/{id}/reports

Submits a moderation report for an animation. Valid reason type IDs are listed in `limits.report.values`. The reason text must pass the `report` validation rules from the same endpoint. Responds with `204 No Content` on success.

            Request Body:
            {
                "type":   number    // Report reason ID (see 'limits.report.values')
                "reason": string    // Description of the issue (10-240 characters)
            }

[ Uploads ]

Endpoints for creating and monitoring uploads.

POST /uploads

Uploads an animation to the site. To prevent spam this endpoint requires a valid Proof of Work challenge solved via `GET /challenge` with a minimum difficulty of `20`.

This endpoint responds as a Server-Sent Events (SSE) stream. Events are emitted throughout processing to report progress. The connection closes after the final `finish` event or on any error.

NOTE: Requests exceeding the `limits.upload.filesize` limit will be aborted immediately.

            Request Body: [multipart/form-data]

            field: data (text/JSON)
                {
                    "title": string     // Animation title (1-80 characters, see 'limits.title')
                    "tags":  string[]   // Tag names to attach, plaintext (see 'limits.tag')
                }

            field: file (binary)
                // Accepted MIME types may change, fetch the current list from 'limits.upload.mime_types'
                image/jpeg,  image/png,   image/gif, image/webp, image/heic, image/heif,
                image/avif,  image/jxl,   image/tiff, image/bmp,
                video/mp4,   video/webm,  video/quicktime,  video/x-matroska,
                video/avi,   video/x-ms-wmv,

            Request Headers:
                X-Pow-Nonce     // Nonce from GET /challenge
                X-Pow-Counter   // Your solved counter value

            SSE Event Stream:

            event: id           // Emitted early — the assigned snowflake ID for this upload
                { "id": string }

            event: step         // Processing stage updates
                { "id": string, "message": string }
                // Known step IDs: PROBE_QUEUE, PROBE_START, SERVER_FINALIZE

            event: progress     // Encoding/classification progress
                { "percent": string }   // Float string, e.g. "42.50"

            event: finish       // Final event on success — save edit_token, it is not recoverable!
                {
                    "id":         string    // Animation snowflake ID
                    "edit_token": string    // Required to delete this animation later
                }

            event: error        // Emitted on client or server error, stream closes after
                { "code": number, "message": string }