Media API: upload and attach files
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-TypeagainstFILE_STORAGE_ALLOWED_MIMETYPES. The default istext/*,image/*,video/*,audio/*,application/pdf. A rejected file returns400withFile type is not allowed. - Empty files return
400withFile length is 0. - Size.
FILE_STORAGE_MAX_FILE_SIZEdefaults 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 asapplication/octet-streamis 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
- Content items: the other field types.
- Recipes: upload an image and create a post in one script.
- File storage: choose local, S3 or Azure and set the limits.