Get Public URLs for ClickUp Attachments: No Auth Required
You attached a mockup to a ClickUp task, copied the attachment URL, and sent it to a client. They clicked the link and got a login screen. They do not have a ClickUp account. They cannot see the file.
This happens constantly. ClickUp is excellent for internal project management, but it was not built to serve files publicly. Every attachment lives behind workspace authentication. If the person opening the link is not a member of your ClickUp workspace, the file is invisible to them.
The fix is straightforward: download the file from ClickUp and re-upload it to a public CDN that gives you a permanent, open URL. This guide covers four ways to do that, from a quick manual approach with curl to fully automated workflows with Make and n8n. All of them use FilePost as the public hosting layer.
Why ClickUp Attachments Aren't Public
ClickUp treats every attachment as a private workspace asset. This is by design. When you upload a file to a task, ClickUp stores it in your workspace's storage and generates a URL that is scoped to authenticated workspace members only.
There are three specific constraints that make ClickUp attachment URLs unusable outside the workspace:
- Authentication required. Every attachment URL requires an active ClickUp session. If the viewer is not logged into the workspace, the request is rejected. There is no anonymous access option.
- Workspace-scoped access. Even if someone has a ClickUp account, they must be a member of your specific workspace to access the file. Being logged into their own ClickUp workspace does not help. This means freelancers, clients, and external partners are locked out.
- No public sharing toggle. Unlike ClickUp Docs (which have a public sharing option), attachments do not have a "make public" button. There is no built-in way to generate a publicly accessible link for an uploaded file. You can share a task view publicly, but the attachment URLs within that view still require authentication to download.
This is a reasonable security default for internal project files. But it creates a real problem when you need to share deliverables with clients, embed files in external systems, reference attachments in emails, or pass file URLs to third-party integrations that cannot authenticate with ClickUp.
The Fix: Re-Upload to a Public CDN
The solution is a two-step process: pull the file out of ClickUp, then push it to a service that gives you a public URL. That is exactly what FilePost does.
FilePost is a file upload API built for developers. You POST a file, you get back a permanent CDN URL. The URL is public, it loads fast globally via Cloudflare, and it stays live until you explicitly delete it. No authentication required for viewers.
The flow looks like this:
- Download from ClickUp. Use the ClickUp API to fetch the attachment. This requires your ClickUp API token (a personal token or OAuth token with access to the workspace).
- Upload to FilePost. Send the downloaded file to the FilePost upload endpoint with your API key.
- Use the public URL. FilePost returns a permanent CDN URL. Send it to your client, embed it on a page, store it in a database, or post it back into the ClickUp task as a comment.
Let's walk through the implementation, starting with the simplest method.
Manual Method: curl
If you need to grab a single attachment and make it public, curl is the fastest way. Two commands. No scripts, no setup.
Step 1: Download the Attachment from ClickUp
You need the attachment URL from the ClickUp API. First, get the task's attachments. Replace TASK_ID with your ClickUp task ID and YOUR_CLICKUP_TOKEN with your personal API token (found in ClickUp Settings > Apps):
curl -s \
-H "Authorization: YOUR_CLICKUP_TOKEN" \
"https://api.clickup.com/api/v2/task/TASK_ID" \
| python3 -m json.tool
The response includes an attachments array. Each attachment has a url field. That URL is what you need to download the file. Note that this URL still requires authentication to access directly in a browser.
Download the file:
curl -s \
-H "Authorization: YOUR_CLICKUP_TOKEN" \
-o "design-mockup.png" \
"https://attachments.clickup.com/YOUR_ATTACHMENT_URL"
You now have the file saved locally. The -o flag saves it with the filename you specify.
Step 2: Upload to FilePost
Now upload the downloaded file to FilePost to get a public URL:
curl -X POST https://filepost.dev/v1/upload \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@design-mockup.png"
Response:
{
"url": "https://cdn.filepost.dev/abc123/design-mockup.png",
"file_id": "abc123",
"name": "design-mockup.png",
"size": 45321
}
That url is public. Anyone can open it. No ClickUp account, no login, no workspace membership. Send it to your client, paste it into an email, embed it in a Notion page. It just works.
One-Liner (Piped)
You can chain both steps into a single command:
curl -s -H "Authorization: YOUR_CLICKUP_TOKEN" \
"https://attachments.clickup.com/YOUR_ATTACHMENT_URL" \
| curl -X POST https://filepost.dev/v1/upload \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@-;filename=design-mockup.png"
The @- tells curl to read from stdin, and ;filename= sets the filename for the upload. The output is the same FilePost JSON response with your public URL.
Python Script: Batch Export ClickUp Attachments
If you have multiple tasks with attachments that need public URLs, a Python script is more practical than running curl commands one at a time. The script below connects to the ClickUp API, pulls all attachments from a specified task (or list of tasks), uploads each one to FilePost, and optionally posts the public URL back as a comment on the task.
import requests
CLICKUP_TOKEN = "your_clickup_personal_token"
FILEPOST_API_KEY = "your_filepost_api_key"
CLICKUP_HEADERS = {"Authorization": CLICKUP_TOKEN}
FILEPOST_HEADERS = {"X-API-Key": FILEPOST_API_KEY}
def get_task_attachments(task_id):
"""Fetch a task and return its attachments list."""
resp = requests.get(
f"https://api.clickup.com/api/v2/task/{task_id}",
headers=CLICKUP_HEADERS,
)
resp.raise_for_status()
return resp.json().get("attachments", [])
def download_attachment(attachment):
"""Download a single attachment from ClickUp, return bytes and filename."""
url = attachment["url"]
filename = attachment.get("title", url.split("/")[-1])
resp = requests.get(url, headers=CLICKUP_HEADERS)
resp.raise_for_status()
return resp.content, filename
def upload_to_filepost(file_bytes, filename):
"""Upload file bytes to FilePost, return the public CDN URL."""
resp = requests.post(
"https://filepost.dev/v1/upload",
headers=FILEPOST_HEADERS,
files={"file": (filename, file_bytes)},
)
resp.raise_for_status()
return resp.json()
def post_comment(task_id, comment_text):
"""Post a comment on a ClickUp task with the public URL."""
requests.post(
f"https://api.clickup.com/api/v2/task/{task_id}/comment",
headers={**CLICKUP_HEADERS, "Content-Type": "application/json"},
json={"comment_text": comment_text},
)
def process_task(task_id, post_back=True):
"""Download all attachments from a task, upload to FilePost, optionally comment."""
attachments = get_task_attachments(task_id)
if not attachments:
print(f"Task {task_id}: no attachments found.")
return []
results = []
for att in attachments:
file_bytes, filename = download_attachment(att)
result = upload_to_filepost(file_bytes, filename)
public_url = result["url"]
results.append({"filename": filename, "url": public_url})
print(f" {filename} -> {public_url}")
if post_back:
post_comment(task_id, f"Public URL for {filename}: {public_url}")
return results
# --- Usage ---
# Process a single task
task_ids = ["abc123", "def456", "ghi789"]
for tid in task_ids:
print(f"\nProcessing task {tid}...")
process_task(tid, post_back=True)
Here is what each function does:
get_task_attachments()calls the ClickUp API to retrieve a task's details and extracts the attachments array.download_attachment()downloads the actual file bytes using the attachment URL with your ClickUp auth header.upload_to_filepost()sends the file to FilePost and returns the response, which includes the permanent CDN URL.post_comment()optionally posts the public URL back to the ClickUp task as a comment, so your team can see the link without leaving ClickUp.process_task()ties it all together for a single task. Loop over a list of task IDs to batch-process as many as you need.
To process an entire ClickUp list, you can first call the /list/{list_id}/task endpoint to get all task IDs, then loop process_task() over each one. The free FilePost plan includes 30 uploads per month. If you are doing bulk exports, the Starter plan ($9/mo) gives you 500 uploads.
Automate with Make
If you want public URLs generated automatically every time someone attaches a file to a ClickUp task, Make (formerly Integromat) is a good option. No code required after the initial setup.
Scenario Overview
The Make scenario has four modules:
- ClickUp: Watch Events. Triggers when a new attachment is added to any task in a specified Space or List.
- HTTP: Get a File. Downloads the attachment from ClickUp using the attachment URL and your ClickUp API token.
- HTTP: Make a Request (FilePost Upload). Sends the downloaded file to the FilePost upload endpoint.
- ClickUp: Update Task. Writes the public FilePost URL back to a custom field on the task (or posts it as a comment).
Step 1: Set Up the ClickUp Trigger
Add a ClickUp > Watch Events module as the trigger. Connect your ClickUp account and configure it to watch for taskAttachmentUpdated events in the Space or List where you want automatic public URLs. When a file is attached to a task, this module fires and provides the task ID and attachment metadata.
Step 2: Download the Attachment
Add an HTTP > Get a File module. Set the URL to the attachment URL from the trigger output. Under Headers, add Authorization with your ClickUp personal API token. This downloads the file into Make's internal buffer.
Step 3: Upload to FilePost
Add an HTTP > Make a Request module with this configuration:
- URL:
https://filepost.dev/v1/upload - Method: POST
- Headers:
X-API-Key= your FilePost API key - Body type: Multipart/form-data
- Fields: Add a field named
file, set its type to File, and map the file data from the previous HTTP module
The response will contain the public CDN URL in the url field.
Step 4: Update the ClickUp Task
Add a ClickUp > Update Task module (or Create Task Comment). If you created a custom field called "Public File URL" on your tasks, map the FilePost URL from the previous step into that field. Alternatively, use the Create Task Comment module to post a comment with the link.
Once activated, this scenario runs every time an attachment is added. Your team attaches a file in ClickUp, and within seconds the public URL appears on the task. No manual steps.
Get Public URLs for Your Files
FilePost gives you permanent CDN URLs with a single API call. Free plan includes 30 uploads/month. No credit card required.
Get Your Free API KeyAutomate with n8n
If you prefer self-hosted automation or already run n8n, the same workflow is easy to build there. n8n gives you more control over error handling and data transformation.
Workflow Overview
The n8n workflow has four nodes:
- Webhook Trigger. Receives ClickUp webhook events when a new attachment is added.
- HTTP Request (Download). Downloads the file from ClickUp.
- HTTP Request (FilePost Upload). Uploads the file to FilePost.
- HTTP Request (ClickUp Comment). Posts the public URL back to the task as a comment.
Step 1: Set Up the Webhook
Add a Webhook node and copy its URL. In ClickUp, go to Settings > Integrations > Webhooks and create a new webhook pointing to that URL. Select the taskAttachmentUpdated event. Now whenever someone attaches a file to a task, ClickUp will POST the event data to your n8n webhook.
Step 2: Download the Attachment
Add an HTTP Request node:
- Method: GET
- URL: Map the attachment URL from the webhook payload
- Headers:
Authorization= your ClickUp personal token - Response Format: File (so n8n stores the result as binary data)
Step 3: Upload to FilePost
Add a second HTTP Request node:
- Method: POST
- URL:
https://filepost.dev/v1/upload - Headers:
X-API-Key= your FilePost API key - Body Content Type: Multipart Form Data
- Send Binary Data: Enabled
- Binary Property:
data - Parameter Name:
file
If you have the n8n-nodes-filepost community node installed, you can use the dedicated FilePost node instead of configuring the HTTP Request manually. Either method produces the same result.
Step 4: Post the URL Back to ClickUp
Add a third HTTP Request node:
- Method: POST
- URL:
https://api.clickup.com/api/v2/task/{{ $json.task_id }}/comment(map the task ID from the webhook) - Headers:
Authorization= your ClickUp token,Content-Type=application/json - Body:
{"comment_text": "Public URL: {{ $node['FilePost Upload'].json.url }}"}
Activate the workflow. From now on, every attachment added to a ClickUp task in the monitored workspace triggers the flow: download, upload to CDN, post public URL as a comment. Your team sees the public link without leaving ClickUp, and clients or external partners can access the file without any login.
Tips for Production Use
A few things to keep in mind when deploying any of these methods:
- ClickUp API rate limits. ClickUp limits API calls to 100 requests per minute per token. If you are batch-processing hundreds of tasks, add a short delay between requests in your Python script or use Make/n8n's built-in rate limiting.
- File size limits. FilePost's free plan supports files up to 50 MB. The Starter plan ($9/mo) supports 200 MB, and the Pro plan ($29/mo) supports 500 MB. Most ClickUp attachments (screenshots, documents, design files) are well within the free tier limit.
- Keep the original. Re-uploading to FilePost does not delete the file from ClickUp. You still have the original attachment on your task. The FilePost URL is an additional, public copy.
- Custom fields vs. comments. In the automated workflows, you can either post the URL as a task comment or write it to a custom field. Custom fields are better if you want to query or filter tasks by whether they have a public URL. Comments are simpler and do not require custom field setup.
FAQ
Are ClickUp attachment URLs public?
No. ClickUp attachment URLs are private by default. Anyone who clicks a ClickUp attachment link must be logged into the workspace that owns the file. If you send the URL to a client or embed it on a public page, it will fail to load. To make a ClickUp attachment publicly accessible, you need to download it and re-upload it to a public file host like FilePost.
How do I share ClickUp files without requiring login?
Download the attachment from ClickUp (manually or via the ClickUp API), then upload it to a public CDN service like FilePost. FilePost returns a permanent public URL that anyone can open without authentication. You can do this manually with curl, automate it with a Python script, or set up a Make or n8n workflow that runs automatically whenever a new attachment is added.
Can I get a permanent URL for ClickUp attachments?
Yes, but not directly from ClickUp. ClickUp attachment URLs are workspace-scoped and require authentication. To get a permanent, publicly accessible URL, download the file from ClickUp and upload it to FilePost. FilePost stores files on a global CDN and the URL never expires unless you explicitly delete the file.
Does FilePost work with ClickUp?
Yes. You can use the ClickUp API to download attachments and the FilePost API to upload them and get public CDN URLs. This works with curl, Python, or any HTTP client. You can also automate the process with Make or n8n so that every new ClickUp attachment automatically gets a public URL.
Try FilePost Free
Get permanent, public CDN URLs for any file. 30 free uploads per month, no credit card required.
Get Your Free API Key