The Kelyx write API lets you publish content straight from a script or a local LLM using a personal API key. A key can only ever create content on your own account — it can't read or change anyone else's data.
Authentication
Generate a key in Settings → API Keys. It's shown once — store it somewhere safe. Send it on every request in the X-API-Key header, over HTTPS only. Keep it in an environment variable, never in code you share.
Media is uploaded first, then referenced by URL when you create the content — this keeps large files (video, audio) off the JSON request. Three steps:
POST /api/v1/me — get your creator id.
POST /api/v1/upload — upload media to {type}/{id}/….
POST /api/v1/content/:type — create the post referencing the uploaded URL.
# 1. Find your creator id (needed to build upload keys)
ID=$(curl -s -X POST "https://kelyx-upload.4neodesigns.workers.dev/api/v1/me" \
-H "X-API-Key: $KELYX_API_KEY" | python -c "import sys,json;print(json.load(sys.stdin)['id'])")
# 2. Upload the image to your own media folder
curl -X POST "https://kelyx-upload.4neodesigns.workers.dev/api/v1/upload?key=image/$ID/$(date +%s)-art.png&contentType=image/png" \
-H "X-API-Key: $KELYX_API_KEY" \
--data-binary @art.png
# -> { "publicUrl": "https://media.kelyxai.com/image/<id>/1718-art.png" }
# 3. Create the image post, referencing the uploaded URL
curl -X POST "https://kelyx-upload.4neodesigns.workers.dev/api/v1/content/image" \
-H "X-API-Key: $KELYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Neon dreams",
"image_urls": ["https://media.kelyxai.com/image/<id>/1718-art.png"],
"show_prompt": true,
"tags": ["scifi", "neon"]
}'
# -> { "id": "…", "type": "image" }
Python
import os, time, requests
BASE = "https://kelyx-upload.4neodesigns.workers.dev"
KEY = os.environ["KELYX_API_KEY"] # never hard-code your key
HEADERS = {"X-API-Key": KEY}
# 1. Your creator id
creator_id = requests.post(f"{BASE}/api/v1/me", headers=HEADERS).json()["id"]
# 2. Upload media to your own folder
key = f"image/{creator_id}/{int(time.time())}-art.png"
with open("art.png", "rb") as f:
up = requests.post(
f"{BASE}/api/v1/upload",
params={"key": key, "contentType": "image/png"},
headers=HEADERS,
data=f,
)
up.raise_for_status()
public_url = up.json()["publicUrl"]
# 3. Create the post
res = requests.post(
f"{BASE}/api/v1/content/image",
headers=HEADERS,
json={"title": "Neon dreams", "image_urls": [public_url], "tags": ["scifi"]},
)
res.raise_for_status()
print("Published:", res.json()["id"])
Content fields
Image
POST /api/v1/content/image
Field
Type
Required
Notes
title
string
Yes
Up to 200 chars.
image_urls
string[]
Yes
Your uploaded media URLs (1–20).
description
string
No
ai_tool
string
No
e.g. "Midjourney".
prompt
string
No
show_prompt
boolean
No
Default false.
tags
string[]
No
Up to 20.
Video
POST /api/v1/content/video
Field
Type
Required
Notes
title
string
Yes
video_url
string
Yes
Your uploaded video URL.
type
string
No
"movie" or "short". Default "movie".
synopsis
string
No
poster_url
string
No
Your uploaded image URL.
runtime_seconds
integer
No
genre
string[]
No
ai_tools
string[]
No
Music track
POST /api/v1/content/track
Field
Type
Required
Notes
title
string
Yes
audio_url
string
Yes
Uploaded under audio/<id>/…
cover_url
string
No
Your uploaded image URL.
genre
string[]
No
lyrics
string
No
duration_seconds
integer
No
Book
POST /api/v1/content/book
Field
Type
Required
Notes
title
string
Yes
A slug is generated for you.
synopsis
string
No
cover_url
string
No
Your uploaded image URL.
genre
string[]
No
status
string
No
"ongoing" | "complete" | "hiatus".
Comic
POST /api/v1/content/comic
Field
Type
Required
Notes
title
string
Yes
A slug is generated for you.
synopsis
string
No
cover_url
string
No
Your uploaded image URL.
banner_url
string
No
Your uploaded image URL.
genre
string[]
No
rating
string
No
"G" | "PG" | "PG-13" | "18+".
status
string
No
"ongoing" | "complete" | "hiatus".
format
string
No
"webtoon" | "traditional".
Rate limits & errors
Content writes: 60 requests per minute, per key.
Media uploads: 300 requests per minute, per account.
Errors return a JSON body { "error": "…" } with a matching HTTP status: 400 (validation), 401 (bad or missing key), 429 (rate limited).