{
  "service": "throwaway-store",
  "version": "1.38.1",
  "base_url": "https://skale.dev/throway",
  "ttl_seconds": 14400,
  "dir_ttl_seconds": {
    "min": 14400,
    "default": 604800,
    "max": 1209600
  },
  "max_file_bytes": 5242880,
  "pool_bytes": 104857600,
  "rate_limit_per_min": 100,
  "endpoints": {
    "upload": {
      "method": "POST",
      "url": "https://skale.dev/throway/?name=<filename>[&ttl=<h|d>][&share=<name>][&once=1]",
      "body": "raw file bytes (or multipart/form-data with a file part)",
      "note": "default lifetime is 4h; optional &ttl=<h|d> extends a single file, clamped to [4h, 14d] (max 14 days); optional &share=<name> stores it under a chosen memorable name (create-or-get, 5-32 chars [a-z0-9-], >=1 letter, not reserved) at /d/<name> with sliding lifetime (default 7d, ttl= clamped [4h,14d]); optional &once=1 = burn-after-reading (single files only, not with &share=): the file auto-deletes after the first download",
      "response": {
        "id": "str",
        "url": "str",
        "size": "int",
        "name": "str",
        "content_type": "str",
        "editable": "bool",
        "tags?": [
          "str"
        ],
        "persistence": {
          "type": "single|dir|bundle",
          "expires_at": "str",
          "extendable_by": "none|activity",
          "max_age": "int|null"
        },
        "expires_in": "int",
        "expires_at": "str"
      }
    },
    "upload_bundle": {
      "method": "POST",
      "url": "https://skale.dev/throway/",
      "body": "multipart/form-data with 2+ file parts",
      "note": "creates a bundle: one URL, files served at /<id>/<filename>, index.html inline for browsers; bundles are immutable snapshots (editable:false)",
      "response": {
        "id": "str",
        "url": "str",
        "bundle": true,
        "editable": false,
        "persistence": {
          "type": "bundle",
          "expires_at": "str",
          "extendable_by": "none",
          "max_age": null
        },
        "files": [
          {
            "name": "str",
            "url": "str",
            "size": "int",
            "content_type": "str"
          }
        ],
        "expires_at": "str"
      }
    },
    "download": {
      "method": "GET",
      "url": "https://skale.dev/throway/<id>",
      "note": "images and text-like types render inline; bundle root serves index.html inline (browser) or zip (agent); append ?download=1 to force download; append ?thumb=1 for a small cached WebP preview (raster images only)"
    },
    "import_url": {
      "method": "POST",
      "url": "https://skale.dev/throway/?url=<url>[&name=<name>][&link=1][&tag=<t>]",
      "note": "MAX 5MB. Two modes: (1) default \u2014 fetch the remote http(s) document SERVER-SIDE and store it as a normal file (name from Content-Disposition/URL path, overridable via &name=); (2) &link=1 \u2014 store the URL itself as a tiny redirect HTML document (browsers get redirected, agents can PUT/PATCH it). Private/loopback hosts are blocked. Optional &tag=<t> (repeatable, up to 5) attaches tags.",
      "response": "same JSON as upload"
    },
    "browse_files": {
      "method": "GET",
      "url": "https://skale.dev/throway/browse?tag=<t>[&q=<substr>][&sort=created|name|size|expires][&order=asc|desc]",
      "note": "JSON listing of live single files for agents (HTML page for browsers). Filter by one or more &tag= values (AND), by name/tag substring &q=; sort with &sort= (default created) and &order= (default desc). Each entry: id, url, name, content_type, size, tags, created_at, expires_at."
    },
    "tag_file": {
      "method": "POST",
      "url": "https://skale.dev/throway/<id>?tag=<t>[&tag=<t2>][&untag=<t3>]",
      "note": "update tags on an existing single file without touching its content or expiry. Tags: lowercase [a-z0-9-], 1-24 chars, max 5 per file."
    },
    "download_bundle_file": {
      "method": "GET",
      "url": "https://skale.dev/throway/<id>/<filename>",
      "note": "serve a single file from a bundle; append &thumb=1 for a small cached WebP preview (raster images only; falls back to the original bytes)"
    },
    "create_dir": {
      "method": "POST",
      "url": "https://skale.dev/throway/?dir=1[&name=<name>][&listed=1][&tag=<tag>][&ttl=<h|d>][&write=1|<token>]",
      "note": "create a dir: unnamed (opaque hex id) or named (create-or-get, 5-32 chars [a-z0-9-], >=1 letter, not reserved); listed=1 to appear in GET /d; tags up to 5; ttl = sliding lifetime, MAX 14 days (clamped [4h,14d]), default 7d; each add/edit/delete slides expires_at forward (capped 30d). Flags honored only on first creation. Optional &write=1 protects writes: the response contains write_token (shown once); writes then need it as the X-Throway-Write header or ?write=<token> (401 otherwise); reads/history/zip stay open.",
      "response": {
        "id": "str",
        "url": "str",
        "dir": true,
        "editable": false,
        "persistence": {
          "type": "dir",
          "expires_at": "str",
          "extendable_by": "activity",
          "max_age": "int"
        },
        "files": [
          {
            "name": "str",
            "url": "str",
            "size": "int",
            "content_type": "str",
            "editable": "bool"
          }
        ],
        "expires_at": "str",
        "max_age": "int",
        "name": "str?",
        "listed": "bool?",
        "tags": [
          "str"
        ]
      }
    },
    "add_to_dir": {
      "method": "POST",
      "url": "https://skale.dev/throway/d/<key>",
      "body": "multipart/form-data file parts",
      "note": "add files to a dir; slides expires_at forward by ttl; 401 without the X-Throway-Write token when the dir is write-protected"
    },
    "get_dir": {
      "method": "GET",
      "url": "https://skale.dev/throway/d/<key>",
      "note": "JSON listing for agents, HTML page for browsers"
    },
    "get_dir_file": {
      "method": "GET",
      "url": "https://skale.dev/throway/d/<key>/<file>",
      "note": "fetch one file from a dir; append &thumb=1 for a small cached WebP preview (raster images only; falls back to the original bytes)"
    },
    "dir_zip": {
      "method": "GET",
      "url": "https://skale.dev/throway/d/<key>?zip=1",
      "note": "download the whole dir as a zip"
    },
    "get_dir_history": {
      "method": "GET",
      "url": "https://skale.dev/throway/d/<key>/history",
      "note": "edit history: list of {ts,file,action,bytes} entries, newest first, capped at HISTORY_LIMIT"
    },
    "edit_dir_file": {
      "method": "PUT",
      "url": "https://skale.dev/throway/d/<key>/<file>",
      "body": "new text (text files only)",
      "note": "replace a file in a dir, bumps updated_at"
    },
    "append_dir_file": {
      "method": "PATCH",
      "url": "https://skale.dev/throway/d/<key>/<file>",
      "body": "text to append (text files only)",
      "note": "append to a file in a dir, bumps updated_at"
    },
    "delete_dir_file": {
      "method": "DELETE",
      "url": "https://skale.dev/throway/d/<key>/<file>",
      "note": "remove one file from a dir, bumps updated_at"
    },
    "delete_dir": {
      "method": "DELETE",
      "url": "https://skale.dev/throway/d/<key>",
      "note": "delete a whole dir"
    },
    "list_dirs": {
      "method": "GET",
      "url": "https://skale.dev/throway/d",
      "note": "list dirs created with listed=1; filters ?q=<sub> (name or tag), ?created_after/before=<ts>, ?updated_after/before=<ts>; sort ?sort=created|updated|name&order=asc|desc (default created desc)"
    },
    "delete": {
      "method": "DELETE",
      "url": "https://skale.dev/throway/<id>"
    },
    "edit_text": {
      "method": "PUT",
      "url": "https://skale.dev/throway/<id>",
      "body": "new text content (text files only)",
      "note": "replaces the whole text content"
    },
    "append_text": {
      "method": "PATCH",
      "url": "https://skale.dev/throway/<id>",
      "body": "text to append (text files only)"
    },
    "contract": {
      "method": "GET",
      "url": "https://skale.dev/throway/api"
    },
    "write_for_agents": {
      "method": "GET",
      "url": "https://skale.dev/throway/write_for_agents",
      "note": "human-readable description of this service for agents"
    },
    "copy_for_agents": {
      "method": "GET",
      "url": "https://skale.dev/throway/copy_for_agents",
      "note": "HTML page with a copy-pasteable agent description"
    },
    "help": {
      "method": "GET",
      "url": "https://skale.dev/throway/help",
      "note": "modular help index (JSON for agents, HTML for browsers); each topic fetched separately at /help/<topic> so agents gather only what they need"
    },
    "releases": {
      "method": "GET",
      "url": "https://skale.dev/throway/releases",
      "note": "release notes; raw markdown for agents, rendered HTML for browsers"
    },
    "pics_create": {
      "method": "POST",
      "url": "https://skale.dev/throway/pics?create=1[&name=<name>][&listed=1]",
      "note": "create a gallery: you become its admin via a per-gallery token (returned once \u2014 store it!). A dir-style name (5-32 chars [a-z0-9-], >=1 letter, not reserved) becomes the gallery's key at /pics/g/<name> (create-or-get, idempotent; an existing named gallery is returned WITHOUT its token). Anything else is a display name on a fresh hex id. Unlisted by default; &listed=1 puts it in GET /pics.",
      "response": {
        "id": "str",
        "url": "str",
        "admin_url": "str",
        "token": "str",
        "existed?": "bool",
        "expires_at": "str"
      }
    },
    "pics_index": {
      "method": "GET",
      "url": "https://skale.dev/throway/pics",
      "note": "gallery index: JSON for agents (listed galleries only), HTML with create form for browsers"
    },
    "pics_gallery": {
      "method": "GET",
      "url": "https://skale.dev/throway/pics/g/<gid>",
      "note": "one gallery: HTML grid for browsers (paginated ?p=N), JSON for agents (images[], pool, limits, upload how-to). Hidden images never appear. ?embed=1 renders a minimal, chrome-less view (transparent bg, no uploader) for <iframe> embedding."
    },
    "pics_upload": {
      "method": "POST",
      "url": "https://skale.dev/throway/pics/g/<gid>?name=<filename>",
      "body": "raw image bytes (or multipart/form-data for batches)",
      "note": "free upload into a gallery, public instantly. Images at or under 2048px are stored byte-identical (JPEG metadata stripped losslessly); only larger images are downscaled to 2048px WebP starting at q90, quality stepped down only while the result exceeds ~1024 kB (alpha preserved; GIFs pass through). Fixed lifetime 90d (slides the gallery's lifetime), shared pool 20 GB \u2014 full pool rejects with 507, never evicts. Max 30 MB per upload. Accepts jpeg/png/webp/gif/heic."
    },
    "pics_import_url": {
      "method": "POST",
      "url": "https://skale.dev/throway/pics/g/<gid>?url=<image-url>",
      "note": "server-side import of a remote image into a gallery (drag a picture from another site into the dropzone, or call directly). Public http(s) hosts only, images only, max 30 MB \u2014 then the normal pics pipeline applies (pixel rule, EXIF/GPS strip, pool).",
      "response": "same JSON as pics_upload"
    },
    "pics_image": {
      "method": "GET",
      "url": "https://skale.dev/throway/pics/i/<id>",
      "note": "serve one image inline; ?thumb=1 for a small cached WebP preview. Hidden or expired images -> 404."
    },
    "pics_like": {
      "method": "POST",
      "url": "https://skale.dev/throway/pics/i/<id>?like=1",
      "note": "toggle an image like. One like per visitor (pseudonymous fingerprint from IP+UA, keyed by the gallery's secret \u2014 the same visitor toggles off). Hidden/expired images -> 404.",
      "response": {
        "id": "str",
        "likes": "int",
        "liked": "bool"
      }
    },
    "pics_likes_json": {
      "method": "GET",
      "url": "https://skale.dev/throway/pics/g/<gid>?likes=1",
      "note": "cheap counters for polling / live ranking: image likes, comment likes, comment count \u2014 no image payloads.",
      "response": {
        "likes": {
          "<id>": "int"
        },
        "cl": {
          "<cid>": "int"
        },
        "comments": "int"
      }
    },
    "pics_comment": {
      "method": "POST",
      "url": "https://skale.dev/throway/pics/g/<gid>?comment=1",
      "body": "urlencoded form (or JSON): name (<=40 chars, optional \u2014 defaults to Gast), text (<=500 chars)",
      "note": "guestbook comment, no login. Cooldown 20s per visitor, max 500 per gallery (env-tunable). Browsers posting the form get redirected back to #comments; JSON clients get the created comment.",
      "response": {
        "id": "str",
        "name": "str",
        "text": "str",
        "ts": "float",
        "likes": "int"
      }
    },
    "pics_comment_like": {
      "method": "POST",
      "url": "https://skale.dev/throway/pics/g/<gid>?clike=<cid>",
      "note": "toggle a comment like \u2014 same fingerprint model as pics_like.",
      "response": {
        "id": "str",
        "likes": "int",
        "liked": "bool"
      }
    },
    "pics_admin": {
      "method": "GET/POST",
      "url": "https://skale.dev/throway/pics/g/<gid>/<secret>",
      "note": "admin of ONE gallery: the per-gallery token from creation (path segment, never a query param) or the server-wide superadmin token (env THROWAWAY_PICS_ADMIN_TOKEN). GET: admin page (/json for listing incl. hidden). POST form (id, action): hide | unhide | delete | up | down | cdel (delete a comment). Wrong secret -> 404."
    }
  }
}