---
title: "Create a drop link where the user uploads files in a browser"
method: POST
path: "/sites/drops"
tags: ["Sites"]
---

# Create a drop link where the user uploads files in a browser

`POST /sites/drops`

## Request

- Base URL: `https://api.hypamoon.com/api/v1`
- URL: `https://api.hypamoon.com/api/v1/sites/drops`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://app.hypamoon.com/oauth/authorize`, token URL `https://api.hypamoon.com/oauth/token`, scopes: `sites:write`)

For when your environment cannot reach an upload_url (a sandbox in a chat app may block it), or the user has the files and you do not. Returns an upload page, https://app.hypamoon.com/drop/<token>, open for 24 hours or until the user presses Done, for this project (and the site, with site_id). The user opens it without signing in and drags files, a whole folder (folders stay folders under /media/<folder>/) or a zip, or chooses or takes photos on a phone; up to 100 files per link, within the plan's limits. Photos are made web-ready in the browser: a WebP of at most 2048 px plus a 640 px version, and the original is kept privately next to them (only an original larger than the plan allows per file is left out). An SVG (a logo or icon, at most 1 MB) goes on the site as it is, but only when it is plain drawing: an SVG with scripts, event handlers or links outside the file is refused. With site_id, Done puts the files on the site as a new version (site_version in GET /sites/drops/{id}); send only the HTML with keep_existing. Without site_id, GET /sites/drops/{id} lists the files with path, sha256 and bytes; deploy them with POST /sites or PUT /sites/{id} without content. The url is only in this response; the token is stored as a hash. user_message is a sentence to give the user with the link.

## Request body

- SiteDropInput: Create a drop link: a page where the user uploads files and whole folders in a browser.
  - `site_id` string, uuid: Optional. The site the files are for (from deploy_site or list_sites). Leave it out for a new site.
  - `strip` 'location' | 'all' | 'none': Optional. What the upload page removes, in the user's browser before upload, from photos (JPEG, PNG, WebP) it keeps as they are: originals the user chooses to keep, and photos that would not get smaller as WebP. location (default) removes only the GPS location and keeps camera, date, orientation and copyright; all removes all metadata (the orientation stays so photos stay upright); none keeps the files as they are. Web versions are re-encoded and carry no metadata at all. Use all or none only when the user asks for it.
  - `folder` string: Optional. The folder under /media/ where the files land, like portfolio or menu/2026. Folders the user drags keep their structure inside it: /media/portfolio/project-12/03.webp. Leave it out to use the user's own folder names.
  - `as_is` boolean: Optional. For moving an existing site (like WordPress uploads): true keeps every file exactly as it is, with no web versions, the same name and extension, and the exact folder path the user drags, under /media/<folder>/.
  - `keep_zip` boolean: Optional. true keeps a .zip the user adds as one file (a download, like a plugin or a pack of files) instead of unpacking it. On a site it is served as a download; the deploy check refuses a zip with a program, a password or another archive inside.

## Response `201`

The drop link.

- SiteDrop: A drop link and the files the user uploaded with it.
  - `id` string, uuid, required: The id of the drop link; pass it to get_drop (GET /sites/drops/{id}).
  - `status` 'open' | 'done' | 'expired', required: open: waiting for the user. done: the user uploaded the files. expired: 24 hours passed; create a new one.
  - `site_id` string, uuid, nullable, required: The site the link is for, or null.
  - `created_at` string, required
  - `expires_at` string, required: When the link stops working (24 hours after it was made).
  - `completed_at` string, nullable, required: When the user finished uploading, or null.
  - `images_only` boolean, required: True when the link only takes images (an editor made it).
  - `as_is` boolean: True when files are kept exactly as they are (for moving a site): no web versions, same names, and paths that follow the user's folders exactly.
  - `keep_zip` boolean: True when a .zip stays one file (a download) instead of being unpacked.
  - `strip` 'location' | 'all' | 'none', required: What the upload page removes from photos before upload: location (the default, only the GPS location), all (all metadata) or none. Web versions are re-encoded and carry no metadata at all.
  - `folder` string, nullable, required: The folder under /media/ the files land in, or null for the user's own folder names.
  - `max_files` integer, required: How many files the user can add with this link (a photo with its versions counts once).
  - `files` object[], required: The files that arrived, in order. Private originals are not listed.
    - `path` string, required: Where the file lives on the site, like /media/portfolio/project-12/03.webp. You can use another path in deploy_site; keep sha256 and bytes.
    - `sha256` string, required: The SHA-256 of the file.
    - `bytes` integer, required: The size in bytes.
    - `content_type` string, required: The type the file will be served with.
    - `name` string: The file name on the user's computer.
    - `variant` '2048' | '640': For photos made web-ready in the browser: 2048 is the web version (longest side at most 2048 px), 640 the small version of the same photo (path ends in -640). Use the small one in srcset, or for a thumbnail /_hypamoon/img?src=<path>&w=160 on the site.
    - `width` integer: Width in pixels, for photos.
    - `height` integer: Height in pixels, for photos.
  - `originals_kept` integer, required: How many originals were kept. They are private: never on a site, only downloadable in the portal.
  - `site_version` object, nullable, required: With site_id, after the user pressed Done: the new version of the site with these files (waiting: it waits for four-eyes approval). The files are already on the site; send only HTML with keep_existing.
    - `version_id` string, nullable, required
    - `version` integer, nullable, required
    - `waiting` boolean, required
    - `files` integer, required
  - `deploy_files` object[], required: The same files in the form deploy_site takes: add them to files, without content.
    - `path` string, required
    - `sha256` string, required
    - `bytes` integer, required
  - `url` string: Only when the link is created: the page to give the user. It is not shown again.
  - `user_message` string: A sentence to give the user.
  - `next_step` string: What to do next.

## Other responses

- `400`: Bad Request: invalid_json.
- `401`: Unauthorized: unauthorized, missing_api_key, invalid_api_key, api_key_revoked, project_deleted.
- `403`: Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.
- `404`: Not Found: not_found.
- `422`: Unprocessable Content: validation_failed.
- `429`: Too Many Requests: rate_limited.
- `500`: Internal Server Error: internal_error.
- `503`: Service Unavailable: service_unavailable.

---

[API](https://skmtc.dev/hypamoon/apis/hypamoon-api.md) · [All operations](https://skmtc.dev/hypamoon/apis/hypamoon-api/llms.txt) · [OpenAPI document](https://skmtc.dev/hypamoon/apis/hypamoon-api/revisions/9f0b90004c06?raw)
