{
  "openapi": "3.1.0",
  "info": {
    "title": "gimmeclip",
    "version": "0.1.0",
    "summary": "Video links → MP4 / MP3 downloads",
    "description": "Free video downloader: paste a YouTube, Instagram, X (Twitter) or TikTok link and save the video or send it on WhatsApp. No app, no signup, no ads.\n\nNo key needed. Supported sites: YouTube, Instagram, X / Twitter, TikTok, Bluesky. 40 downloads per IP per day; 20 detail lookups and 6 downloads per minute. Public videos up to 2 hours and 1 GB. Error messages are written for people and safe to show.",
    "contact": {
      "url": "https://gimmeclip.com/docs/"
    }
  },
  "servers": [
    {
      "url": "https://gimmeclip.com"
    }
  ],
  "security": [],
  "paths": {
    "/api/info": {
      "get": {
        "operationId": "getVideoInfo",
        "summary": "Details of a video link: title, thumbnail, videos in the post, qualities",
        "description": "Cached for 6 hours per canonical link.",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "A public video link (scheme optional). Text around the link is ignored, so a pasted share message works too.",
            "schema": {
              "type": "string",
              "maxLength": 4096
            },
            "examples": {
              "youtube": {
                "value": "https://youtu.be/jNQXAC9IVRw"
              },
              "x": {
                "value": "x.com/i/status/1600009574919962625"
              },
              "tiktok": {
                "value": "https://www.tiktok.com/@scout2015/video/6718335390845095173"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The video.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Info"
                }
              }
            }
          },
          "4XX": {
            "description": "Unsupported or malformed link, private/removed video, playlist, live stream, or rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "The video site failed or blocked the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/jobs": {
      "post": {
        "operationId": "startDownload",
        "summary": "Start preparing a download",
        "description": "The server downloads (and merges or converts) the video, then serves it at `/api/jobs/{id}/file`. Poll `/api/jobs/{id}` for progress. Counts against the daily quota (40/day).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The video link."
                  },
                  "quality": {
                    "$ref": "#/components/schemas/Quality"
                  },
                  "item": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 20,
                    "default": 1,
                    "description": "Which video of a multi-video post (1-based, see `Info.items`)."
                  }
                }
              },
              "examples": {
                "mp4": {
                  "value": {
                    "url": "https://youtu.be/jNQXAC9IVRw",
                    "quality": "720"
                  }
                },
                "mp3": {
                  "value": {
                    "url": "https://youtu.be/jNQXAC9IVRw",
                    "quality": "audio"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Started.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "Bad link, quality or item; daily quota or rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "5XX": {
            "description": "The engine is busy or the video site failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/jobs/{id}": {
      "get": {
        "operationId": "getDownload",
        "summary": "Progress of a download",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Job id from `POST /api/jobs`.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9][0-9a-f]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current state. Poll about once a second until `done` or `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "404": {
            "description": "Unknown or expired job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/jobs/{id}/file": {
      "get": {
        "operationId": "getDownloadFile",
        "summary": "The finished file",
        "description": "Supports Range requests. Available for 15 minutes after the last request.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Job id from `POST /api/jobs`.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9][0-9a-f]{32}$"
            }
          },
          {
            "name": "dl",
            "in": "query",
            "description": "`1` sends `content-disposition: attachment` instead of `inline`.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Part of the file (Range request)."
          },
          "404": {
            "description": "Unknown or expired job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Not finished yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/{link}": {
      "get": {
        "operationId": "openShareLink",
        "summary": "Share link: the web app with a video loaded",
        "description": "Put the video link after the slash, as-is (`/x.com/i/status/1600009574919962625`). Returns HTML; link-preview bots get the video title and thumbnail as Open Graph tags.",
        "parameters": [
          {
            "name": "link",
            "in": "path",
            "required": true,
            "description": "The video link, slashes and query string included.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The web app.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Not a supported video link."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Quality": {
        "type": "string",
        "enum": [
          "1080",
          "720",
          "480",
          "360",
          "audio"
        ],
        "default": "720",
        "description": "Resolution cap (smaller side, so vertical videos count the same) or `audio` for MP3."
      },
      "Item": {
        "type": "object",
        "required": [
          "title",
          "duration",
          "thumbnail",
          "qualities",
          "audio"
        ],
        "properties": {
          "title": {
            "type": "string"
          },
          "duration": {
            "type": [
              "number",
              "null"
            ],
            "description": "Seconds."
          },
          "thumbnail": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "qualities": {
            "type": "array",
            "items": {
              "type": "integer",
              "enum": [
                1080,
                720,
                480,
                360
              ]
            },
            "description": "Available caps, best first. Empty when unknown: request 1080 for the best."
          },
          "audio": {
            "type": "boolean",
            "description": "Has sound (MP3 possible)."
          }
        }
      },
      "Info": {
        "type": "object",
        "required": [
          "url",
          "site",
          "title",
          "uploader",
          "duration",
          "thumbnail",
          "items",
          "cached"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Canonical link: use it for jobs and as `/{link}`."
          },
          "site": {
            "type": "string",
            "enum": [
              "YouTube",
              "Instagram",
              "X / Twitter",
              "TikTok",
              "Bluesky"
            ]
          },
          "title": {
            "type": "string"
          },
          "uploader": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration": {
            "type": [
              "number",
              "null"
            ],
            "description": "Seconds."
          },
          "thumbnail": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Item"
            },
            "description": "Videos in the post; usually one."
          },
          "cached": {
            "type": "boolean"
          }
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "id",
          "state",
          "phase",
          "percent"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "queued",
              "downloading",
              "done",
              "error"
            ]
          },
          "phase": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "download",
              "audio",
              "merge",
              "convert",
              "encode",
              null
            ],
            "description": "`encode`: the site had no H.264, so the video is being re-encoded to H.264 + AAC."
          },
          "percent": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "bytes": {
            "type": "integer",
            "description": "While downloading."
          },
          "speed": {
            "type": [
              "number",
              "null"
            ],
            "description": "Bytes per second, while downloading."
          },
          "eta": {
            "type": [
              "number",
              "null"
            ],
            "description": "Seconds, while downloading."
          },
          "name": {
            "type": "string",
            "description": "File name, when done."
          },
          "size": {
            "type": "integer",
            "description": "Bytes, when done."
          },
          "type": {
            "type": "string",
            "description": "MIME type, when done."
          },
          "error": {
            "type": "string",
            "description": "Message for people, on error."
          },
          "status": {
            "type": "integer",
            "description": "HTTP-style status of the error."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    }
  }
}