{{define "api.html"}}{{template "base" .}}{{end}}

{{define "content"}}
<section class="docs-view" aria-labelledby="api-title">
  <div class="docs-header">
    <p class="kicker">Developer docs</p>
    <h1 id="api-title">Warpbox API</h1>
    <p>Anonymous uploads for curl, scripts, and ShareX. The upload endpoint accepts multipart files and returns either plain text or JSON based on the <code>Accept</code> header.</p>
  </div>

  <div class="docs-grid">
    <article class="card docs-card">
      <div class="card-content">
        <h2>Endpoints</h2>
        <dl class="endpoint-list">
          <div><dt>Upload</dt><dd><code>POST /api/v1/upload</code></dd></div>
          <div><dt>Resumable create</dt><dd><code>POST /api/v1/uploads/resumable</code></dd></div>
          <div><dt>Resumable status</dt><dd><code>GET /api/v1/uploads/resumable/{sessionID}</code></dd></div>
          <div><dt>Resumable chunk</dt><dd><code>PUT /api/v1/uploads/resumable/{sessionID}/files/{fileID}/chunks/{index}</code></dd></div>
          <div><dt>Resumable complete</dt><dd><code>POST /api/v1/uploads/resumable/{sessionID}/complete</code></dd></div>
          <div><dt>Health</dt><dd><code>GET /health</code></dd></div>
          <div><dt>Request schema</dt><dd><a href="/api/v1/schemas/upload-request.json"><code>/api/v1/schemas/upload-request.json</code></a></dd></div>
          <div><dt>Response schema</dt><dd><a href="/api/v1/schemas/upload-response.json"><code>/api/v1/schemas/upload-response.json</code></a></dd></div>
        </dl>
      </div>
    </article>

    <article class="card docs-card docs-card-wide">
      <div class="card-content">
        <h2>Resumable uploads</h2>
        <p>Browser uploads use the resumable API by default. Custom clients can use the same flow: create a session with file metadata, upload exact-sized chunks, then complete the session. Chunks are temporary and are cleaned if the session expires.</p>
        <pre><code># 1. Create a session.
curl -s {{.Data.BaseURL}}/api/v1/uploads/resumable \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{"files":[{"name":"report.pdf","size":1048576,"contentType":"application/pdf"}],"expiresMinutes":1440}'

# 2. Upload each chunk using the returned sessionId, file id, and chunkSize.
dd if=./report.pdf bs=8388608 count=1 skip=0 2>/dev/null | \
  curl -X PUT --data-binary @- \
  {{.Data.BaseURL}}/api/v1/uploads/resumable/SESSION_ID/files/FILE_ID/chunks/0

# 3. Complete after all chunks are present. The response is the normal upload JSON.
curl -X POST -H 'Accept: application/json' \
  {{.Data.BaseURL}}/api/v1/uploads/resumable/SESSION_ID/complete</code></pre>
        <p class="muted-copy">For authenticated uploads, send the same <code>Authorization: Bearer &lt;token&gt;</code> header on every resumable request. Incomplete chunks are stored under <code>data/tmp/uploads</code> before finalizing into the selected storage backend.</p>
      </div>
    </article>

    <article class="card docs-card">
      <div class="card-content">
        <h2>Curl upload</h2>
        <p>Without a JSON <code>Accept</code> header, Warpbox prints one plain box URL for shell-friendly usage.</p>
        <pre><code>curl -F file=@./report.pdf {{.Data.UploadURL}}</code></pre>
        <p>For automation, request JSON to get file URLs and the private manage/delete URLs.</p>
        <pre><code>curl -F file=@./report.pdf \
  -H 'Accept: application/json' \
  {{.Data.UploadURL}}</code></pre>
      </div>
    </article>

    <article class="card docs-card">
      <div class="card-content">
        <h2>JSON response</h2>
        <p>The raw delete token is returned only once inside <code>manageUrl</code> and <code>deleteUrl</code>. Keep those links private. On error the body is <code>{ "error": "message" }</code> with a non-2xx status (e.g. rate limited or over a limit).</p>
        <pre><code>{
  "boxId": "abc123",
  "boxUrl": "{{.Data.BaseURL}}/d/abc123",
  "zipUrl": "{{.Data.BaseURL}}/d/abc123/zip",
  "thumbnailUrl": "{{.Data.BaseURL}}/d/abc123/thumb/file123",
  "manageUrl": "{{.Data.BaseURL}}/d/abc123/manage/private-token",
  "deleteUrl": "{{.Data.BaseURL}}/d/abc123/manage/private-token/delete",
  "expiresAt": "2026-06-05T12:00:00Z",
  "files": [
    {
      "id": "file123",
      "name": "report.pdf",
      "size": "2.4 MiB",
      "url": "{{.Data.BaseURL}}/d/abc123/f/file123",
      "thumbnailUrl": "{{.Data.BaseURL}}/d/abc123/thumb/file123"
    }
  ]
}</code></pre>
      </div>
    </article>

    <article class="card docs-card">
      <div class="card-content">
        <h2>ShareX setup</h2>
        <p>Import the uploader, then add your API key to upload as your account — with your account's size, daily, and retention limits — instead of as an anonymous guest.</p>

        <h3>1 · Import the uploader</h3>
        <ol class="docs-steps">
          <li>Download <a href="/api/v1/sharex/warpbox-anonymous.sxcu"><code>warpbox-anonymous.sxcu</code></a>.</li>
          <li>In ShareX: <code>Destinations → Custom uploader settings → Import → From file</code>, then pick the <code>.sxcu</code>.</li>
        </ol>

        <h3>2 · Add your API key (upload as your account)</h3>
        <ol class="docs-steps">
          <li>Create a personal access token under <a href="/account/settings">Account → Access tokens</a> and copy it.</li>
          <li>In <code>Custom uploader settings</code>, select the Warpbox uploader and open the <code>Headers</code> section.</li>
          <li>Add a header — Name <code>Authorization</code>, Value <code>Bearer &lt;your token&gt;</code>.</li>
        </ol>
        <p class="muted-copy">Without that header, uploads stay anonymous. With it, they're attributed to your account and use your account's limits.</p>

        <pre><code>{
  "Version": "1.0.0",
  "Name": "Warpbox (my account)",
  "DestinationType": "ImageUploader, FileUploader, TextUploader",
  "RequestMethod": "POST",
  "RequestURL": "{{.Data.ShareXExampleURL}}",
  "Headers": {
    "Accept": "application/json",
    "Authorization": "Bearer <span class="sxcu-highlight">YOUR_API_TOKEN</span>",
    "X-Warpbox-Batch": "sharex"
  },
  "Body": "MultipartFormData",
  "FileFormName": "{{.Data.ShareXFileFieldName}}",
  "URL": "{json:boxUrl}",
  "ThumbnailURL": "{json:thumbnailUrl}",
  "DeletionURL": "{json:deleteUrl}",
  "ErrorMessage": "{json:error}"
}</code></pre>

        <h3>Grouping multiple files into one box</h3>
        <p>Grouping is <strong>opt-in via the <code>X-Warpbox-Batch</code> request header</strong> — without it, every file becomes its own box (the default). When the header is present, uploads sharing the same value (per account, or per IP for anonymous) within {{.Data.ShareXGroupWindow}} of each other are added to the <strong>same</strong> box, so a multi-file ShareX selection produces one shareable link instead of one per file. The shipped config sets <code>X-Warpbox-Batch: sharex</code>; remove that header for one box per file.</p>
        <p class="muted-copy">The response also exposes <code>{json:thumbnailUrl}</code> for ShareX previews, <code>{json:deleteUrl}</code> for the deletion URL, and <code>{json:error}</code> so ShareX surfaces messages like rate limiting.</p>
      </div>
    </article>

    <article class="card docs-card docs-card-wide">
      <div class="card-content">
        <h2>Multipart fields</h2>
        <div class="field-grid">
          <span><code>file</code></span><p>One or more files for curl, browser, and generic multipart clients.</p>
          <span><code>sharex</code></span><p>One or more files from ShareX custom uploader configs.</p>
          <span><code>max_days</code></span><p>Optional number of days before expiration. Defaults to 7.</p>
          <span><code>expires_minutes</code></span><p>Optional lifetime in minutes. Takes precedence over <code>max_days</code> when greater than zero — useful for sub-day expiries (e.g. <code>60</code> for one hour).</p>
          <span><code>max_downloads</code></span><p>Optional download count limit.</p>
          <span><code>password</code></span><p>Optional password required before viewing/downloading.</p>
          <span><code>obfuscate_metadata</code></span><p>Optional <code>on</code>; hides names/counts until unlock when a password is set.</p>
        </div>
      </div>
    </article>
  </div>
</section>
{{end}}
