Create an Attachment from a URL
If your file is already publicly hosted somewhere (your CDN, a partner's S3 bucket, a webcam image endpoint, etc.), you don't need to download it locally and re-upload. Hand SendSeven the URL and we fetch it server-side, validate it, store it, and return a normal attachment record.
POST /api/v1/attachments/from-url
Content-Type: application/json
Required scope: messages:create
Request body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Publicly reachable http:// or https:// URL. |
filename | string | No | Override the stored filename. If omitted, derived from the URL path; falls back to a UUID. |
curl
curl -X POST "https://api.sendseven.com/api/v1/attachments/from-url" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"url": "https://cdn.example.com/products/widget-v2.jpg",
"filename": "widget-v2.jpg"
}'
Response (201 Created)
The shape is identical to POST /upload, so your message-sending code does not need to know which path produced the attachment:
{
"id": "9b3f1a8e-7c2d-4e5b-9f01-12a3b4c5d6e7",
"attachment_id": "9b3f1a8e-7c2d-4e5b-9f01-12a3b4c5d6e7",
"filename": "widget-v2.jpg",
"content_type": "image/jpeg",
"size": 184221,
"storage_path": "tenant_abc/attachments/9b3f1a8e-...jpg",
"download_url": "https://api.sendseven.com/api/v1/attachments/9b3f1a8e-.../download",
"public_url": "https://api.sendseven.com/api/v1/attachments/public/<tenant>/<id>/widget-v2.jpg"
}
Per-tenant URL deduplication
If you call from-url again with the exact same URL (full match, including query string), SendSeven returns the existing attachment instead of re-fetching and re-storing. This is per-tenant -- another tenant calling with the same URL gets their own copy. Tenants cannot see or share each other's cached attachments.
This means it is safe to call from-url repeatedly in your code paths -- you'll only pay the network/storage cost the first time per URL.
# Call 1 -- fetches and stores. Returns id "9b3f...".
curl ... -d '{"url": "https://cdn.example.com/logo.png"}'
# Call 2 -- same URL. Returns the SAME id "9b3f..." instantly, no re-fetch.
curl ... -d '{"url": "https://cdn.example.com/logo.png"}'
Forcing a re-fetch (dynamic URLs)
Some URLs serve different bytes over time even though the path is the same -- a webcam snapshot endpoint, a generated thumbnail service, a "latest report" CSV. For these, dedup is wrong: you want fresh bytes each time.
The trick: add a query parameter that changes. Any change to the URL string (including a new ?t=... value) makes it a different URL from the dedup table's perspective, so SendSeven fetches it fresh.
# Webcam snapshot -- the URL is the same every minute, but the bytes change.
# Without a cache-buster you'd send last hour's snapshot forever.
TS=$(date +%s)
curl -X POST "https://api.sendseven.com/api/v1/attachments/from-url" \
-H "Authorization: Bearer s7_api_..." \
-H "Content-Type: application/json" \
-d "{\"url\": \"https://webcam.example.com/snapshot.jpg?t=${TS}\"}"
The query param is not used by the source server (typically); it just makes each request a unique cache key on our side.
Allowed MIME types
The from-url endpoint uses a stricter allowlist than /upload because the bytes come from arbitrary external hosts. If you need a type that is not on this list, use /upload instead.
| Category | Allowed types |
|---|---|
| Images | image/jpeg, image/png, image/gif, image/webp |
| Videos | video/mp4, video/quicktime, video/webm |
| Audio | audio/mpeg, audio/ogg, audio/mp4, audio/wav, audio/x-wav |
| Documents | application/pdf |
Anything else returns 415 Unsupported Media Type with allowed_types listed in the response body.
Security restrictions (SSRF)
The from-url endpoint is a server-side request, so we have to be paranoid about where we let you point us. The following URLs are rejected with 400 URL not allowed:
- Non-HTTP schemes -- only
http://andhttps://accepted.file://,gopher://,data:,ftp://, etc. are rejected. - Private IPs -- RFC1918 (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16), ULA (fc00::/7). - Loopback --
127.0.0.0/8,::1,localhost. - Link-local --
169.254.0.0/16(this includes the cloud metadata IP169.254.169.254). - Reserved / multicast / unspecified addresses.
- Hostnames that fail DNS resolution.
- Dual-record DNS rebinding -- if a hostname resolves to multiple IPs and any one is private, the whole request is rejected.
Redirects are not followed. If your source URL responds with a 3xx, the call fails.
The URL must be reachable from the public internet. Pre-signed URLs from S3 / GCS / Azure work fine as long as they resolve to a public IP.
Size limit
50 MB hard cap. Enforced both via Content-Length (when the source server sends it honestly) and via streaming cut-off (we stop reading at 50 MB even if the source lies). Oversized files return 413 Payload Too Large.
Timeouts
- Connect timeout: 5 seconds.
- Total fetch time: 30 seconds.
Slow donor servers will fail fast with 400 Failed to fetch URL.
Retention
Attachments created via from-url follow the same retention rule as uploads: deleted 90 days after their last use. See Attachments FAQ: How long are attachments stored? for details.