Platform
Learn
Developer docs User guide Quickstart Blog
Company
Services About Contact Links Get started

Media API: upload and attach files

Updated

Upload a file with one multipart request. Raytha stores it in your configured file storage and returns an object key. Put that key in an attachment field to attach the file to a content item. This page shows the upload in curl, JavaScript and C#, plus listing, resolving and deleting media.

Upload a file

POST /raytha/api/v1/MediaItems/upload-direct takes a multipart/form-data body with one part named file.

curl -s -X POST "$RAYTHA_URL/raytha/api/v1/MediaItems/upload-direct" \
  -H "X-API-KEY: $RAYTHA_API_KEY" \
  -F "[email protected];type=image/jpeg"

Raytha answers 201:

{
  "success": true,
  "result": "lNGmEiOqJEWlclH34pAFxg_hero.jpg"
}

The result is the object key: the new media item's id, an underscore and a cleaned version of the file name. It is the only handle you need. The response also carries a Location header for the lookup endpoint below.

From JavaScript

Node 18 and later, and every browser, provide fetch, FormData and Blob. Do not set the Content-Type header yourself, because fetch adds the multipart boundary.

const fs = require("node:fs");
const path = require("node:path");

async function uploadFile(filePath, mimeType) {
  const data = await fs.promises.readFile(filePath);
  const form = new FormData();
  form.append("file", new Blob([data], { type: mimeType }), path.basename(filePath));
  const response = await fetch(`${process.env.RAYTHA_URL}/raytha/api/v1/MediaItems/upload-direct`, {
    method: "POST",
    headers: { "X-API-KEY": process.env.RAYTHA_API_KEY },
    body: form,
  });
  const body = await response.json();
  if (!response.ok) throw new Error(`Upload failed: ${response.status} ${body.error}`);
  return body.result; // the object key
}

From C#

using System.Net.Http.Headers;
using System.Text.Json;

var baseUrl = Environment.GetEnvironmentVariable("RAYTHA_URL")!.TrimEnd('/');
using var http = new HttpClient { BaseAddress = new Uri(baseUrl + "/raytha/api/v1/") };
http.DefaultRequestHeaders.Add("X-API-KEY", Environment.GetEnvironmentVariable("RAYTHA_API_KEY"));

var filePath = args[0];
await using var stream = File.OpenRead(filePath);
using var form = new MultipartFormDataContent();
var file = new StreamContent(stream);
file.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
form.Add(file, "file", Path.GetFileName(filePath));

using var response = await http.PostAsync("MediaItems/upload-direct", form);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    throw new InvalidOperationException($"Upload failed with {(int)response.StatusCode}: {body}");
}

var objectKey = JsonDocument.Parse(body).RootElement.GetProperty("result").GetString();
Console.WriteLine(objectKey);

Attach the file to an item

Create or edit the item with the object key as the value of an attachment field. Here hero_image is an attachment field on posts.

curl -s -X POST "$RAYTHA_URL/raytha/api/v1/ContentItems/posts" \
  -H "X-API-KEY: $RAYTHA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "REPLACE_WITH_TEMPLATE_ID",
    "saveAsDraft": false,
    "content": {
      "title": "A post with an image",
      "hero_image": "lNGmEiOqJEWlclH34pAFxg_hero.jpg"
    }
  }'

In a template, render the file with the attachment_public_url filter. See Functions and filters. For a rich text field, upload the file and place its public path in the HTML yourself, as described next.

Public URLs

A file is served from a stable, public, root-relative path:

/raytha/media-items/objectkey/lNGmEiOqJEWlclH34pAFxg_hero.jpg

The path redirects to the file. With local storage it redirects to /_static-files/<key>. With Azure or S3 it redirects to a signed URL that expires. Responses from the content API rewrite these paths into absolute URLs using your site's Website URL setting, so <img src> values in rich text arrive ready to use.

You can also ask for the redirect target with the API:

curl -s "$RAYTHA_URL/raytha/api/v1/MediaItems/lNGmEiOqJEWlclH34pAFxg_hero.jpg" \
  -H "X-API-KEY: $RAYTHA_API_KEY"

The response is {"success": true, "result": "<download url>"}. A cloud URL in result expires after about a day. Do not store it. Store the object key.

List and delete

Both calls need the media_items permission.

curl -s -G "$RAYTHA_URL/raytha/api/v1/MediaItems" \
  -H "X-API-KEY: $RAYTHA_API_KEY" \
  --data-urlencode "search=hero" \
  --data-urlencode "pageSize=20" \
  | jq '.result.items[] | {fileName, contentType, length, objectKey}'

curl -s -X DELETE "$RAYTHA_URL/raytha/api/v1/MediaItems/lNGmEiOqJEWlclH34pAFxg_hero.jpg" \
  -H "X-API-KEY: $RAYTHA_API_KEY"

The list accepts search, orderBy (default CreationTime desc), pageNumber, pageSize and contentType. Each item has id, fileName, contentType, length, fileStorageProvider, objectKey and creationTime.

Limits

  • Allowed types. Raytha checks the part's Content-Type against FILE_STORAGE_ALLOWED_MIMETYPES. The default is text/*,image/*,video/*,audio/*,application/pdf. A rejected file returns 400 with File type is not allowed.
  • Empty files return 400 with File length is 0.
  • Size. FILE_STORAGE_MAX_FILE_SIZE defaults to 20,000,000 bytes. With local storage, an upload over the limit fails. Keep files under it with every provider. See File storage.

Gotchas

  • Send the right MIME type. The server trusts the part's Content-Type. A missing or generic type such as application/octet-stream is rejected unless you allow it.
  • The media endpoint is public. Anyone who knows an object key can fetch the file. Do not upload private files.
  • The key, not the URL, goes in the field. An attachment field holds the object key. A full URL does not resolve.
  • Deleting a media item does not clean up content. Items that still hold its key show a broken file.
  • Uploads need a permission. A key can upload if its admin can author content or templates, but listing and deleting need media_items.

Next steps