Skip to main content

1. Get an API key

In the Melius app, go to Team settings → Integrations, create a key, and copy it — it’s shown once. See Authentication for details.

2. Make your first request

Paste your key into the snippet below and run it. It creates a project and canvas, adds an image node with your prompt, runs it, waits for the generation to finish, and prints the URL of the generated image.
KEY="mel_..."
BASE="https://api.melius.com/api/v1"
AUTH=(-H "Authorization: Bearer $KEY" -H "Content-Type: application/json")

PROJECT=$(curl -s "${AUTH[@]}" -d '{"title":"My first project"}' $BASE/projects | jq -r .id)
CANVAS=$(curl -s "${AUTH[@]}" -d '{"title":"My first canvas"}' $BASE/projects/$PROJECT/canvases | jq -r .id)

NODE=$(uuidgen | tr 'A-Z' 'a-z')
curl -s "${AUTH[@]}" -d "{\"id\":\"$NODE\",\"nodeType\":\"image\",\"geometry\":{\"x\":0,\"y\":0,\"w\":380,\"h\":632},\"textPrompt\":\"a golden retriever puppy in a field\"}" $BASE/canvases/$CANVAS/direct/nodes > /dev/null

RUN=$(curl -s "${AUTH[@]}" -d "{\"canvasId\":\"$CANVAS\"}" $BASE/nodes/$NODE/runs | jq -r .id)

while true; do
  STATUS=$(curl -s "${AUTH[@]}" $BASE/node-runs/$RUN | jq -r .status)
  [ "$STATUS" = "finished" ] && break
  [ "$STATUS" = "failed" ] && { echo "generation failed"; exit 1; }
  sleep 2
done

curl -s "${AUTH[@]}" -d '{}' $BASE/node-runs/$RUN/download | jq -r .url
import time, uuid, requests

KEY = "mel_..."
BASE = "https://api.melius.com/api/v1"
auth = {"Authorization": f"Bearer {KEY}"}

project = requests.post(f"{BASE}/projects", headers=auth, json={"title": "My first project"}).json()
canvas = requests.post(f"{BASE}/projects/{project['id']}/canvases", headers=auth, json={"title": "My first canvas"}).json()

node_id = str(uuid.uuid4())
requests.post(f"{BASE}/canvases/{canvas['id']}/direct/nodes", headers=auth, json={
    "id": node_id,
    "nodeType": "image",
    "geometry": {"x": 0, "y": 0, "w": 380, "h": 632},
    "textPrompt": "a golden retriever puppy in a field",
})

run = requests.post(f"{BASE}/nodes/{node_id}/runs", headers=auth, json={"canvasId": canvas["id"]}).json()

while True:
    status = requests.get(f"{BASE}/node-runs/{run['id']}", headers=auth).json()["status"]
    if status in ("finished", "failed"):
        break
    time.sleep(2)

result = requests.post(f"{BASE}/node-runs/{run['id']}/download", headers=auth, json={}).json()
print(result["url"])
const KEY = "mel_...";
const BASE = "https://api.melius.com/api/v1";
const headers = { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" };
const post = <T>(path: string, body: unknown): Promise<T> =>
  fetch(`${BASE}${path}`, { method: "POST", headers, body: JSON.stringify(body) }).then((r) => r.json());
const get = <T>(path: string): Promise<T> => fetch(`${BASE}${path}`, { headers }).then((r) => r.json());

async function main() {
  const project = await post<{ id: string }>("/projects", { title: "My first project" });
  const canvas = await post<{ id: string }>(`/projects/${project.id}/canvases`, { title: "My first canvas" });

  const nodeId = crypto.randomUUID();
  await post(`/canvases/${canvas.id}/direct/nodes`, {
    id: nodeId,
    nodeType: "image",
    geometry: { x: 0, y: 0, w: 380, h: 632 },
    textPrompt: "a golden retriever puppy in a field",
  });

  const run = await post<{ id: string }>(`/nodes/${nodeId}/runs`, { canvasId: canvas.id });

  let status: string;
  do {
    await new Promise((r) => setTimeout(r, 2000));
    ({ status } = await get<{ status: string }>(`/node-runs/${run.id}`));
  } while (status !== "finished" && status !== "failed");

  const result = await post<{ url: string }>(`/node-runs/${run.id}/download`, {});
  console.log(result.url);
}

main();
<?php
$KEY = "mel_...";
$BASE = "https://api.melius.com/api/v1";

function call($method, $path, $body = null) {
    global $KEY, $BASE;
    $ch = curl_init("$BASE$path");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => ["Authorization: Bearer $KEY", "Content-Type: application/json"],
        CURLOPT_POSTFIELDS => $body === null ? null : json_encode($body),
    ]);
    return json_decode(curl_exec($ch), true);
}

function uuidv4() {
    $d = random_bytes(16);
    $d[6] = chr(ord($d[6]) & 0x0f | 0x40);
    $d[8] = chr(ord($d[8]) & 0x3f | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($d), 4));
}

$project = call("POST", "/projects", ["title" => "My first project"]);
$canvas = call("POST", "/projects/{$project['id']}/canvases", ["title" => "My first canvas"]);

$nodeId = uuidv4();
call("POST", "/canvases/{$canvas['id']}/direct/nodes", [
    "id" => $nodeId,
    "nodeType" => "image",
    "geometry" => ["x" => 0, "y" => 0, "w" => 380, "h" => 632],
    "textPrompt" => "a golden retriever puppy in a field",
]);

$run = call("POST", "/nodes/$nodeId/runs", ["canvasId" => $canvas["id"]]);

do {
    sleep(2);
    $status = call("GET", "/node-runs/{$run['id']}")["status"];
} while ($status !== "finished" && $status !== "failed");

$result = call("POST", "/node-runs/{$run['id']}/download", new stdClass());
echo $result["url"] . "\n";
package main

import (
	"bytes"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

const key = "mel_..."
const base = "https://api.melius.com/api/v1"

func call(method, path string, body any) map[string]any {
	var buf io.Reader
	if body != nil {
		b, _ := json.Marshal(body)
		buf = bytes.NewReader(b)
	}
	req, _ := http.NewRequest(method, base+path, buf)
	req.Header.Set("Authorization", "Bearer "+key)
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	var out map[string]any
	json.Unmarshal(data, &out)
	return out
}

func uuidv4() string {
	b := make([]byte, 16)
	rand.Read(b)
	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}

func main() {
	project := call("POST", "/projects", map[string]any{"title": "My first project"})
	canvas := call("POST", fmt.Sprintf("/projects/%s/canvases", project["id"]), map[string]any{"title": "My first canvas"})

	nodeID := uuidv4()
	call("POST", fmt.Sprintf("/canvases/%s/direct/nodes", canvas["id"]), map[string]any{
		"id":         nodeID,
		"nodeType":   "image",
		"geometry":   map[string]any{"x": 0, "y": 0, "w": 380, "h": 632},
		"textPrompt": "a golden retriever puppy in a field",
	})

	run := call("POST", fmt.Sprintf("/nodes/%s/runs", nodeID), map[string]any{"canvasId": canvas["id"]})

	status := ""
	for status != "finished" && status != "failed" {
		time.Sleep(2 * time.Second)
		status, _ = call("GET", fmt.Sprintf("/node-runs/%s", run["id"]), nil)["status"].(string)
	}

	result := call("POST", fmt.Sprintf("/node-runs/%s/download", run["id"]), map[string]any{})
	fmt.Println(result["url"])
}
require "net/http"
require "json"
require "securerandom"

KEY = "mel_..."
BASE = "https://api.melius.com/api/v1"

def call(method, path, body = nil)
  uri = URI("#{BASE}#{path}")
  req = (method == "GET" ? Net::HTTP::Get : Net::HTTP::Post).new(uri)
  req["Authorization"] = "Bearer #{KEY}"
  req["Content-Type"] = "application/json"
  req.body = body.to_json if body
  res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
  JSON.parse(res.body)
end

project = call("POST", "/projects", { title: "My first project" })
canvas = call("POST", "/projects/#{project['id']}/canvases", { title: "My first canvas" })

node_id = SecureRandom.uuid
call("POST", "/canvases/#{canvas['id']}/direct/nodes", {
  id: node_id,
  nodeType: "image",
  geometry: { x: 0, y: 0, w: 380, h: 632 },
  textPrompt: "a golden retriever puppy in a field",
})

run = call("POST", "/nodes/#{node_id}/runs", { canvasId: canvas["id"] })

status = nil
until ["finished", "failed"].include?(status)
  sleep 2
  status = call("GET", "/node-runs/#{run['id']}")["status"]
end

result = call("POST", "/node-runs/#{run['id']}/download", {})
puts result["url"]
No model is specified, so Melius picks one for you. To choose a specific model, add a modelConfig to the node from List generative models. The x-team-id header is optional — requests default to the team your key belongs to.

Next steps

Authentication

Keys, the x-team-id header, and how keys are scoped.

API overview

The base URL, how generations work, rate limits, and credits.
Last modified on June 30, 2026