Start here
Start a canvas
/v1/studio/projects/{project}/canvaseson https://api.auray.ai/v1Start a canvas in a project: a name, and a canvas document (one empty page when none is sent).
Start a canvas in a project: a name, and a canvas document (one empty page when none is sent). The document is read strictly — anything that does not read is refused canvas_invalid with each problem, past 2 MB of JSON canvas_too_large — and stored with no run on any node: a node's run is only ever what the jobs it started say. A project holds as many canvases as its limits.canvases says: its owner's plan's figure, whoever starts them. Needs the edit_canvas capability in the project. Takes a key holding studio:write — Account offers it unticked — or the desktop's own session key. Spends nothing.
#Authentication
Authorization: Bearer auray_sk_… — studio:write — Account offers it unticked; the desktop's session key holds it.
#Request
#Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project | string | yes |
#Body
application/json, required.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | 1 to 80 characters. |
doc | object | no | — | A canvas document, version 1: pages (up to 20), each with its viewport, its nodes (up to 1,500 — prompts, generators, uploads and the project's assets, upscalers, editors, notes, stickies, shapes, text, drawn strokes, tables, comment pins, reactions and frames) and the edges wiring an output port into an input port that takes its type. The full shape is published with the Studio's contract; a document that does not read is refused canvas_invalid, naming each problem. A node's run is the server's: anything sent there is ignored. |
doc.v | integer | yes | — | One of 1. |
doc.pages | object[] | yes | — | |
doc.pages[].id | string | yes | — | |
doc.pages[].name | string | yes | — | 0 to 80 characters. |
doc.pages[].viewport | object | yes | — | |
doc.pages[].viewport.x | number | no | — | |
doc.pages[].viewport.y | number | no | — | |
doc.pages[].viewport.zoom | number | no | — | 0.05 to 4. |
doc.pages[].nodes | object[] | yes | — | |
doc.pages[].nodes[].id | string | yes | — | |
doc.pages[].nodes[].kind | string | yes | — | One of "prompt", "number", "upload", "assets", "image_gen", "video_gen", "audio_gen", "llm", "upscale", "edit", "note", "sticky", "shape", "text", "stroke", "table", "comment_pin", "reaction", "frame". |
doc.pages[].nodes[].x | number | yes | — | |
doc.pages[].nodes[].y | number | yes | — | |
doc.pages[].nodes[].w | number | yes | — | |
doc.pages[].nodes[].h | number | yes | — | |
doc.pages[].nodes[].parent | string | no | — | May be null. |
doc.pages[].nodes[].z | integer | no | — | |
doc.pages[].nodes[].locked | boolean | no | — | |
doc.pages[].nodes[].data | object | yes | — | |
doc.pages[].nodes[].run | object | no | — | A node's last run, read off the jobs it started — never written by a request. Null before it ran. May be null. |
doc.pages[].edges | object[] | yes | — | |
doc.pages[].edges[].id | string | yes | — | |
doc.pages[].edges[].from | object | yes | — | |
doc.pages[].edges[].to | object | yes | — |
#Example request
curl -X POST https://api.auray.ai/v1/studio/projects/3f0c8a52-6a1d-4e8b-9c2d-7e5f4a3b2c1d/canvases \
-H "Authorization: Bearer $AURAY_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "Storm sequence",
"doc": {
"v": 1,
"pages": [
{
"id": "page-1",
"name": "Page 1",
"viewport": {
"x": 0,
"y": 0,
"zoom": 1
},
"nodes": [
{
"id": "prompt-1",
"kind": "prompt",
"x": 0,
"y": 0,
"w": 280,
"h": 160,
"parent": null,
"z": 0,
"locked": false,
"data": {
"text": "A lighthouse in a storm, seen from the sea."
},
"run": null
},
{
"id": "image-1",
"kind": "image_gen",
"x": 400,
"y": 0,
"w": 300,
"h": 340,
"parent": null,
"z": 0,
"locked": false,
"data": {
"model": null,
"prompt": "",
"params": {}
},
"run": null
}
],
"edges": [
{
"id": "edge-1",
"from": {
"node": "prompt-1",
"port": "text"
},
"to": {
"node": "image-1",
"port": "prompt"
}
}
]
}
]
}
}'const res = await fetch("https://api.auray.ai/v1/studio/projects/3f0c8a52-6a1d-4e8b-9c2d-7e5f4a3b2c1d/canvases", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AURAY_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
"name": "Storm sequence",
"doc": {
"v": 1,
"pages": [
{
"id": "page-1",
"name": "Page 1",
"viewport": {
"x": 0,
"y": 0,
"zoom": 1
},
"nodes": [
{
"id": "prompt-1",
"kind": "prompt",
"x": 0,
"y": 0,
"w": 280,
"h": 160,
"parent": null,
"z": 0,
"locked": false,
"data": {
"text": "A lighthouse in a storm, seen from the sea."
},
"run": null
},
{
"id": "image-1",
"kind": "image_gen",
"x": 400,
"y": 0,
"w": 300,
"h": 340,
"parent": null,
"z": 0,
"locked": false,
"data": {
"model": null,
"prompt": "",
"params": {}
},
"run": null
}
],
"edges": [
{
"id": "edge-1",
"from": {
"node": "prompt-1",
"port": "text"
},
"to": {
"node": "image-1",
"port": "prompt"
}
}
]
}
]
}
}),
});
console.log(await res.json());import os, requests
r = requests.post(
"https://api.auray.ai/v1/studio/projects/3f0c8a52-6a1d-4e8b-9c2d-7e5f4a3b2c1d/canvases",
headers={"Authorization": f"Bearer {os.environ['AURAY_API_KEY']}"},
json={
"name": "Storm sequence",
"doc": {
"v": 1,
"pages": [
{
"id": "page-1",
"name": "Page 1",
"viewport": {
"x": 0,
"y": 0,
"zoom": 1
},
"nodes": [
{
"id": "prompt-1",
"kind": "prompt",
"x": 0,
"y": 0,
"w": 280,
"h": 160,
"parent": None,
"z": 0,
"locked": False,
"data": {
"text": "A lighthouse in a storm, seen from the sea."
},
"run": None
},
{
"id": "image-1",
"kind": "image_gen",
"x": 400,
"y": 0,
"w": 300,
"h": 340,
"parent": None,
"z": 0,
"locked": False,
"data": {
"model": None,
"prompt": "",
"params": {}
},
"run": None
}
],
"edges": [
{
"id": "edge-1",
"from": {
"node": "prompt-1",
"port": "text"
},
"to": {
"node": "image-1",
"port": "prompt"
}
}
]
}
]
}
},
)
r.raise_for_status()
print(r.json())package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := []byte(`{
"name": "Storm sequence",
"doc": {
"v": 1,
"pages": [
{
"id": "page-1",
"name": "Page 1",
"viewport": {
"x": 0,
"y": 0,
"zoom": 1
},
"nodes": [
{
"id": "prompt-1",
"kind": "prompt",
"x": 0,
"y": 0,
"w": 280,
"h": 160,
"parent": null,
"z": 0,
"locked": false,
"data": {
"text": "A lighthouse in a storm, seen from the sea."
},
"run": null
},
{
"id": "image-1",
"kind": "image_gen",
"x": 400,
"y": 0,
"w": 300,
"h": 340,
"parent": null,
"z": 0,
"locked": false,
"data": {
"model": null,
"prompt": "",
"params": {}
},
"run": null
}
],
"edges": [
{
"id": "edge-1",
"from": {
"node": "prompt-1",
"port": "text"
},
"to": {
"node": "image-1",
"port": "prompt"
}
}
]
}
]
}
}`)
req, err := http.NewRequest("POST", "https://api.auray.ai/v1/studio/projects/3f0c8a52-6a1d-4e8b-9c2d-7e5f4a3b2c1d/canvases", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("AURAY_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}<?php
$ch = curl_init("https://api.auray.ai/v1/studio/projects/3f0c8a52-6a1d-4e8b-9c2d-7e5f4a3b2c1d/canvases");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("AURAY_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"name" => "Storm sequence",
"doc" => [
"v" => 1,
"pages" => [
[
"id" => "page-1",
"name" => "Page 1",
"viewport" => [
"x" => 0,
"y" => 0,
"zoom" => 1,
],
"nodes" => [
[
"id" => "prompt-1",
"kind" => "prompt",
"x" => 0,
"y" => 0,
"w" => 280,
"h" => 160,
"parent" => null,
"z" => 0,
"locked" => false,
"data" => [
"text" => "A lighthouse in a storm, seen from the sea.",
],
"run" => null,
],
[
"id" => "image-1",
"kind" => "image_gen",
"x" => 400,
"y" => 0,
"w" => 300,
"h" => 340,
"parent" => null,
"z" => 0,
"locked" => false,
"data" => [
"model" => null,
"prompt" => "",
"params" => new \stdClass(),
],
"run" => null,
],
],
"edges" => [
[
"id" => "edge-1",
"from" => [
"node" => "prompt-1",
"port" => "text",
],
"to" => [
"node" => "image-1",
"port" => "prompt",
],
],
],
],
],
],
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $response, PHP_EOL;require "net/http"
require "json"
uri = URI("https://api.auray.ai/v1/studio/projects/3f0c8a52-6a1d-4e8b-9c2d-7e5f4a3b2c1d/canvases")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("AURAY_API_KEY")}"
req["Content-Type"] = "application/json"
req.body = {
"name" => "Storm sequence",
"doc" => {
"v" => 1,
"pages" => [
{
"id" => "page-1",
"name" => "Page 1",
"viewport" => {
"x" => 0,
"y" => 0,
"zoom" => 1
},
"nodes" => [
{
"id" => "prompt-1",
"kind" => "prompt",
"x" => 0,
"y" => 0,
"w" => 280,
"h" => 160,
"parent" => nil,
"z" => 0,
"locked" => false,
"data" => {
"text" => "A lighthouse in a storm, seen from the sea."
},
"run" => nil
},
{
"id" => "image-1",
"kind" => "image_gen",
"x" => 400,
"y" => 0,
"w" => 300,
"h" => 340,
"parent" => nil,
"z" => 0,
"locked" => false,
"data" => {
"model" => nil,
"prompt" => "",
"params" => {}
},
"run" => nil
}
],
"edges" => [
{
"id" => "edge-1",
"from" => {
"node" => "prompt-1",
"port" => "text"
},
"to" => {
"node" => "image-1",
"port" => "prompt"
}
}
]
}
]
}
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.code, res.bodyimport java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"name": "Storm sequence",
"doc": {
"v": 1,
"pages": [
{
"id": "page-1",
"name": "Page 1",
"viewport": {
"x": 0,
"y": 0,
"zoom": 1
},
"nodes": [
{
"id": "prompt-1",
"kind": "prompt",
"x": 0,
"y": 0,
"w": 280,
"h": 160,
"parent": null,
"z": 0,
"locked": false,
"data": {
"text": "A lighthouse in a storm, seen from the sea."
},
"run": null
},
{
"id": "image-1",
"kind": "image_gen",
"x": 400,
"y": 0,
"w": 300,
"h": 340,
"parent": null,
"z": 0,
"locked": false,
"data": {
"model": null,
"prompt": "",
"params": {}
},
"run": null
}
],
"edges": [
{
"id": "edge-1",
"from": {
"node": "prompt-1",
"port": "text"
},
"to": {
"node": "image-1",
"port": "prompt"
}
}
]
}
]
}
}""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.auray.ai/v1/studio/projects/3f0c8a52-6a1d-4e8b-9c2d-7e5f4a3b2c1d/canvases"))
.header("Authorization", "Bearer " + System.getenv("AURAY_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AURAY_API_KEY"));
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.auray.ai/v1/studio/projects/3f0c8a52-6a1d-4e8b-9c2d-7e5f4a3b2c1d/canvases");
var body = """
{
"name": "Storm sequence",
"doc": {
"v": 1,
"pages": [
{
"id": "page-1",
"name": "Page 1",
"viewport": {
"x": 0,
"y": 0,
"zoom": 1
},
"nodes": [
{
"id": "prompt-1",
"kind": "prompt",
"x": 0,
"y": 0,
"w": 280,
"h": 160,
"parent": null,
"z": 0,
"locked": false,
"data": {
"text": "A lighthouse in a storm, seen from the sea."
},
"run": null
},
{
"id": "image-1",
"kind": "image_gen",
"x": 400,
"y": 0,
"w": 300,
"h": 340,
"parent": null,
"z": 0,
"locked": false,
"data": {
"model": null,
"prompt": "",
"params": {}
},
"run": null
}
],
"edges": [
{
"id": "edge-1",
"from": {
"node": "prompt-1",
"port": "text"
},
"to": {
"node": "image-1",
"port": "prompt"
}
}
]
}
]
}
}
""";
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");#Responses
#201 Created
Created.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
object | string | yes | — | One of "canvas". |
id | string | yes | — | |
project_id | string | yes | — | |
name | string | yes | — | 0 to 80 characters. |
rev | integer | yes | — | A save names the rev it was drawn from. 1 to …. |
pages | integer | yes | — | 1 to …. |
nodes | integer | yes | — | 0 to …. |
updated_at | string | yes | — | |
updated_by | null or object | yes | — | Who saved it last, while they are a member; null once they left. |
doc | object | yes | — | A canvas document, version 1: pages (up to 20), each with its viewport, its nodes (up to 1,500 — prompts, generators, uploads and the project's assets, upscalers, editors, notes, stickies, shapes, text, drawn strokes, tables, comment pins, reactions and frames) and the edges wiring an output port into an input port that takes its type. The full shape is published with the Studio's contract; a document that does not read is refused canvas_invalid, naming each problem. A node's run is the server's: anything sent there is ignored. |
doc.v | integer | yes | — | One of 1. |
doc.pages | object[] | yes | — | |
doc.pages[].id | string | yes | — | |
doc.pages[].name | string | yes | — | 0 to 80 characters. |
doc.pages[].viewport | object | yes | — | |
doc.pages[].viewport.x | number | no | — | |
doc.pages[].viewport.y | number | no | — | |
doc.pages[].viewport.zoom | number | no | — | 0.05 to 4. |
doc.pages[].nodes | object[] | yes | — | |
doc.pages[].nodes[].id | string | yes | — | |
doc.pages[].nodes[].kind | string | yes | — | One of "prompt", "number", "upload", "assets", "image_gen", "video_gen", "audio_gen", "llm", "upscale", "edit", "note", "sticky", "shape", "text", "stroke", "table", "comment_pin", "reaction", "frame". |
doc.pages[].nodes[].x | number | yes | — | |
doc.pages[].nodes[].y | number | yes | — | |
doc.pages[].nodes[].w | number | yes | — | |
doc.pages[].nodes[].h | number | yes | — | |
doc.pages[].nodes[].parent | string | no | — | May be null. |
doc.pages[].nodes[].z | integer | no | — | |
doc.pages[].nodes[].locked | boolean | no | — | |
doc.pages[].nodes[].data | object | yes | — | |
doc.pages[].nodes[].run | object | no | — | A node's last run, read off the jobs it started — never written by a request. Null before it ran. May be null. |
doc.pages[].edges | object[] | yes | — | |
doc.pages[].edges[].id | string | yes | — | |
doc.pages[].edges[].from | object | yes | — | |
doc.pages[].edges[].to | object | yes | — | |
bytes | integer | yes | — | The document's size as JSON, which is kept up to 2 MB. 0 to …. |
crdt | string | yes | — | The name the canvas's live edits travel under: canvas: and its id. |
request_id | string | no | — | Mirrored in the X-Request-Id header. |
Headers:
| Header | Description |
|---|---|
RateLimit-Limit | Burst capacity. |
RateLimit-Remaining | |
RateLimit-Reset | Seconds until refill. |
X-RateLimit-Limit | |
X-RateLimit-Remaining | |
X-RateLimit-Reset | Unix seconds. The X- spelling is what client libraries read. |
X-Request-Id |
#400 Bad Request
#401 Unauthorized
#403 Forbidden
#404 Not Found
#409 Conflict
#413 Payload Too Large
#429 Too Many Requests
#500 Internal Server Error
#503 Service Unavailable
#Status codes
| Status | Meaning |
|---|---|
201 | Created. |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
404 | Not Found |
409 | Conflict |
413 | Payload Too Large |
429 | Too Many Requests |
500 | Internal Server Error |
503 | Service Unavailable |
Every refusal names its code, and every code is explained at Errors.