rc-1
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
<div class="document-section">
|
||||
<p class="document-header">API Guide</p>
|
||||
<p class="document-paragraph">Last Updated: April 13th 2026</p>
|
||||
</div>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<div class="document-section">
|
||||
<p class="document-header">[ Introduction ]</p>
|
||||
<p class="document-paragraph">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>Use the following URLs for HTTP requests:</p>
|
||||
<pre class="document-codeblock">
|
||||
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
|
||||
</pre>
|
||||
|
||||
<p>Ratelimit headers are provided with each request:</p>
|
||||
<pre class="document-codeblock">
|
||||
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
|
||||
</pre>
|
||||
|
||||
<div class="document-divider"></div>
|
||||
|
||||
<p class="document-header">[ Transparency ]</p>
|
||||
<p class="document-paragraph">
|
||||
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.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
The <code>alpha.webm</code> 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.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
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:
|
||||
</p>
|
||||
<pre class="document-codeblock">
|
||||
// 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);
|
||||
</pre>
|
||||
<p class="document-paragraph">
|
||||
This technique was pioneered by
|
||||
<a target="_blank" href="https://jakearchibald.com/2024/video-with-transparency/">Jake Archibald</a>.
|
||||
If you don't want to implement this yourself, you can embed our player by clicking the
|
||||
<code>EMBED</code> button while viewing any animation.
|
||||
</p>
|
||||
|
||||
<p class="document-header">[ Audio ]</p>
|
||||
<p class="document-paragraph">
|
||||
You can check for audio by reading the <code>audio</code> field on Art objects, or by
|
||||
requesting it from the CDN and checking for a <code>404 Not Found</code> response.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
Content is encoded with the <a target="_blank" href="https://en.wikipedia.org/wiki/Opus_(audio_format)">Opus</a>
|
||||
codec inside an <a target="_blank" href="https://en.wikipedia.org/wiki/Ogg">Ogg</a> container
|
||||
for compatibility with Apple devices.
|
||||
</p>
|
||||
|
||||
<div class="document-divider"></div>
|
||||
|
||||
<p class="document-header">[ Object Types ]</p>
|
||||
<p class="document-paragraph">The following types are returned as responses across API endpoints:</p>
|
||||
|
||||
<p class="document-subheader">Object: Tag</p>
|
||||
<pre class="document-codeblock">
|
||||
{
|
||||
"id": string // Tag ID (snowflake string)
|
||||
"label": string // Tag Name
|
||||
"usage": number // Number of animations using this tag
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p class="document-subheader">Object: Art</p>
|
||||
<pre class="document-codeblock">
|
||||
{
|
||||
"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
|
||||
}
|
||||
</pre>
|
||||
|
||||
<div class="document-divider"></div>
|
||||
|
||||
<p class="document-header">[ Special ]</p>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">GET /limits</p>
|
||||
<p class="document-paragraph">
|
||||
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.
|
||||
</p>
|
||||
<pre class="document-codeblock">
|
||||
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
|
||||
}
|
||||
</pre>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">GET /challenge</p>
|
||||
<p class="document-paragraph">
|
||||
Returns a fresh Proof of Work challenge.
|
||||
You select the difficulty, the server enforces a minimum of <code>18</code>.
|
||||
Challenges expire after 5 minutes.
|
||||
They are consumed immediately upon use even if the request fails.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
Provide your completed counter and given nonce to endpoints that require PoW via the
|
||||
<code>X-Pow-Counter</code> and <code>X-Pow-Nonce</code> headers respectively.
|
||||
</p>
|
||||
<pre class="document-codeblock">
|
||||
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
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p class="document-paragraph">
|
||||
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:
|
||||
</p>
|
||||
<pre class="document-codeblock">
|
||||
Endpoint Minimum Difficulty
|
||||
------------------------ ------------------
|
||||
POST /uploads 20
|
||||
</pre>
|
||||
|
||||
<p class="document-paragraph">Example Solver (JavaScript):</p>
|
||||
<pre class="document-codeblock">
|
||||
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
|
||||
</pre>
|
||||
|
||||
<div class="document-divider"></div>
|
||||
|
||||
<p class="document-header">[ Tags ]</p>
|
||||
<p class="document-paragraph">
|
||||
gifuu uses tags to make its database queryable. Sanitize tag strings against the rules
|
||||
provided by <code>limits.tags</code> or the server will reject your request.
|
||||
</p>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">GET /tags/popular</p>
|
||||
<p class="document-paragraph">Returns the most popular tags (highest usage) on the platform.</p>
|
||||
<pre class="document-codeblock">
|
||||
Query Parameters:
|
||||
limit number // Amount of results to return (range 1-100)
|
||||
|
||||
Response Body:
|
||||
Tag[]
|
||||
</pre>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">GET /tags/autocomplete</p>
|
||||
<p class="document-paragraph">Search for tags with a similar spelling using word similarity ranking.</p>
|
||||
<pre class="document-codeblock">
|
||||
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[]
|
||||
</pre>
|
||||
|
||||
<div class="document-divider"></div>
|
||||
|
||||
<p class="document-header">[ Art ]</p>
|
||||
<p class="document-paragraph">
|
||||
Content is processed and served as AVIF files for efficiency. Most modern web browsers
|
||||
and operating systems <a target="_blank" href="https://caniuse.com/avif">support this format</a>.
|
||||
</p>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">GET /art/latest</p>
|
||||
<p class="document-paragraph">Returns the most recently uploaded animations, newest first.</p>
|
||||
<pre class="document-codeblock">
|
||||
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[]
|
||||
</pre>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">GET /art/search</p>
|
||||
<p class="document-paragraph">
|
||||
Returns animations matching all provided tags (AND logic). At least one
|
||||
<code>tag</code> parameter is required.
|
||||
</p>
|
||||
<pre class="document-codeblock">
|
||||
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[]
|
||||
</pre>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">GET /art/{id}</p>
|
||||
<p class="document-paragraph">Returns metadata for a single animation.</p>
|
||||
<pre class="document-codeblock">
|
||||
Response Body:
|
||||
Art
|
||||
</pre>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">DELETE /art/{id}</p>
|
||||
<p class="document-paragraph">
|
||||
Deletes an animation. Requires the edit token returned at upload time,
|
||||
passed as a query parameter. Responds with <code>204 No Content</code> on success.
|
||||
</p>
|
||||
<pre class="document-codeblock">
|
||||
Query Parameters:
|
||||
token string // Edit token from upload response
|
||||
</pre>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">POST /art/{id}/reports</p>
|
||||
<p class="document-paragraph">
|
||||
Submits a moderation report for an animation.
|
||||
Valid reason type IDs are listed in <code>limits.report.values</code></code>.
|
||||
The reason text must pass the <code>report</code> validation rules from the same endpoint.
|
||||
Responds with <code>204 No Content</code> on success.
|
||||
</p>
|
||||
<pre class="document-codeblock">
|
||||
Request Body:
|
||||
{
|
||||
"type": number // Report reason ID (see 'limits.report.values')
|
||||
"reason": string // Description of the issue (10-240 characters)
|
||||
}
|
||||
</pre>
|
||||
|
||||
<div class="document-divider"></div>
|
||||
|
||||
<p class="document-header">[ Uploads ]</p>
|
||||
<p class="document-paragraph">Endpoints for creating and monitoring uploads.</p>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<p class="document-subheader">POST /uploads</p>
|
||||
<p class="document-paragraph">
|
||||
Uploads an animation to the site. To prevent spam this endpoint requires a valid
|
||||
Proof of Work challenge solved via <code>GET /challenge</code> with a minimum difficulty of <code>20</code>.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
This endpoint responds as a <strong>Server-Sent Events (SSE) stream</strong>. Events are
|
||||
emitted throughout processing to report progress. The connection closes after the final
|
||||
<code>finish</code> event or on any error.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
NOTE: Requests exceeding the <code>limits.upload.filesize</code> limit will be aborted immediately.
|
||||
</p>
|
||||
<pre class="document-codeblock">
|
||||
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
|
||||
</pre>
|
||||
|
||||
<pre class="document-codeblock">
|
||||
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 }
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,51 @@
|
||||
<div class="document-section">
|
||||
<p class="document-header">Privacy Policy</p>
|
||||
<p class="document-paragraph">Last Updated: March 21st 2026</p>
|
||||
</div>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<div class="document-section">
|
||||
<p class="document-header">[ Data Collection ]</p>
|
||||
<p class="document-paragraph">We collect the following data in order to provide you with our services:</p>
|
||||
<div class="document-list">
|
||||
<p class="document-item">Content that you upload</p>
|
||||
<p class="document-item">Your edit tokens¹</p>
|
||||
<p class="document-item">Your IP address²</p>
|
||||
</div>
|
||||
<p class="document-paragraph">
|
||||
¹ Edit tokens are kept on your device when using our website.
|
||||
Please back them up via the settings menu, as we cannot assist in recovery.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
² Your IP address is stored in hashed form via a one-way algorithm to reduce direct identification.
|
||||
We use this data to prevent abuse on our platform and issue disciplinary actions towards bad actors.
|
||||
Decisions are made at our discretion and are final.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<div class="document-section">
|
||||
<p class="document-header">[ Third Party ]</p>
|
||||
<p class="document-paragraph">
|
||||
We use a self-hosted instance of <a target="_blank" href="https://umami.is/">Umami</a>
|
||||
for analytics to see how people arrive at and interact with our site.
|
||||
We use <a href="https://en.wikipedia.org/wiki/UTM_parameters">UTM parameters</a>,
|
||||
which can be manually removed if desired.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
All of this data is stored anonymously on our own servers and isn’t shared with any third parties.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<div class="document-section">
|
||||
<p class="document-header">[ Contact ]</p>
|
||||
<p class="document-paragraph">
|
||||
gifuu is a personal project operated by bakonpancakz.
|
||||
For privacy or legal concerns, please visit:
|
||||
<a target="_blank" href="https://pancakz.net/">https://pancakz.net/</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,69 @@
|
||||
<div class="document-section">
|
||||
<p class="document-header">Terms of Service</p>
|
||||
<p class="document-paragraph">Last Updated: March 21st 2026</p>
|
||||
</div>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<div class="document-section">
|
||||
<p class="document-header">[1] Acceptance</p>
|
||||
<p class="document-paragraph">
|
||||
By using gifuu, you agree to the following terms of service.
|
||||
If you do not agree to these terms, do not use our platform.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
These terms may be updated at any time without prior notice. Continued use constitutes acceptance.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<div class="document-section">
|
||||
<p class="document-header">[2] Content</p>
|
||||
<p class="document-paragraph">
|
||||
You are solely responsible for any content you upload to gifuu.
|
||||
You may not upload, distribute, or store content that:
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
Violates any applicable law or regulation;
|
||||
infringes on any copyright, trademark, or other intellectual property right;
|
||||
depicts any being in a sexual or exploitative manner;
|
||||
constitutes targeted harassment, hate speech, or incitement of violence;
|
||||
depicts graphic violence, gore, or abuse;
|
||||
promotes or depicts self-harm, dangerous activity, or seizure-inducing imagery;
|
||||
constitutes spam, advertising, or unsolicited solicitation;
|
||||
or that you do not have the rights to distribute.
|
||||
</p>
|
||||
<p class="document-paragraph">
|
||||
We reserve the right to moderate, remove content, or restrict access to our platform at our discretion.
|
||||
Violations may be reported by users and are reviewed by our moderation team.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<div class="document-section">
|
||||
<p class="document-header">[3] Data Collection</p>
|
||||
<p class="document-paragraph">
|
||||
You agree to our data collection and privacy policies.
|
||||
A description of how we collect and process your data is available <a href="/text/privacy-policy">here</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<div class="document-section">
|
||||
<p class="document-header">[4] Availability</p>
|
||||
<p class="document-paragraph">
|
||||
We are not liable for any loss of data or damages resulting from use of the platform.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="document-spacer"></div>
|
||||
|
||||
<div class="document-section">
|
||||
<p class="document-header">[5] Other</p>
|
||||
<p class="document-paragraph">
|
||||
You agree that bunnies are adorable.
|
||||
</p>
|
||||
</div>
|
||||
Reference in New Issue
Block a user