Skip to main content

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

FieldTypeRequiredDescription
urlstringYesPublicly reachable http:// or https:// URL.
filenamestringNoOverride 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.

CategoryAllowed types
Imagesimage/jpeg, image/png, image/gif, image/webp
Videosvideo/mp4, video/quicktime, video/webm
Audioaudio/mpeg, audio/ogg, audio/mp4, audio/wav, audio/x-wav
Documentsapplication/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:// and https:// 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 IP 169.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.

warning

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.

Next