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.panca.kz/ // Base URL for API Requests
https://cdn.gifuu.panca.kz/ // Base URL for CDN Requests
https://cdn.gifuu.panca.kz/{id}/preview.avif // Up to 240px at 16fps
https://cdn.gifuu.panca.kz/{id}/standard.avif // Up to 720px at 60fps
https://cdn.gifuu.panca.kz/{id}/alpha.webm // See "Transparency" section below
https://cdn.gifuu.panca.kz/{id}/standard.ogg // See "Audio" section belowRatelimit 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
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.
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.
The following types are returned as responses across API endpoints:
{
"id": string // Tag ID (snowflake string)
"label": string // Tag Name
"usage": number // Number of animations using this tag
}{
"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
}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
}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 headersgifuu uses tags to make its database queryable. Sanitize tag strings against the rules provided by limits.tags or the server will reject your request.
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[]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[]Content is processed and served as AVIF files for efficiency. Most modern web browsers and operating systems support this format.
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[]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[]Returns metadata for a single animation.
Response Body:
Art
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 responseSubmits 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)
}Endpoints for creating and monitoring 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 valueSSE 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 }