Working with the API
Uploads
Getting an image, a track or a mesh into a request, in three calls, with the bytes never passing through the API.
Many addresses take a file: an image to turn into a mesh, a mesh to split, a reference track, a clip to extend. This API sends JSON, so a file travels as a URL — one you already have, or one you make here — or, for our own 3D and video addresses, as the path of an upload.
A field's contract says which it accepts. In the address's OpenAPI document an asset field carries
format: "uri" when it takes a URL, contentEncoding: "base64" when it takes the bytes inline,
x-auray-encoding: "upload-path" when it takes the path an upload answered and nothing else, and
x-auray-encoding: "multipart" when the model only takes a file part — which this API does not
send, and refuses with unsupported_encoding rather than pretend.
#Three calls
Ask for somewhere to put it, naming the address the file is for. Everything else follows from
the address: which bucket holds it, which kinds of file its fields take, how large one may be, and
which key may put it there — the same scopes that may submit to that address. idempotency_key is
required, so a retried request lands on the same object.
curl -X POST https://api.auray.ai/v1/uploads \
-H "Authorization: Bearer $AURAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "address": "hitem3d/hi3d/split", "content_type": "model/gltf-binary", "idempotency_key": "chair-scan-3" }'import { readFile } from "node:fs/promises";
import { createClient } from "@auray/client";
const auray = createClient(); // reads AURAY_API_KEY
const upload = await auray.storage.upload({
address: "hitem3d/hi3d/split",
contentType: "model/gltf-binary",
data: await readFile("model.glb"),
idempotencyKey: "chair-scan-3",
});
// send_as names the field that takes the file, and whether it wants the path or the signed url
for (const { field, value } of upload.send_as ?? []) {
console.log(field, value === "read_url" ? upload.url : upload.path);
}from auray_client import Client
auray = Client() # reads AURAY_API_KEY
upload = auray.storage.upload_file("model.glb", address="hitem3d/hi3d/split", idempotency_key="chair-scan-3")
# send_as names the field that takes the file, and whether it wants the path or the signed url
for target in upload["send_as"] or []:
print(target["field"], upload["url"] if target["value"] == "read_url" else upload["path"]){
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "upload_file",
"arguments": {
"address": "hitem3d/hi3d/split",
"content_type": "model/gltf-binary",
"bytes_base64": "<base64>",
"idempotency_key": "chair-scan-3"
}
}
}const res = await fetch("https://api.auray.ai/v1/uploads", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AURAY_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
"address": "hitem3d/hi3d/split",
"content_type": "model/gltf-binary",
"idempotency_key": "chair-scan-3"
}),
});
console.log(await res.json());import os, requests
r = requests.post(
"https://api.auray.ai/v1/uploads",
headers={"Authorization": f"Bearer {os.environ['AURAY_API_KEY']}"},
json={
"address": "hitem3d/hi3d/split",
"content_type": "model/gltf-binary",
"idempotency_key": "chair-scan-3"
},
)
r.raise_for_status()
print(r.json())package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := []byte(`{
"address": "hitem3d/hi3d/split",
"content_type": "model/gltf-binary",
"idempotency_key": "chair-scan-3"
}`)
req, err := http.NewRequest("POST", "https://api.auray.ai/v1/uploads", 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/uploads");
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([
"address" => "hitem3d/hi3d/split",
"content_type" => "model/gltf-binary",
"idempotency_key" => "chair-scan-3",
], 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/uploads")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("AURAY_API_KEY")}"
req["Content-Type"] = "application/json"
req.body = {
"address" => "hitem3d/hi3d/split",
"content_type" => "model/gltf-binary",
"idempotency_key" => "chair-scan-3"
}.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 = """
{
"address": "hitem3d/hi3d/split",
"content_type": "model/gltf-binary",
"idempotency_key": "chair-scan-3"
}""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.auray.ai/v1/uploads"))
.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/uploads");
var body = """
{
"address": "hitem3d/hi3d/split",
"content_type": "model/gltf-binary",
"idempotency_key": "chair-scan-3"
}
""";
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()}");{
"object": "upload",
"upload_url": "https://…/upload/…",
"path": "86e97633-…/chair-scan-3.glb",
"bucket": "external-inputs",
"address": "hitem3d/hi3d/split",
"max_bytes": 209715200,
"expires_at": "2026-09-06T06:21:58.000Z",
"read": "https://api.auray.ai/v1/uploads/86e97633-…/chair-scan-3.glb?address=hitem3d%2Fhi3d%2Fsplit",
"send_as": [{ "field": "mesh_url", "kind": "mesh", "list": false, "value": "read_url" }],
"request_id": "…"
}send_as is every field of that address this file can go in. value says what to send there:
read_url is the url the third call answers, path is the path above. A file of a kind no field
of the address takes is refused with unknown_content_type, listing the types it does take; an
address none of whose fields takes a file is refused with upload_not_accepted.
Put the bytes there. Straight to storage, with the content type you declared — the API never
sees them, so there is no body-size limit on this side but the bucket's max_bytes.
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: model/gltf-binary" \
--data-binary @chair-scan.glbAsk for the read URL, once the bytes are there — at the read address the first response
handed back. It names the address, which picks the bucket to sign from, and any scope that may upload
for that address — the kind it makes, or the older product name — reads back through it the account's
files of the kinds that address takes, which are often more than the kind it makes: a language model
takes images, video, audio and documents. assets:read alone does not: it fetches what a job made,
and an upload is something you sent. It is signed for 24 hours, which is
longer than any job takes. The upload itself is deleted 3 days after it landed;
upload again for a job you run after that.
curl "https://api.auray.ai/v1/uploads/86e97633-…/chair-scan-3.glb?address=hitem3d%2Fhi3d%2Fsplit" \
-H "Authorization: Bearer $AURAY_API_KEY"const res = await fetch("https://api.auray.ai/v1/uploads/86e97633-…/chair-scan-3.glb?address=hitem3d%2Fhi3d%2Fsplit", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.AURAY_API_KEY}`,
},
});
console.log(res.status, await res.json());import os, requests
r = requests.get(
"https://api.auray.ai/v1/uploads/86e97633-…/chair-scan-3.glb?address=hitem3d%2Fhi3d%2Fsplit",
headers={"Authorization": f"Bearer {os.environ['AURAY_API_KEY']}"},
)
r.raise_for_status()
print(r.json())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.auray.ai/v1/uploads/86e97633-…/chair-scan-3.glb?address=hitem3d%2Fhi3d%2Fsplit", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("AURAY_API_KEY"))
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/uploads/86e97633-…/chair-scan-3.glb?address=hitem3d%2Fhi3d%2Fsplit");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("AURAY_API_KEY"),
],
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $response, PHP_EOL;require "net/http"
uri = URI("https://api.auray.ai/v1/uploads/86e97633-…/chair-scan-3.glb?address=hitem3d%2Fhi3d%2Fsplit")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("AURAY_API_KEY")}"
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 {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.auray.ai/v1/uploads/86e97633-…/chair-scan-3.glb?address=hitem3d%2Fhi3d%2Fsplit"))
.header("Authorization", "Bearer " + System.getenv("AURAY_API_KEY"))
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}using System.Net.Http.Headers;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AURAY_API_KEY"));
using var request = new HttpRequestMessage(HttpMethod.Get, "https://api.auray.ai/v1/uploads/86e97633-…/chair-scan-3.glb?address=hitem3d%2Fhi3d%2Fsplit");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");{
"object": "upload",
"path": "86e97633-…/chair-scan-3.glb",
"bucket": "external-inputs",
"url": "https://…/sign/…",
"expires_at": "2026-09-07T04:22:11.000Z",
"request_id": "…"
}Asking before the bytes have landed answers 404 not_found; storage will not sign an object that
does not exist yet, and a read through an address signs no file of a kind that address does not take.
A read that names neither address nor product looks only in the buckets whose own scope your key
holds — the catalogue models' with external:write, then 3D's with 3d:write or threed:write. A
key without external:write, which is every key Account mints, is refused 403 insufficient_scope
there and told to name the address, unless the file is in 3D's bucket and the key holds 3d:write
or threed:write; so keep the query the read link carries.
Then put url in the field send_as named:
curl -X POST https://queue.auray.run/hitem3d/hi3d/split \
-H "Authorization: Bearer $AURAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "mesh_url": "https://…/sign/…" }'import { createClient } from "@auray/client";
const auray = createClient(); // reads AURAY_API_KEY
const job = await auray.queue.submit("hitem3d/hi3d/split", {
input: {
"mesh_url": "https://…/sign/…"
},
});
console.log(job.request_id, job.status);from auray_client import Client
auray = Client() # reads AURAY_API_KEY
job = auray.queue.submit(
"hitem3d/hi3d/split",
input={
"mesh_url": "https://…/sign/…"
},
)
print(job["request_id"], job["status"]){
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "submit_job",
"arguments": {
"address": "hitem3d/hi3d/split",
"input": {
"mesh_url": "https://…/sign/…"
}
}
}
}const res = await fetch("https://queue.auray.run/hitem3d/hi3d/split", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AURAY_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
"mesh_url": "https://…/sign/…"
}),
});
console.log(await res.json());import os, requests
r = requests.post(
"https://queue.auray.run/hitem3d/hi3d/split",
headers={"Authorization": f"Bearer {os.environ['AURAY_API_KEY']}"},
json={
"mesh_url": "https://…/sign/…"
},
)
r.raise_for_status()
print(r.json())package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := []byte(`{
"mesh_url": "https://…/sign/…"
}`)
req, err := http.NewRequest("POST", "https://queue.auray.run/hitem3d/hi3d/split", 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://queue.auray.run/hitem3d/hi3d/split");
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([
"mesh_url" => "https://…/sign/…",
], 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://queue.auray.run/hitem3d/hi3d/split")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("AURAY_API_KEY")}"
req["Content-Type"] = "application/json"
req.body = {
"mesh_url" => "https://…/sign/…"
}.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 = """
{
"mesh_url": "https://…/sign/…"
}""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://queue.auray.run/hitem3d/hi3d/split"))
.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://queue.auray.run/hitem3d/hi3d/split");
var body = """
{
"mesh_url": "https://…/sign/…"
}
""";
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()}");#A path, not a URL: our 3D and video addresses
auray-ai/hunyuan-3d/image-to-3d takes its photograph as image_path, and that field takes the
path the first call answered — as it is, with no read step; its send_as says "value": "path".
It never takes a URL: we do not fetch one you give us, and a path in another account's folder is
refused with image_required.
auray-ai/minimax-h3/text-to-video takes pictures the same way: first_frame_path, the picture a
clip opens on, or reference_image_paths, a list of up to nine that hold a subject — never both in
one request. Its uploads are stored as api-<idempotency_key>, and its read link answers only those.
A path in another account's folder, or one with nothing there, is refused with
reference_must_be_uploaded before any charge.
curl -X POST https://queue.auray.run/auray-ai/hunyuan-3d/image-to-3d \
-H "Authorization: Bearer $AURAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "image_path": "86e97633-…/chair-photo-1.png", "tier": "textured" }'import { createClient } from "@auray/client";
const auray = createClient(); // reads AURAY_API_KEY
const job = await auray.queue.submit("auray-ai/hunyuan-3d/image-to-3d", {
input: {
"image_path": "86e97633-…/chair-photo-1.png",
"tier": "textured"
},
});
console.log(job.request_id, job.status);from auray_client import Client
auray = Client() # reads AURAY_API_KEY
job = auray.queue.submit(
"auray-ai/hunyuan-3d/image-to-3d",
input={
"image_path": "86e97633-…/chair-photo-1.png",
"tier": "textured"
},
)
print(job["request_id"], job["status"]){
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "submit_job",
"arguments": {
"address": "auray-ai/hunyuan-3d/image-to-3d",
"input": {
"image_path": "86e97633-…/chair-photo-1.png",
"tier": "textured"
}
}
}
}const res = await fetch("https://queue.auray.run/auray-ai/hunyuan-3d/image-to-3d", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AURAY_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
"image_path": "86e97633-…/chair-photo-1.png",
"tier": "textured"
}),
});
console.log(await res.json());import os, requests
r = requests.post(
"https://queue.auray.run/auray-ai/hunyuan-3d/image-to-3d",
headers={"Authorization": f"Bearer {os.environ['AURAY_API_KEY']}"},
json={
"image_path": "86e97633-…/chair-photo-1.png",
"tier": "textured"
},
)
r.raise_for_status()
print(r.json())package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := []byte(`{
"image_path": "86e97633-…/chair-photo-1.png",
"tier": "textured"
}`)
req, err := http.NewRequest("POST", "https://queue.auray.run/auray-ai/hunyuan-3d/image-to-3d", 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://queue.auray.run/auray-ai/hunyuan-3d/image-to-3d");
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([
"image_path" => "86e97633-…/chair-photo-1.png",
"tier" => "textured",
], 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://queue.auray.run/auray-ai/hunyuan-3d/image-to-3d")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("AURAY_API_KEY")}"
req["Content-Type"] = "application/json"
req.body = {
"image_path" => "86e97633-…/chair-photo-1.png",
"tier" => "textured"
}.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 = """
{
"image_path": "86e97633-…/chair-photo-1.png",
"tier": "textured"
}""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://queue.auray.run/auray-ai/hunyuan-3d/image-to-3d"))
.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://queue.auray.run/auray-ai/hunyuan-3d/image-to-3d");
var body = """
{
"image_path": "86e97633-…/chair-photo-1.png",
"tier": "textured"
}
""";
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()}");#Buckets and limits
The address picks the row; you never name a bucket.
product | For | Takes | Up to |
|---|---|---|---|
threed | Our 3D address's photograph, sent as image_path | image/jpeg, image/png, image/webp | 20 MB |
external | Any input to an address outside auray-ai/ — only the kinds its fields take | images, video, audio, meshes, archives | 200 MB |
| — | Our video address's pictures, sent as first_frame_path or reference_image_paths; reached through its address only | image/jpeg, image/png, image/webp | 25 MB |
The last column is the bucket's own ceiling, and it is the max_bytes the first call answers.
Uploads are yours: the path begins with your account id, and no other account can read or sign it.
#Address, not product
The first call used to name a product — threed, video or external — and the caller had to
know which bucket an address reads. product still works for every key it worked for, and every
response to it carries a Deprecation header and a Link to this section; send address instead.
A key minted with the kinds uses it too, which is what a client that can name only the product
needs: external takes such a key's files of the kinds some address of its kinds takes — an image
with any of them, a mesh with 3d:write — and so does its read link; a file of another kind is
refused 403 insufficient_scope, naming the scopes that would take it.
video is no longer offered: a picture for our video goes through its address, which stores it under a
name of its own and reads back only those. The product spelling answers only the keys that used it — one holding auray-ai-video:write,
or video:write beside external:write — for an upload there and for a read back with
?product=video. video:write without external:write — the video kind as Account mints it — is
refused 403 insufficient_scope there, and so is external:write alone.
#Or skip all of this
A public https:// URL works in any URL field as it is. Uploading is for
files that are not on the internet yet, or that you would rather not leave there. A path field is
the exception: image_path, first_frame_path and reference_image_paths take only an upload's path.
The desktop's Playground app does exactly these three calls when you press Upload a file next to a URL field, and prints the resulting request on its API page.