Get started
Quickstart
From no account to a finished render.
Every request here is drawn in cURL, both client libraries and the other languages a docs site usually carries — with the MCP call an agent makes wherever a tool does the same thing. Pick yours once and every example in these docs follows it. Every response below is one the API actually sent, request ids and all.
#1. Get a key
Open app.auray.ai, then Account → API Keys → New key.
Two things are decided at mint time and matter later:
- Scopes. A key may only reach the products you tick. A key with
photo:writecannot submit a video job — it is refused before anything is charged. - Ceiling. An optional cap on what this key may spend in one calendar month, counted in UTC and starting again on the 1st. Useful for a key that lives in something you do not fully control.
The key is shown once. It starts with auray_sk_ and is 71 characters long.
#2. Submit a job
The model's name is the URL. Nothing else identifies it. prompt, aspect_ratio and resolution
are the three fields this address requires; its contract
lists the rest.
curl -X POST https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image \
-H "Authorization: Bearer $AURAY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: docs-mtpb1vnh" \
-d '{
"prompt": "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": 1024
}'import { createClient } from "@auray/client";
const auray = createClient(); // reads AURAY_API_KEY
const job = await auray.queue.submit("auray-ai/krea-2-turbo/text-to-image", {
input: {
"prompt": "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": 1024
},
idempotencyKey: "docs-mtpb1vnh",
});
console.log(job.request_id, job.status);from auray_client import Client
auray = Client() # reads AURAY_API_KEY
job = auray.queue.submit(
"auray-ai/krea-2-turbo/text-to-image",
input={
"prompt": "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": 1024
},
idempotency_key="docs-mtpb1vnh",
)
print(job["request_id"], job["status"]){
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "submit_job",
"arguments": {
"address": "auray-ai/krea-2-turbo/text-to-image",
"input": {
"prompt": "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": 1024
},
"idempotency_key": "docs-mtpb1vnh"
}
}
}const res = await fetch("https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AURAY_API_KEY}`,
"content-type": "application/json",
"Idempotency-Key": "docs-mtpb1vnh",
},
body: JSON.stringify({
"prompt": "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": 1024
}),
});
console.log(res.status, await res.json());import os, requests
r = requests.post(
"https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image",
headers={"Authorization": f"Bearer {os.environ['AURAY_API_KEY']}", "Idempotency-Key": "docs-mtpb1vnh"},
json={
"prompt": "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": 1024
},
)
r.raise_for_status()
print(r.json())package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := []byte(`{
"prompt": "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": 1024
}`)
req, err := http.NewRequest("POST", "https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image", 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")
req.Header.Set("Idempotency-Key", "docs-mtpb1vnh")
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/krea-2-turbo/text-to-image");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("AURAY_API_KEY"),
"Content-Type: application/json",
"Idempotency-Key: docs-mtpb1vnh",
],
CURLOPT_POSTFIELDS => json_encode([
"prompt" => "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio" => "16:9",
"resolution" => 1024,
], 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/krea-2-turbo/text-to-image")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("AURAY_API_KEY")}"
req["Idempotency-Key"] = "docs-mtpb1vnh"
req["Content-Type"] = "application/json"
req.body = {
"prompt" => "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio" => "16:9",
"resolution" => 1024
}.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 = """
{
"prompt": "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": 1024
}""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image"))
.header("Authorization", "Bearer " + System.getenv("AURAY_API_KEY"))
.header("Idempotency-Key", "docs-mtpb1vnh")
.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/krea-2-turbo/text-to-image");
request.Headers.Add("Idempotency-Key", "docs-mtpb1vnh");
var body = """
{
"prompt": "a brass orrery on a walnut desk, morning light from a window to the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": 1024
}
""";
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()}");You get the job back immediately, before anything has rendered. The charge is already made — it is reversed if the job fails — and three URLs tell you where to go next:
{
"status": "IN_QUEUE",
"request_id": "photo_docs-mtpb1vnh",
"job_id": "photo_docs-mtpb1vnh",
"queue_position": 0,
"logs": [],
"metrics": {},
"product": "photo",
"provider_job_id": "f30c5f765770468097181c2bcb31fd4d",
"model": "auray-ai/krea-2-turbo/text-to-image",
"engine": "Krea 2 Turbo",
"attribution": "Powered by Krea 2",
"credits_charged": 4,
"credits_remaining": 38807,
"expected_seconds": 39,
"replayed": false,
"status_url": "https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status",
"response_url": "https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh",
"cancel_url": "https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/cancel"
}request_id is the job's name everywhere else: photo_ plus the idempotency key you sent — the
same string as job_id beside it, which is what every host calls a job, and id on
GET /v1/jobs/{id}. model is the address you submitted to; engine is the service's own name for
what ran. Send
the same key again and you get this same job back with "replayed": true and nothing charged —
see Idempotency.
#3. Collect the result
Poll status_url until it says COMPLETED, then fetch response_url:
curl "https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status?logs=1" \
-H "Authorization: Bearer $AURAY_API_KEY"import { createClient } from "@auray/client";
const auray = createClient(); // reads AURAY_API_KEY
const status = await auray.queue.status("auray-ai/krea-2-turbo/text-to-image", { requestId: "photo_docs-mtpb1vnh", logs: true });
console.log(status.status);from auray_client import Client
auray = Client() # reads AURAY_API_KEY
status = auray.queue.status("auray-ai/krea-2-turbo/text-to-image", request_id="photo_docs-mtpb1vnh", logs=True)
print(status["status"]){
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "check_job",
"arguments": {
"address": "auray-ai/krea-2-turbo/text-to-image",
"request_id": "photo_docs-mtpb1vnh"
}
}
}const res = await fetch("https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status?logs=1", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.AURAY_API_KEY}`,
},
});
console.log(res.status, await res.json());import os, requests
r = requests.get(
"https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status?logs=1",
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://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status?logs=1", 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://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status?logs=1");
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://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status?logs=1")
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://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status?logs=1"))
.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://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status?logs=1");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");{
"status": "COMPLETED",
"request_id": "photo_docs-mtpb1vnh",
"job_id": "photo_docs-mtpb1vnh",
"logs": [],
"metrics": { "inference_time": 2.32 },
"error": null,
"error_type": null,
"status_url": "…/requests/photo_docs-mtpb1vnh/status",
"response_url": "…/requests/photo_docs-mtpb1vnh",
"cancel_url": "…/requests/photo_docs-mtpb1vnh/cancel"
}The result names what was made; the signed URL to download it comes from the assets route on
api.auray.ai, because signing it costs no credits but counts the files' size against the
egress rate-limit bucket, and a status poll does not. The link can be fetched again until it
expires; asking for it again counts the size again:
curl https://api.auray.ai/v1/jobs/photo_docs-mtpb1vnh/assets \
-H "Authorization: Bearer $AURAY_API_KEY"import { createClient } from "@auray/client";
const auray = createClient(); // reads AURAY_API_KEY
const { assets } = await auray.platform.jobs.assets("photo_docs-mtpb1vnh");
for (const asset of assets) console.log(asset.url);from auray_client import Client
auray = Client() # reads AURAY_API_KEY
listing = auray.platform.jobs.assets("photo_docs-mtpb1vnh")
for asset in listing["assets"]:
print(asset["url"]){
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_job_result",
"arguments": {
"address": "auray-ai/krea-2-turbo/text-to-image",
"request_id": "photo_docs-mtpb1vnh",
"assets": true
}
}
}const res = await fetch("https://api.auray.ai/v1/jobs/photo_docs-mtpb1vnh/assets", {
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/jobs/photo_docs-mtpb1vnh/assets",
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/jobs/photo_docs-mtpb1vnh/assets", 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/jobs/photo_docs-mtpb1vnh/assets");
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/jobs/photo_docs-mtpb1vnh/assets")
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/jobs/photo_docs-mtpb1vnh/assets"))
.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/jobs/photo_docs-mtpb1vnh/assets");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");{
"id": "photo_docs-mtpb1vnh",
"object": "asset_list",
"product": "photo",
"expires_at": "2026-09-06T04:37:47.701Z",
"megabytes_charged": 2,
"assets": [
{
"index": 0,
"kind": "image",
"filename": "0.png",
"content_type": "image/png",
"bytes": 1065131,
"url": "https://…/0.png?…"
}
],
"request_id": "bc0cab07-ee23-40ea-b977-8b9cce022e9c"
}Asset URLs expire. Download what you want to keep.
For anything longer than a few seconds, prefer a webhook — you get a signed POST the moment the
job settles, and you never hold a connection open. See Webhooks.
#4. Check what it cost
curl https://api.auray.ai/v1/me \
-H "Authorization: Bearer $AURAY_API_KEY"import { createClient } from "@auray/client";
const auray = createClient(); // reads AURAY_API_KEY
console.log(await auray.platform.me());from auray_client import Client
auray = Client() # reads AURAY_API_KEY
print(auray.platform.me()){
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_account",
"arguments": {}
}
}const res = await fetch("https://api.auray.ai/v1/me", {
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/me",
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/me", 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/me");
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/me")
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/me"))
.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/me");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");{
"object": "account",
"id": "86e97633-…",
"plan": "ultimate",
"credits": { "balance": 38807, "plan_credits": 3807, "purchased_credits": 35000, "granted_credits": 0, "monthly_allowance": 5000, "period": "2026-09" },
"key": {
"id": "55oy2jFo5cmq",
"scopes": ["jobs:read", "assets:read", "image:write", "video:write", "audio:write", "3d:write", "text:write"],
"credit_ceiling": 500,
"credits_spent_period": 332
},
"request_id": "961dd3b6-59b4-4e26-bb8b-e4c28f1cbf96"
}A refused job is not charged. A job that fails after it starts is refunded automatically — you do not file anything. Every other case — stopped, stalled, partly withheld — is in the settlement table.