POST /v1/uploads/from-url
Useful when you have an ephemeral link that needs a permanent home: an image-generation API result, a screenshot service URL, a search-result thumbnail, anything you would otherwise download then re-upload. dropkit fetches the source, streams it into R2, and returns the CDN URL in one round trip.
Request
POST https://api.dropkit.app/v1/uploads/from-urlAuthorization: Bearer sk_live_...Content-Type: application/json{ "url": "https://example.com/photo.jpg", "name": "hero.jpg", "visibility": "public", "metadata": { "source": "openai-image" }}| field | required | what it is |
|---|---|---|
url | yes | Absolute https URL. Must resolve to a public address. http, localhost, and private IPs are rejected. |
name | no | Override the stored filename. Defaults to the last path segment of the URL (decoded). Falls back to upload.<ext> if neither is usable. |
visibility | no | public (default) or private. Private files require a signed URL to read. |
metadata | no | Optional string -> string map stored with the file. |
Response
{ "id": "ggdhhwrfarqp", "url": "https://cdn.dropkit.app/ggdhhwrfarqp/hero.jpg", "name": "hero.jpg", "size": 283910, "type": "image/jpeg", "visibility": "public"}Status 201. The URL is immediately live. Same response shape as POST /v1/upload.
Auth
Real project key only. Browser (pk_live_*) and server (sk_live_*) keys both work; the bundled demo key (pk_demo_*) is rejected to prevent the public demo from being used as a free egress mirror.
Browser keys must satisfy the project’s origin allowlist as usual.
Size enforcement
dropkit issues a HEAD first to learn Content-Length and Content-Type. If the source declares a size, it is checked against the plan’s per-file cap (100 MB Free, 5 GiB paid) up front. If the size is unknown or the source lies, the GET stream is capped at the plan limit and aborted on overflow.
Per-plan storage caps and metered overage apply the same as for direct uploads.
SDK
import { uploadFromUrl } from '@dropkit/sdk';
const { data, error } = await uploadFromUrl( 'https://example.com/photo.jpg', { key: 'sk_live_...' },);if (error) throw new Error(error.message);console.log(data.url);Or with a client:
const client = dropkit({ key: process.env.DROPKIT_KEY });const { data } = await client.uploadFromUrl('https://example.com/photo.jpg');CLI
dropkit upload detects when the argument is an http(s):// URL and routes through this endpoint:
dropkit upload https://example.com/photo.jpgRequires a real key (saved via dropkit login, set via DROPKIT_KEY, or passed as --key). The demo fallback only applies to local file paths.
MCP
Agents call the dropkit_upload_url tool. See the MCP docs.
Errors
| HTTP | code | meaning |
|---|---|---|
| 400 | bad_url | url missing or not a valid absolute URL |
| 400 | bad_url_scheme | url is not https |
| 400 | host_blocked | Hostname is localhost or a private/loopback/link-local IP |
| 400 | bad_visibility | visibility is not public or private |
| 400 | empty_body | Source URL returned 0 bytes |
| 401 | missing_auth / invalid_key | Auth header missing or unrecognized |
| 402 | billing_past_due / quota_exceeded | Owner is past-due, or the file would exceed the storage cap |
| 403 | demo_not_allowed | Demo key cannot use URL ingest |
| 403 | origin_not_allowed | Browser key origin not on the allowlist |
| 413 | file_too_large | Source declared or streamed beyond the plan’s per-file cap |
| 502 | fetch_failed | The remote URL did not return 2xx, or could not be reached |
| 502 | ingest_failed | Stream into storage failed mid-flight |