Errors
Every API error has an HTTP status and a JSON body with a stable, machine-readable code, a human-readable message and, sometimes, details:
{
"error": {
"code": "missing_files",
"message": "Upload every file before finalizing",
"details": { "missingFiles": ["index.html"] }
}
}
Match on code, not on message: messages can be reworded. The CLI prints the same code and message to stderr (for example missing_files: Upload every file before finalizing) and exits with status 1.
Requests and manifests
| Code | Status | Meaning | What to do |
invalid_json | 400 | The body is not a JSON object. | Send a JSON object with Content-Type: application/json. |
body_too_large | 413 | The request body is too large. | Send a smaller manifest. |
invalid_manifest | 400 | files is missing, empty, has too many entries, or an entry is not an object. A ZIP can also expand past the file limit. | Send 1 to 500 files. |
invalid_path | 400 | A file path is hidden, traverses folders, is too long, is not NFC-normalised, starts with the reserved __edge segment or has a forbidden character. | Use relative, NFC-normalised paths with no segments starting with . (which also rules out ..), and no %, ?, #, backslashes or control characters. |
duplicate_path | 400 | Two files have the same path, possibly after a ZIP was unpacked. | Make every path unique. |
invalid_hash | 400 | A file hash is not a lower-case SHA-256 hex string. | Send the SHA-256 of each file's exact bytes. |
invalid_content_type | 400 | A content type is malformed. | Use a plain type such as text/html; charset=utf-8. |
invalid_spa_mode | 400 | spaMode is not true or false. | Send a boolean. |
missing_index | 400 | Single-page app mode is on but there is no root index.html. | Add index.html, or set spaMode to false. |
invalid_title | 400 | The title is not a string of up to 120 characters. | Shorten the title. |
invalid_slug | 400 | The name is not 3 to 48 lower-case letters, numbers and inner hyphens, or it is reserved. | Choose another name, or omit it for a random one. |
invalid_password | 400 | A visitor password is not 8 to 256 characters. | Choose a longer or shorter password. |
use_metadata | 400 | An update tried to change the password. | Use PATCH /publish/{slug}/metadata. |
empty_patch | 400 | A metadata change had nothing to change. | Send title, password or feedbackEnabled. |
version_required | 400 | A finalise call was sent without a versionId. | Send the versionId from the create or update response. |
Access
| Code | Status | Meaning | What to do |
unauthorized | 401 | The key is missing, malformed, revoked or removed. | Check the key. If it worked before, it has probably been removed: ask the account owner, do not retry. |
anonymous_disabled | 401 | This service only accepts publishing with a key. | Send a key. |
forbidden | 403 | The key does not own this link. | Use the owner's key. |
claim_required | 403 | The link is an unclaimed guest link and no valid claim token was sent. | Send its claim token, or claim it first. |
feedback_owner_only | 403 | Only the owner can turn feedback on or off. | Use the owner's key, not a claim token. |
cross_origin | 403 | A browser request came from another website. | Call the API from a server, CLI or agent. |
wrong_host | 421 | The request was sent to the wrong host. | Use https://publish.dropzoom.link. |
challenge_required | 400 | A request without a key tried to create a guest link, which needs a human check. | Send a key. Guest links are for people in a browser. |
challenge_failed | 403 | The human check for a guest link did not pass. | Complete the check again in the browser. |
challenge_expired | 403 | The human check expired or was already used. | Complete a new check in the browser. |
Uploads and finalising
| Code | Status | Meaning | What to do |
invalid_upload_token | 403 | The upload URL's token is wrong or was replaced by a refresh. | Use the latest upload URLs. |
length_required | 411 | An upload had no Content-Length. | Send Content-Length equal to the manifest size. |
size_mismatch | 400 | The uploaded bytes differ in size from the manifest. | Upload the exact file you described. |
checksum_mismatch | 400 | The uploaded bytes do not match the manifest hash. | Upload the exact file you described. |
upload_expired | 410 | Upload URLs last one hour, and a staged version can only be refreshed for 24 hours after it was created. | Within 24 hours, call uploads/refresh for fresh upload URLs, then upload and finalise. After 24 hours, stage a new version by publishing again. |
missing_files | 400 | Finalising was attempted before every file was uploaded. details.missingFiles lists them. | Upload the listed files, then finalise again. |
invalid_zip | 400 | A ZIP file is damaged or uses a feature DropZoom does not support. | Rebuild the ZIP with standard settings. |
invalid_zip_path | 400 | A path inside a ZIP is unsafe, such as an absolute path or one that traverses folders. | Rebuild the ZIP with relative paths. |
encrypted_zip | 400 | The ZIP is password-protected. | Upload an unencrypted ZIP. |
empty_zip | 400 | The ZIP has no publishable files. | Check what the ZIP contains. |
nested_archive | 400 | A ZIP contains another ZIP, 7z or tar archive. | Unpack inner archives before zipping. |
zip_changed | 409 | The ZIP changed while it was being unpacked. | Publish again. |
secret_detected | 422 | The content looks like it contains a secret, such as a private key or access token. details.files names the files. | Remove the secret, rotate it if it was real, then publish again. |
malware_detected | 422 | A guest publication was refused by malware scanning. | Do not publish that file. |
finalize_conflict | 409 | The version changed while it was being finalised. | Retry finalising. |
Conflicts and state
| Code | Status | Meaning | What to do |
not_found | 404 | The link, version, upload or route does not exist. | Check the name and path. |
method_not_allowed | 405 | The route exists but not with that HTTP method. | Use a method listed for the route in the HTTP API. |
expired | 410 | The guest link has expired. | Publish again. Expired guest links cannot be claimed. |
slug_taken | 409 | The name is already in use, or was used before. Names are never reused. | Choose another name. |
anonymous_slug | 400 | A guest publication tried to choose its name. | Omit slug; guest links get random names. |
version_conflict | 409 | Someone else published first. details.currentVersionId shows the live version when known. | Read the link, then publish again from its current version. |
cannot_prune | 409 | The version is live or does not exist. | Prune a version that is not live. |
already_claimed | 409 | Another account owns this guest link now. | Nothing to do. |
claim_conflict | 409 | The link changed while it was being claimed. | Retry the claim. |
Limits
| Code | Status | Meaning | What to do |
file_too_large | 413 | A file is over the per-file limit. | See Limits. |
site_too_large | 413 | The publication is over the per-publication limit, possibly after unpacking a ZIP. | Publish less, or split it across links. |
guest_site_too_large | 413 | A guest publication is over the guest limit. | Register to publish larger work. |
storage_limit | 413 | Your account's total storage is full. Only the live and unfinished (staged) versions of your links count. | Delete links you no longer need, prune unfinished versions, or publish less. Pruning retired versions does not free space, because they do not count. |
guest_capacity | 503 | Guest publishing is temporarily full. | Register to publish now. |
site_limit | 409 | You have reached the number of links an account can have. | Delete a link you no longer need. |
version_limit | 409 | This link has reached the number of versions it can keep, including pruned versions that have not been cleaned up yet. | Prune old versions, then retry after cleanup has removed them, which takes about 2 hours. |
rate_limited | 429 | Too many requests from your address. | Wait a minute, then retry. |
Feedback
| Code | Status | Meaning | What to do |
invalid_state | 400 | state is not open, resolved or all. | Use one of those values. |
invalid_limit | 400 | limit is not a whole number from 1 to 200. | Use a smaller whole number. |
invalid_cursor | 400 | The pagination cursor is malformed. | Use the nextCursor value exactly as returned. |
invalid_feedback_enabled | 400 | feedbackEnabled is not true or false. | Send a boolean. |
invalid_request | 400 | Resolve needs ids or all, not both, and IDs must be 32-character feedback IDs. | Send one or the other. |
too_many_ids | 400 | More than 90 IDs in one resolve call. | Split the IDs across calls. The CLI does this for you. |
feedback_not_found | 404 | One or more IDs are not feedback on this link. details.ids lists them. | Check the IDs. |
Temporary problems
| Code | Status | Meaning | What to do |
content_scan_unavailable | 503 | Secret scanning could not finish. Nothing was published. | Retry finalising later. |
malware_scan_unavailable | 503 | Malware scanning for a guest publication could not finish. Nothing was published. | Retry later. |
zip_unpack_unavailable | 503 | A ZIP could not be unpacked just now. | Retry finalising later. |
challenge_unavailable | 503 | The human check could not be completed. | Retry later. |
publish_outcome_unknown | 503 | The service could not confirm whether the link was created. | List your links before retrying, so you do not create a duplicate. |
internal_error | 500 | Something unexpected went wrong. | Retry later. If it keeps happening, use the contact form. |