DemandFlow Support Centre

API: POST /v1/files/public. Upload a public asset

ReferenceAPI Reference16/04/2026Updated 16/04/2026
Upload an image or other public asset to your tenant public bucket and get back a direct URL for use in knowledge base article bodies and other public HTML.

POST /v1/files/public

Uploads an asset to your tenant's public bucket and returns a direct URL that can be embedded in public-facing content such as knowledge base article images.

Public content only. Files uploaded through this endpoint land in your subscription's public S3 bucket and are readable by anyone who knows the URL, with no authentication. Only upload material that is intended to be public: images, diagrams, and other assets referenced from your portal's knowledge base or from any HTML field whose rendered output is public. Do NOT use this endpoint for confidential documents, customer data, invoices, contracts, pitch decks, or anything you would not print on a billboard. For private file uploads, use the dedicated private-upload endpoints inside the DemandFlow app.

When to use this

  • Embedding an image in a public knowledge base article (KBPUBLIC content field).
  • Attaching a logo, diagram, or screenshot to a public portal page.
  • Generating the target URL for any <img> tag that must be reachable without a login.

How it works

Uploads are a two-step presigned POST flow. The REST API never sees the file bytes itself.

  1. Your code calls POST /v1/files/public with the filename. The server returns a short-lived presigned S3 POST form and the final public URL the file will have once uploaded.
  2. Your code POSTs the file bytes directly to the returned S3 endpoint, with the form fields and the file attached as file. S3 stores the object in your tenant's public bucket.
  3. The public URL returned in step 1 is now live. Use it as the src of an <img> tag, or anywhere else a public HTTPS URL is expected.

Authentication

Standard PAT bearer token in the Authorization header, like every other endpoint under rest.demandflow.com.

Request body

{
  "filename": "network-diagram.png"
}
  • filename (optional): the name to use when storing the object. Non-alphanumeric characters other than . _ - are replaced with underscores, and the name is truncated to 120 characters. If omitted, file is used.

Response

{
  "url":     "https://<your-sub-id>-public.s3.eu-west-2.amazonaws.com/",
  "fields":  {
    "key":                "<your-sub-id>/a1b2c3d4-.../network-diagram.png",
    "bucket":              "<your-sub-id>-public",
    "X-Amz-Algorithm":     "...",
    "X-Amz-Credential":    "...",
    "X-Amz-Date":          "...",
    "Policy":              "...",
    "X-Amz-Signature":     "..."
  },
  "fileKey": "<your-sub-id>/a1b2c3d4-.../network-diagram.png",
  "fileUrl": "https://<your-sub-id>-public.s3.eu-west-2.amazonaws.com/<your-sub-id>/a1b2c3d4-.../network-diagram.png"
}
  • url: the S3 endpoint you POST the file bytes to in step 2.
  • fields: an opaque map of form fields returned by S3. Submit every one of them as part of the upload form, before the file itself.
  • fileKey: the object's key within the bucket. Useful for audit logs or for constructing the URL yourself.
  • fileUrl: the final public URL. Store this value, not the presigned url, in any record that needs to reference the file after upload.

Limits

  • Maximum file size: 10 MB per upload.
  • Presigned URL validity: 10 minutes from issue. Upload must complete within that window.
  • Content type is inferred by S3 from the file bytes or the Content-Type header you pass on the POST. For browser uploads of <input type="file"> this is set automatically.

Error responses

  • 401: no Authorization header.
  • 403: invalid or expired token.
  • 400: malformed JSON body.
  • 405: the endpoint only accepts POST.
  • 500: internal error. The S3 presign failed for example because the bucket is misconfigured. Retry after a short delay. If it persists, contact support.

Example: curl

Two requests. The first asks the API for a presigned form. The second uploads the file directly to S3.

# Step 1: request a presigned POST
curl -s -X POST "https://rest.demandflow.com/v1/files/public" \
  -H "Authorization: Bearer <your-pat>" \
  -H "Content-Type: application/json" \
  -d '{"filename":"diagram.png"}' \
  -o presign.json

# Step 2: upload to S3. jq reads the form fields out of the response.
UPLOAD_URL=$(jq -r '.url' presign.json)
FIELDS=$(jq -r '.fields | to_entries[] | "-F \(.key)=\(.value)"' presign.json | tr '\n' ' ')
eval curl -s $FIELDS -F 'file=@./diagram.png' "$UPLOAD_URL"

# Step 3: the file is now public at
jq -r '.fileUrl' presign.json

Example: Node

async function uploadPublic(pat, file) {
  const presign = await fetch('https://rest.demandflow.com/v1/files/public', {
    method: 'POST',
    headers: { Authorization: `Bearer ${pat}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ filename: file.name }),
  }).then(r => r.json());

  const form = new FormData();
  for (const [k, v] of Object.entries(presign.fields)) form.append(k, v);
  form.append('file', file);
  const upload = await fetch(presign.url, { method: 'POST', body: form });
  if (!upload.ok) throw new Error(`Upload failed: ${upload.status}`);

  return presign.fileUrl;
}

Using the URL in an article

<img src="https://<your-sub-id>-public.s3.eu-west-2.amazonaws.com/<key>" alt="Network diagram">

Paste the fileUrl directly into your article's HTML. No further server-side rewriting is needed. The URL stays valid indefinitely unless the object is deleted.

Safety reminder

The bucket behind this endpoint is configured for anonymous public read. Uploading a file here is equivalent to publishing it on the open internet. Review every upload before calling this endpoint, and prefer the private-upload routes for anything else.

See also

apiuploadfileimagepublickbknowledge basebucketpresigneds3

Was this article helpful?

← Back to Knowledge Base