API mockupów i packshotów 3D suplementów
Wyślij projekt etykiety (i kartonika) na wykrojniku naszego opakowania – od razu dostaniesz interaktywny podgląd 3D do osadzenia na stronie, a po chwili komplet packshotów 2000 × 2000 px, grafikę „Skład” i podgląd 3D na przezroczystym tle. Ten sam silnik, którym liczymy packshoty w kreatorze.

Co daje API
Jedno zlecenie z plikiem etykiety – a wynik wygląda dokładnie tak jak packshoty, które klienci kupują w kreatorze.
| Wynik | Kiedy | Postać |
|---|---|---|
| Interaktywny podgląd 3D | od razu w odpowiedzi na zlecenie | adres strony do osadzenia w ramce (viewer.url, gotowy kod viewer.embed) – obraca się, przybliża, działa na telefonie |
| Packshoty | po chwili (zwykle kilkanaście sekund) | 11–12 ujęć produktu (butelka 11, słoik i blister 12), bez kartonika mniej – JPEG 2000 × 2000 px: na bieli, na tle w kolorze etykiety, na ciemnym tle, trzy sztuki, otwarte opakowanie, zbliżenie… |
| Grafika „Skład” | razem z packshotami | infografika z produktem i tabelą składu w porcji dziennej (z receptury produktu albo z Twojej tabeli) – JPEG 2000 × 2000 px |
| Podgląd 3D na przezroczystym tle | razem z packshotami | PNG 1200 × 1200 px z cieniem w kanale alfa – do wstawienia na stronę |
| ZIP | gdy jest choć jeden wynik | wszystkie gotowe pliki pod jednym podpisanym adresem |
Klucz API wydajemy partnerom – agencjom, sklepom i markom, które projektują etykiety u siebie. Napisz do nas albo na kontakt@auralab360.pl: ustalimy limit renderów i adres webhooka. Na próbę dostaniesz klucz próbny – wyniki mają wtedy znak wodny „auralab360 · podgląd” i najwyżej 1200 px, a podgląd 3D plakietkę „Podgląd”.
Jak to działa
Podgląd 3D rysuje przeglądarka oglądającego, a packshoty liczy węzeł renderujący – komputer z kartą graficzną, który pobiera zlecenia z kolejki.
- 1Wybierz opakowanie (
GET /packagings) albo produkt z naszego katalogu (GET /products) i pobierz jego wykrojnik (PDF). - 2Zaprojektuj etykietę na wykrojniku – strona ze spadem; samą etykietę bez spadu też przyjmiemy.
- 3Wyślij
POST /rendersz plikiem. Odpowiedź202przychodzi od razu: render w staniequeuedi adres podglądu 3D (viewer.url) – możesz go pokazać klientowi, zanim powstaną packshoty. - 4Węzeł renderujący liczy wyniki po kolei – stan i postęp w
GET /renders/{id}; polequeuemówi, ile renderów czeka przed Twoim i czy węzeł jest w sieci. - 5Gdy render się skończy, wyślemy webhook
render.succeeded(albo czekaj zapytaniemGET /renders/{id}?wait=30). - 6Pobierz pliki z podpisanych adresów (
outputs[].url) albo cały ZIP (archive.url) – bez klucza, przez 7 dni.
Czas: komplet 12 wyników 2000 px to zwykle kilkanaście sekund od chwili, gdy węzeł weźmie zlecenie. Gdy węzeł jest wyłączony, render czeka w kolejce (queue.workersOnline: 0, estimatedSeconds: null) i zacznie się liczyć, gdy węzeł wróci – nic nie przepada, a podgląd 3D działa od razu.
Szybki start
Adres API: https://kreator.auralab360.pl/api/v1. Odpowiedzi i błędy to JSON (UTF-8); komunikaty są po polsku – można je pokazać użytkownikowi.
1. Opakowania i wykrojniki
# klucz API trzymaj w zmiennej środowiska – nigdy w kodzie strony
export AURA_API_KEY=al_live_…
curl https://kreator.auralab360.pl/api/v1/packagings \
-H "Authorization: Bearer $AURA_API_KEY"
# wykrojnik etykiety (adres z pola label.dieline)
curl -o wykrojnik-etykieta.pdf "https://kreator.auralab360.pl/files/dieline/2026/09/VeYazr5KhSSCwHNX98uu6g.pdf"
# wykrojnik kartonika (box.dieline)
curl -o wykrojnik-kartonik.pdf "https://kreator.auralab360.pl/files/dieline/2026/09/bfOy8gqTKcjkpf7eujPE8w.pdf"{
"object": "list",
"data": [
{
"id": "dropper-50",
"name": "Butelka z pipetą 50 ml (szkło bursztynowe)",
"model": "dropper",
"volume": "50 ml",
"label": {
"size": [120, 55],
"page": [126, 61],
"trim": [120, 55],
"dieline": "https://kreator.auralab360.pl/files/dieline/2026/09/VeYazr5KhSSCwHNX98uu6g.pdf"
},
"box": {
"size": [38, 38, 117],
"page": [171.32, 229.83],
"trim": null,
"dieline": "https://kreator.auralab360.pl/files/dieline/2026/09/bfOy8gqTKcjkpf7eujPE8w.pdf",
"required": false
},
"caps": [
{ "id": "czarna", "name": "Czarna" },
{ "id": "biala", "name": "Biała" },
{ "id": "zlota", "name": "Złota" }
],
"defaultCap": "czarna",
"contents": {
"kind": "liquid",
"options": [
{ "id": "olej", "name": "Olej" },
{ "id": "ekstrakt", "name": "Ekstrakt (ciemny)" },
{ "id": "woda", "name": "Płyn wodny (jasny)" }
],
"default": "olej"
},
"shots": {
"withBox": ["hero", "color", "front", "left", "back", "duo", "trio", "open", "flatlay", "detail", "dark"],
"withoutBox": ["color", "front", "left", "back", "trio", "open", "detail", "dark"]
},
"products": 39,
"orderable": true
}
]
}2. Zlecenie renderu
curl https://kreator.auralab360.pl/api/v1/renders \
-H "Authorization: Bearer $AURA_API_KEY" \
-H "Idempotency-Key: zh-50-etykieta-v1" \
-F packaging=dropper-50 \
-F name="Zielona herbata" \
-F cap=zlota \
-F label=@etykieta.pdf \
-F box=@kartonik.pdf \
-F 'metadata={"sku":"ZH-50"}'Odpowiedź przychodzi od razu (202 Accepted, nagłówek Location). Idempotency-Key chroni przed podwójnym zleceniem, gdy ponawiasz zapytanie po zerwanym połączeniu – ten sam klucz zwraca ten sam render.
{
"id": "rnd_7Gk2LmQ9TxWb3Hc5Rv1Z",
"object": "render",
"status": "queued",
"createdAt": "2026-10-09T09:15:39.721Z",
"startedAt": null,
"finishedAt": null,
"expiresAt": "2026-11-08T09:15:39.719Z",
"purged": false,
"packaging": {
"id": "dropper-50",
"name": "Butelka z pipetą 50 ml (szkło bursztynowe)",
"volume": "50 ml",
"model": "dropper"
},
"product": null,
"input": {
"label": {
"name": "etykieta.pdf",
"kind": "pdf",
"fit": "page",
"page": [126, 61],
"dpi": null,
"dielineRemoved": false,
"warning": null
},
"box": {
"name": "kartonik.pdf",
"kind": "pdf",
"fit": "page",
"page": [171.32, 229.83],
"dpi": null,
"dielineRemoved": false,
"warning": null
},
"cap": "zlota",
"contents": "olej",
"size": 2000,
"watermark": false,
"outputs": ["hero", "color", "front", "left", "back", "duo", "trio", "open", "flatlay", "detail", "dark", "preview"]
},
"progress": { "done": 0, "total": 12 },
"queue": { "position": 1, "workersOnline": 1, "estimatedSeconds": 9 },
"viewer": {
"url": "https://kreator.auralab360.pl/podglad-3d/q3Xn8LpV0aT2bR6sKe1WmY9cHd4fJg7u",
"embed": "<iframe src=\"https://kreator.auralab360.pl/podglad-3d/q3Xn8LpV0aT2bR6sKe1WmY9cHd4fJg7u\" title=\"Podgląd 3D\" width=\"100%\" height=\"560\" style=\"border:0\" allow=\"fullscreen\" loading=\"lazy\"></iframe>"
},
"outputs": [
{
"name": "hero",
"kind": "shot",
"label": "Butelka z kartonikiem",
"status": "pending",
"contentType": null,
"width": null,
"height": null,
"bytes": null,
"url": null,
"downloadUrl": null,
"error": null
},
{
"name": "color",
"kind": "shot",
"label": "Na tle w kolorze etykiety",
"status": "pending",
"contentType": null,
"width": null,
"height": null,
"bytes": null,
"url": null,
"downloadUrl": null,
"error": null
}
],
"archive": null,
"error": null,
"warnings": [],
"metadata": { "sku": "ZH-50" }
}3. Czekanie na wynik i pliki
# czeka do 30 s, aż render się skończy (powtarzaj, dopóki status to queued albo processing)
curl "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z?wait=30" \
-H "Authorization: Bearer $AURA_API_KEY"# podpisany adres – działa bez klucza do terminu z parametru expires
curl -o packshoty.zip "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/archive.zip?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA"{
"id": "rnd_7Gk2LmQ9TxWb3Hc5Rv1Z",
"object": "render",
"status": "succeeded",
"createdAt": "2026-10-09T09:15:39.721Z",
"startedAt": "2026-10-09T09:15:41.102Z",
"finishedAt": "2026-10-09T09:15:47.659Z",
"expiresAt": "2026-11-08T09:15:39.719Z",
"purged": false,
"packaging": {
"id": "dropper-50",
"name": "Butelka z pipetą 50 ml (szkło bursztynowe)",
"volume": "50 ml",
"model": "dropper"
},
"product": null,
"input": {
"label": {
"name": "etykieta.pdf",
"kind": "pdf",
"fit": "page",
"page": [126, 61],
"dpi": null,
"dielineRemoved": false,
"warning": null
},
"box": {
"name": "kartonik.pdf",
"kind": "pdf",
"fit": "page",
"page": [171.32, 229.83],
"dpi": null,
"dielineRemoved": false,
"warning": null
},
"cap": "zlota",
"contents": "olej",
"size": 2000,
"watermark": false,
"outputs": ["hero", "color", "front", "left", "back", "duo", "trio", "open", "flatlay", "detail", "dark", "preview"]
},
"progress": { "done": 12, "total": 12 },
"queue": null,
"viewer": {
"url": "https://kreator.auralab360.pl/podglad-3d/q3Xn8LpV0aT2bR6sKe1WmY9cHd4fJg7u",
"embed": "<iframe src=\"https://kreator.auralab360.pl/podglad-3d/q3Xn8LpV0aT2bR6sKe1WmY9cHd4fJg7u\" title=\"Podgląd 3D\" width=\"100%\" height=\"560\" style=\"border:0\" allow=\"fullscreen\" loading=\"lazy\"></iframe>"
},
"outputs": [
{
"name": "hero",
"kind": "shot",
"label": "Butelka z kartonikiem",
"status": "ready",
"contentType": "image/jpeg",
"width": 2000,
"height": 2000,
"bytes": 280732,
"url": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/outputs/hero?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA",
"downloadUrl": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/outputs/hero?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA&download=1",
"error": null
},
{
"name": "front",
"kind": "shot",
"label": "Front",
"status": "ready",
"contentType": "image/jpeg",
"width": 2000,
"height": 2000,
"bytes": 148891,
"url": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/outputs/front?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA",
"downloadUrl": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/outputs/front?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA&download=1",
"error": null
},
{
"name": "preview",
"kind": "preview",
"label": "Podgląd 3D (przezroczyste tło)",
"status": "ready",
"contentType": "image/png",
"width": 1200,
"height": 1200,
"bytes": 529808,
"url": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/outputs/preview?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA",
"downloadUrl": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/outputs/preview?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA&download=1",
"error": null
}
],
"archive": {
"url": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/archive.zip?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA"
},
"error": null,
"warnings": [],
"metadata": { "sku": "ZH-50" }
}Wszystkie metody
| Metoda | Ścieżka (od https://kreator.auralab360.pl/api/v1) | Opis |
|---|---|---|
GET | /me | Klucz: nazwa, zużycie w miesiącu, limity, adres webhooka |
GET | /packagings | Opakowania z wykrojnikami, zakrętkami, zawartością i zestawem ujęć |
GET | /packagings/{id} | Jedno opakowanie |
GET | /products | Produkty katalogu, które można narysować z Twoją etykietą (?q=, ?packaging=) |
POST | /renders | Zlecenie renderu – odpowiedź 202 z podglądem 3D od razu |
GET | /renders | Twoje rendery, najnowsze najpierw (?limit=, ?after=, ?status=) |
GET | /renders/{id} | Stan i wyniki renderu (?wait=1…30 – czekanie na koniec) |
DELETE | /renders/{id} | Anulowanie renderu i usunięcie jego plików |
GET | /renders/{id}/outputs/{name} | Plik wyniku – z kluczem albo z podpisanego adresu |
GET | /renders/{id}/archive.zip | Wszystkie gotowe wyniki w ZIP-ie |
POST | /webhooks/test | Próbny webhook ping na adres klucza |
GET | /openapi.json | Opis API w formacie OpenAPI 3.1 (bez klucza) |
Klucz API i limity
- Każde zapytanie: nagłówek
Authorization: Bearer al_live_…(alboX-API-Key: al_live_…). Bez klucza –401z kodemAPI_KEY_MISSING, ze złym –API_KEY_INVALID. - API jest dla serwerów: nie wysyłamy nagłówków CORS, a klucza nie wolno umieszczać w kodzie strony ani aplikacji mobilnej. Klientowi pokazuj podgląd 3D i podpisane adresy plików – one klucza nie wymagają.
- Jeden klucz na integrację. Klucz, który wyciekł, unieważniamy od razu i wydajemy nowy.
- Bez klucza działają tylko: strona startowa i opis API (
GET /api/v1,GET /openapi.json), podgląd 3D po adresie z renderu i pliki wyników z podpisanych adresów.
| Limit | Wartość |
|---|---|
| Zapytania jednym kluczem | 120 na minutę (nagłówki X-RateLimit-Limit, X-RateLimit-Remaining) |
| Nowe rendery jednym kluczem | 30 na minutę |
| Rendery w kolejce i w trakcie liczenia | najwyżej 50 na klucz |
| Pliki wysłane jednym kluczem | 2 GB na godzinę (liczone z Content-Length zlecenia) |
| Rendery w miesiącu | według umowy (bez limitu albo ustalona liczba; miesiąc kalendarzowy, czas polski, nieudane się nie liczą) |
| Zapytania z jednego adresu IP | 300 na minutę (wszystkie klucze razem) |
| Plik etykiety albo kartonika | do 25 MB, obraz do 20 000 px na dłuższym boku |
| Treść zapytania JSON | do 32 MB (plik w base64 jest o ⅓ większy) – większe pliki jako multipart albo adres URL |
| Przechowywanie plików renderu | 30 dni od zlecenia (expiresAt) |
{
"object": "api_key",
"name": "Agencja XYZ – sklep",
"prefix": "al_live_7Gk2",
"watermark": false,
"maxSize": 2000,
"usage": { "month": 148, "quota": 1000, "resetsAt": "2026-10-31T23:00:00.000Z", "open": 2 },
"limits": { "requestsPerMinute": 120, "rendersPerMinute": 30, "openRenders": 50 },
"webhook": { "url": "https://twoj-sklep.pl/aura/webhook", "signed": true }
}Po przekroczeniu limitu odpowiadamy 429 z nagłówkiem Retry-After (sekundy). Limit miesięczny odnawia się pierwszego dnia miesiąca o północy czasu polskiego (usage.resetsAt).
Opakowania i wykrojniki
Lista z naszego katalogu – ta sama, którą zwraca GET /packagings. Identyfikator to rodzaj opakowania i wielkość; plik etykiety ma mieć stronę wykrojnika ze spadem (albo sam wymiar etykiety).
| Opakowanie | Etykieta | Kartonik | Opcje |
|---|---|---|---|
jar-glass-150Słoik mały (szkło bursztynowe)ujęć: 12 z kartonikiem, 9 bez | zakrętka ( cap)czarna (domyślna)zlotasrebrnazawartość ( contents)kapsulki (domyślnie)tabletkisoftgelproszek | ||
jar-glass-250Słoik mały (szkło bursztynowe)ujęć: 12 z kartonikiem, 9 bez | zakrętka ( cap)czarna (domyślna)zlotasrebrnazawartość ( contents)kapsulkitabletkisoftgel (domyślnie)proszek | ||
jar-glass-300Słoik duży (szkło bursztynowe)ujęć: 12 z kartonikiem, 9 bez | zakrętka ( cap)czarna (domyślna)zlotasrebrnazawartość ( contents)kapsulkitabletki (domyślnie)softgelproszek | ||
blister-100Kartonik z blistrem – probiotyki – w sklepie chwilowo niedostępne (render działa)ujęć: 12 | – | zawartość ( contents)kapsulki (domyślnie)tabletkisoftgel | |
blister-150Kartonik z blistrem – duży – w sklepie chwilowo niedostępne (render działa)ujęć: 12 | – | zawartość ( contents)kapsulki (domyślnie)tabletkisoftgel | |
dropper-15Butelka z pipetą 15 ml (szkło bursztynowe)ujęć: 11 z kartonikiem, 8 bez | zakrętka ( cap)czarna (domyślna)bialazlotazawartość ( contents)olej (domyślnie)ekstraktwoda | ||
dropper-50Butelka z pipetą 50 ml (szkło bursztynowe)ujęć: 11 z kartonikiem, 8 bez | zakrętka ( cap)czarna (domyślna)bialazlotazawartość ( contents)olej (domyślnie)ekstraktwoda | ||
jar-hdpe-2000Słoik HDPE 2 l (biały)ujęć: 9 | – | zakrętka ( cap)biala (domyślna)zawartość ( contents)proszek (domyślnie) |
- Wymiary w milimetrach;
label.pageibox.pageto strona pliku, który wysyłasz (PDF – z tolerancją 2 mm i 2% proporcji, obraz – 2% proporcji). Wykrojnik to PDF tej strony: etykieta ze spadem 3 mm z każdej strony i linią cięcia. - Blister to sam kartonik: wysyłasz tylko plik
box. Przy butelce i słoiku kartonik jest opcjonalny – bez niego nie ma ujęć z kartonikiem. - Tryb produktu: zamiast
packagingpodajproduct(identyfikator albo SKU zGET /products) – opakowanie, zawartość (kapsułki, płyn) i grafika „Skład” z naszej receptury. Wykrojnik i strona pliku – z póllabeliboxproduktu (mogą się różnić od opakowania). - Zawartość (
contents) widać przez szkło i w ujęciach z otwartym opakowaniem: kapsułki, tabletki, kapsułki żelowe albo proszek w słoiku, a w butelce z pipetą – olej, ciemny ekstrakt albo jasny płyn wodny.
Plik etykiety i kartonika
- PDF (pierwsza strona), PNG albo JPG, do 25 MB; rodzaj sprawdzamy po zawartości, nie po rozszerzeniu.
- Strona pliku = strona wykrojnika ze spadem (
page). Etykietę przyjmiemy też w samym wymiarze netto (trim) – spad dopełnimy, przedłużając krawędzie projektu (input.label.fit: "trim"i uwaga wwarnings). Kartonik – tylko na stronie wykrojnika. - Obraz: co najmniej 300 dpi w rozmiarze arkusza. Od 150 do 299 dpi – przyjmiemy z uwagą w
warnings(drobny tekst może być nieostry na zbliżeniach), poniżej 150 dpi – odrzucimy (OWN_ART_DPI). PDF z grafiką wektorową jest zawsze ostry. - Linie wykrojnika zostawione w pliku (cięcie, zagięcia, opis) ukrywamy na podglądzie –
dielineRemoved: true. Plik pusty albo sam wykrojnik bez projektu odrzucamy (OWN_ART_EMPTY). - Treści etykiety (skład, ostrzeżenia, dane producenta, kod EAN) są Twoje – niczego w pliku nie zmieniamy ani nie sprawdzamy pod kątem przepisów.
| Jak przesłać | Kiedy |
|---|---|
multipart: pliki w polach label i box | zalecane – najmniej danych, pliki do 25 MB |
multipart: labelUrl, boxUrl | pliki leżą na Twoim serwerze: publiczny adres https, do 25 MB, odpowiedź w 20 s, najwyżej 3 przekierowania |
JSON: "label": { "url": "https://…" } | to samo co wyżej, w treści JSON |
JSON: "label": { "data": "<base64>", "filename": "…" } | plik w treści – do ok. 23 MB (limit treści zapytania 32 MB) |
curl https://kreator.auralab360.pl/api/v1/renders \
-H "Authorization: Bearer $AURA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"packaging":"dropper-50","outputs":["front","color","preview"],"label":{"url":"https://twoj-serwer.pl/projekty/etykieta.pdf"}}'# plik w base64 w treści JSON (do ok. 23 MB – limit treści zapytania to 32 MB)
B64=$(base64 < etykieta.png | tr -d "\n")
curl https://kreator.auralab360.pl/api/v1/renders \
-H "Authorization: Bearer $AURA_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"packaging\":\"dropper-50\",\"label\":{\"data\":\"$B64\",\"filename\":\"etykieta.png\"}}"Zlecenie renderu – pola
POST /renders: multipart/form-data (pliki i pola tekstowe) albo application/json. Pola opcji są takie same w obu formach.
| Pole | Typ | Wymagane | Domyślnie | Opis |
|---|---|---|---|---|
packaging | tekst | jedno z dwóch | – | Opakowanie z GET /packagings, np. dropper-50. Produkt zastępczy bez receptury – zawartość wybierasz polem contents. |
product | tekst | jedno z dwóch | – | Produkt z katalogu (GET /products): identyfikator albo SKU. Opakowanie, zawartość i tabela „Skład” z receptury. |
label | plik | tak (poza blistrem) | – | Projekt etykiety: PDF, PNG albo JPG do 25 MB. Multipart – plik; JSON – { "data": "<base64>", "filename": "…" } albo { "url": "https://…" }. |
box | plik | blister – tak | – | Projekt kartonika (strona wykrojnika kartonika). Butelka i słoik: opcjonalny – bez niego nie ma ujęć z kartonikiem. |
labelUrl, boxUrl | adres https | nie | – | Tylko w multipart: adres pliku zamiast pliku. Pobieramy go sami (do 25 MB, 20 s, najwyżej 3 przekierowania). |
cap | tekst | nie | zakrętka produktu albo defaultCap | Zakrętka z listy caps opakowania, np. czarna, zlota. |
contents | tekst | nie | contents.default | Tylko z packaging: forma (kapsulki, tabletki, softgel, proszek) albo płyn w butelce (olej, ekstrakt, woda). |
outputs | lista | nie | wszystkie dostępne | Wybrane wyniki. Multipart: po przecinku (hero,front,back) albo powtórzone pole; JSON: tablica. |
size | liczba | nie | 2000 (klucz próbny – 1200) | Bok packshotów w px, kwadrat, od 800 do limitu klucza (domyślnie 2000, najwyżej 3000). |
name | tekst ≤ 120 | z `composition` – tak | nazwa produktu z katalogu | Nazwa produktu – tytuł grafiki „Skład” i początek nazw plików. Wymagana, gdy zamawiasz grafikę „Skład” z własnej tabeli (composition). |
color | #rrggbb | nie | główny kolor etykiety | Kolor nagłówka tabeli na grafice „Skład”. |
composition | obiekt | nie | – | Tylko z packaging: tabela grafiki „Skład” (schemat niżej). Multipart – JSON w polu tekstowym. |
webhookUrl | adres https | nie | adres webhooka klucza | Webhook tylko dla tego renderu – podpisany sekretem webhooka klucza (każdy klucz go ma, także bez domyślnego adresu). |
metadata | obiekt | nie | – | Twoje dane (do 20 pól, razem do 4000 znaków; wartości: tekst do 500 znaków, liczba, prawda/fałsz, null) – wracają w renderze i webhooku. Multipart – JSON w polu tekstowym. |
Idempotency-Key | nagłówek | nie | – | Do 100 znaków ASCII bez spacji. Ten sam klucz – ten sam render (bezpieczne ponowienie po zerwanym połączeniu); odpowiedź 200 z nagłówkiem Idempotent-Replayed: true. |
Tabela „Skład” (composition)
rows– od 1 do 40 wierszy:name(do 120 znaków),amount(liczba albo tekst),unit(np.mg,µg),rws– %RWS (liczba albo tekst; liczba 100,6 → „101%”, 7,25 → „7,3%”),sub: true– wiersz „w tym”, wcięty.portion– porcja dzienna („2 kapsułki”),ingredients– pełny skład (do 3000 znaków),usage– sposób użycia (do 1000 znaków).- Liczby zapisujemy po polsku (przecinek, spacja tysięcy); kolumna %RWS pojawia się, gdy choć jeden wiersz ją ma.
- Tylko z
packaging– produkt z katalogu ma tabelę z receptury. Bez tabeli wynikcompositionjest niedostępny (UNKNOWN_OUTPUT).
{
"portion": "2 kapsułki",
"rows": [
{ "name": "Ekstrakt z ashwagandhy", "amount": 600, "unit": "mg" },
{ "name": "w tym witanolidy", "amount": 30, "unit": "mg", "sub": true },
{ "name": "Witamina B6", "amount": 1.4, "unit": "mg", "rws": 100 },
{ "name": "Magnez", "amount": 112.5, "unit": "mg", "rws": 30 }
],
"ingredients": "Składniki: ekstrakt z korzenia ashwagandhy (Withania somnifera), cytrynian magnezu, otoczka kapsułki – hydroksypropylometyloceluloza, chlorowodorek pirydoksyny.",
"usage": "Dorośli: 2 kapsułki dziennie, popijając wodą."
}curl https://kreator.auralab360.pl/api/v1/renders \
-H "Authorization: Bearer $AURA_API_KEY" \
-F packaging=dropper-50 \
-F name="Spokojny sen" \
-F outputs=front,composition \
-F label=@etykieta.pdf \
-F 'composition={"portion":"2 kapsułki","rows":[{"name":"Witamina B6","amount":1.4,"unit":"mg","rws":100}]}'Wyniki
Nazwy wyników (pole outputs) – ujęcia w kolejności galerii, grafika „Skład” i podgląd 3D. Zestaw zależy od opakowania i od tego, czy wysłałeś kartonik.
| Wynik | Butelka z pipetą | Słoik | Blister w kartoniku |
|---|---|---|---|
hero | Butelka z kartonikiem · wymaga pliku box | Słoik z kartonikiem · wymaga pliku box | Kartonik z blistrem |
color | Na tle w kolorze etykiety | Na tle w kolorze etykiety | Na tle w kolorze kartonika |
front | Front | Front | Kartonik – front |
left | ¾ z góry | ¾ z góry | Kartonik ¾ z góry |
back | Tył ze składem | Tył ze składem | Kartonik – tył ze składem |
duo | Butelka obok kartonika · wymaga pliku box | Słoik obok kartonika · wymaga pliku box | Kartonik obok blistra |
trio | Trzy butelki | Trzy słoiki | Trzy kartoniki |
flat | – | – | Blister na płasko |
open | Z wyjętą pipetą | Otwarty słoik z kapsułkami | Blister z kapsułkami obok |
top | – | Z góry – wnętrze słoika | – |
flatlay | Z góry – na leżącym kartoniku · wymaga pliku box | Z góry – z leżącym kartonikiem · wymaga pliku box | Z góry – kartonik i blister |
detail | Zbliżenie etykiety | Zbliżenie etykiety | Zbliżenie blistra |
dark | Na ciemnym tle | Na ciemnym tle | Na ciemnym tle |
composition | Skład w porcji dziennej | Skład w porcji dziennej | Skład w porcji dziennej |
preview | Podgląd 3D (przezroczyste tło) | Podgląd 3D (przezroczyste tło) | Podgląd 3D (przezroczyste tło) |
- Ujęcia i grafika „Skład”: JPEG, kwadrat
size×size(domyślnie 2000 px). Ujęcia „Kolor” i „Ciemne tło” są na tle studyjnym (w kolorze etykiety – z przeważającej barwy Twojego projektu), pozostałe na bieli. - Podgląd 3D: PNG z przezroczystym tłem i cieniem w kanale alfa, najwyżej 1200 px (przy
sizemniejszym niż 1200 – w tym rozmiarze). - Klucz próbny: wszystkie wyniki ze znakiem wodnym, najwyżej 1200 px, JPEG (także podgląd 3D – na bieli).
- Otwarty słoik ma podpis według formy: z kapsułkami, z tabletkami albo z miarką proszku.
outputs[].url– podpisany adres pliku (Content-Disposition: inline),downloadUrl– to samo do pobrania. Działają bez klucza 7 dni (najdłużej doexpiresAt); każdeGET /renders/{id}daje świeże. Można ich użyć w<img>na Twojej stronie.- Z kluczem:
GET /renders/{id}/outputs/{name}z nagłówkiemAuthorization. Zmieniony albo przeterminowany podpis –403 LINK_EXPIRED. - Nazwy plików:
<nazwa>-<nr>-<wynik>.jpg, np.zielona-herbata-01-hero.jpg(nazwa z polaname, bez niej – identyfikator opakowania; podgląd 3D –.png). archive.url– ZIP ze wszystkimi gotowymi wynikami (te same nazwy plików).
Stan renderu
| status | Znaczenie |
|---|---|
queued | W kolejce – czeka na węzeł renderujący. Pole queue: miejsce w kolejce, liczba węzłów w sieci, szacowany czas. |
processing | Węzeł liczy wyniki – progress.done rośnie, gotowe wyniki mają już adresy. |
succeeded | Gotowe. Pojedynczy wynik może mieć status: "failed" z opisem w error – pozostałe są do pobrania. |
failed | Nie udało się po 3 próbach – opis w error. Pliki wejścia zostają do końca przechowywania. |
canceled | Anulowany przez DELETE – pliki usunięte, podgląd 3D i linki nie działają. |
progress–done(wyniki gotowe albo nieudane) itotal. Wynik nieudany mastatus: "failed"i opis werror; render z choć jednym gotowym wynikiem kończy się jakosucceeded.queue(tylko w kolejce):position,workersOnlineiestimatedSeconds– szacunek ze średniej ostatnich renderów;null, gdy żaden węzeł nie jest w sieci.GET /renders/{id}?wait=30– odpowiedź przyjdzie, gdy render się skończy, najpóźniej po 30 s (od 1 do 30). Zamiast odpytywać co sekundę – wywołuj w pętli.GET /renders?limit=20– lista najnowszych; następna strona:?after=z polanext(dopókihasMore), filtr?status=.DELETE /renders/{id}– anuluje render (także w trakcie liczenia) i usuwa jego pliki: podgląd 3D i adresy plików przestają działać, zostaje sam opis.- Pliki renderu (Twoje pliki i wyniki) trzymamy 30 dni (
expiresAt), potem usuwamy:purged: true,viewer: null, adresy plikównull. Zapisz wyniki u siebie. warnings– uwagi do plików: rozdzielczość poniżej 300 dpi, ukryte linie wykrojnika, dopełniony spad.
Podgląd 3D na Twojej stronie
viewer.url to strona z interaktywnym modelem – ten sam podgląd co w konfiguratorze kreatora. Rysuje go przeglądarka oglądającego, więc działa od razu, także gdy packshoty jeszcze się liczą.
<!-- podgląd 3D na Twojej stronie: adres z pola viewer.url -->
<iframe src="https://kreator.auralab360.pl/podglad-3d/q3Xn8LpV0aT2bR6sKe1WmY9cHd4fJg7u?tlo=jasne"
title="Podgląd 3D" width="100%" height="560" style="border:0"
allow="fullscreen" loading="lazy"></iframe>| Parametr adresu | Działanie |
|---|---|
tlo=jasne · tlo=ciemne | tło sceny (także bg=light, bg=dark) |
obrot=1 | model obraca się sam, dopóki ktoś go nie dotknie (także autorotate=1) |
ui=0 | bez przycisków i podpowiedzi – sam model |
- Przeciągnięcie obraca produkt (na komputerze także pochyla), dwuklik wraca do frontu; przyciski: przód, tył, przybliżenie, tło jasne/ciemne i pełny ekran (ramka potrzebuje allow="fullscreen").
- Na telefonie palec obraca w poziomie, dwa palce przybliżają, dwa stuknięcia wracają do frontu; w wąskiej ramce przyciski stoją w rzędzie pod modelem.
- Stronę można osadzić w ramce na dowolnej domenie; wyszukiwarki jej nie indeksują. Adres jest dostępem do podglądu – działa do końca przechowywania renderu albo do DELETE.
Webhooki
Po zakończeniu renderu wysyłamy POST z pełnym renderem na adres webhooka – domyślny adres klucza albo webhookUrl z danego zlecenia.
| Zdarzenie | Kiedy |
|---|---|
render.succeeded | render gotowy (pojedyncze wyniki mogą być nieudane) |
render.failed | render nieudany po 3 próbach |
ping | próbny – POST /webhooks/test wysyła go od razu i zwraca kod odpowiedzi Twojego serwera |
- Podpis:
X-Aura-Signature: t=<unix>,v1=<hex>, gdziehex= HMAC-SHA256 sekretem webhooka (whsec_…– dostajesz go od nas razem z kluczem; ma go każdy klucz) z tekstu<t>.<surowa treść>. Licz go z bajtów, które przyszły (przed parsowaniem JSON), porównuj w stałym czasie i odrzucaj wiadomości starsze niż 5 minut. - Odpowiedz kodem 2xx w ciągu 10 s (dłuższą pracę zrób w tle). Inaczej ponawiamy: do 8 prób – kolejne po 30 s, 2 min, 10 min, 30 min, 1 h, 3 h i 6 h. Odpowiedź
410 Gonekończy ponawianie. Przekierowań nie wykonujemy. X-Aura-Deliveryiidzdarzenia są te same przy każdym ponowieniu – zapisz je i pomiń duplikat. Kolejność dostaw nie jest gwarantowana – decydujedata.status.- Adres webhooka: publiczny https z nazwą domeny (adresów IP i sieci wewnętrznych nie obsługujemy).
POST /aura/webhook HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: AuraLab360-Webhooks/1.0
X-Aura-Event: render.succeeded
X-Aura-Delivery: cmv1x0d2k0004a95n3ytzq7e1
X-Aura-Signature: t=1760001347,v1=5f0c6e0d5a7c3e9b1f2a4d6c8e0b2a4c6e8f0a2b4c6d8e0f1a3b5c7d9e1f3a5b{
"id": "evt_cmv1x0c9n0002a95nhq3l5r2b",
"type": "render.succeeded",
"createdAt": "2026-10-09T09:15:47.660Z",
"data": {
"id": "rnd_7Gk2LmQ9TxWb3Hc5Rv1Z",
"object": "render",
"status": "succeeded",
"finishedAt": "2026-10-09T09:15:47.659Z",
"packaging": {
"id": "dropper-50",
"name": "Butelka z pipetą 50 ml (szkło bursztynowe)",
"volume": "50 ml",
"model": "dropper"
},
"progress": { "done": 12, "total": 12 },
"viewer": { "url": "https://kreator.auralab360.pl/podglad-3d/q3Xn8LpV0aT2bR6sKe1WmY9cHd4fJg7u" },
"outputs": [
{
"name": "hero",
"kind": "shot",
"label": "Butelka z kartonikiem",
"status": "ready",
"contentType": "image/jpeg",
"width": 2000,
"height": 2000,
"bytes": 280732,
"url": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/outputs/hero?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA",
"downloadUrl": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/outputs/hero?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA&download=1",
"error": null
}
],
"archive": {
"url": "https://kreator.auralab360.pl/api/v1/renders/rnd_7Gk2LmQ9TxWb3Hc5Rv1Z/archive.zip?expires=1792142540&signature=Jm7fQ2v9xKpL0aZr8sYbWc3nT5dE1gHu6iOkRqVwXyA"
},
"error": null,
"warnings": [],
"metadata": { "sku": "ZH-50" }
}
}// Node.js 18+ i Express: treść musi zostać surowa (express.raw), podpis liczymy z bajtów, które przyszły
import crypto from 'node:crypto';
import express from 'express';
const SECRET = process.env.AURA_WEBHOOK_SECRET; // whsec_… – od AuraLab360, razem z kluczem API
const app = express();
/** Podpis z nagłówka X-Aura-Signature: t=<unix>,v1=<hex> – HMAC-SHA256 z "<t>.<surowa treść>". */
function verify(header, body) {
const parts = Object.fromEntries(String(header || '').split(',').map((p) => p.trim().split('=', 2)));
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300 || !parts.v1) return false;
const expected = crypto.createHmac('sha256', SECRET).update(t + '.' + body.toString('utf8')).digest('hex');
return expected.length === parts.v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
app.post('/aura/webhook', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(req.get('X-Aura-Signature'), req.body)) return res.status(400).send('Zły podpis');
const event = JSON.parse(req.body.toString('utf8'));
// X-Aura-Delivery jest ten sam przy ponowieniu – zapisz go i pomiń dostawę, którą już obsłużyłeś
if (event.type === 'render.succeeded') {
// event.data.archive.url – ZIP ze wszystkimi wynikami; event.data.metadata – Twoje dane ze zlecenia
}
res.sendStatus(200); // odpowiedz w ciągu 10 s – dłuższą pracę zrób w tle
});
app.listen(3000);<?php
// PHP 7.4+: surowa treść z php://input, porównanie w stałym czasie (hash_equals)
$secret = getenv('AURA_WEBHOOK_SECRET'); // whsec_…
$body = file_get_contents('php://input');
$parts = [];
foreach (explode(',', $_SERVER['HTTP_X_AURA_SIGNATURE'] ?? '') as $p) {
$kv = array_map('trim', explode('=', $p, 2));
if (count($kv) === 2) $parts[$kv[0]] = $kv[1];
}
$t = isset($parts['t']) ? (int) $parts['t'] : 0;
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
if ($t === 0 || abs(time() - $t) > 300 || !isset($parts['v1']) || !hash_equals($expected, $parts['v1'])) {
http_response_code(400);
exit('Zły podpis');
}
$event = json_decode($body, true);
$delivery = $_SERVER['HTTP_X_AURA_DELIVERY'] ?? ''; // ten sam przy ponowieniu – pomiń duplikat
if ($event['type'] === 'render.succeeded') {
// $event['data']['archive']['url'] – ZIP ze wszystkimi wynikami
}
http_response_code(200);# Python 3.9+ i Flask: request.get_data() – surowe bajty treści
import hashlib, hmac, os, time
from flask import Flask, abort, request
SECRET = os.environ["AURA_WEBHOOK_SECRET"].encode() # whsec_…
app = Flask(__name__)
def verify(header: str, body: bytes) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
try:
t = int(parts.get("t", ""))
except ValueError:
return False
if abs(time.time() - t) > 300 or "v1" not in parts:
return False
expected = hmac.new(SECRET, f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
@app.post("/aura/webhook")
def aura_webhook():
if not verify(request.headers.get("X-Aura-Signature", ""), request.get_data()):
abort(400)
event = request.get_json()
# request.headers["X-Aura-Delivery"] – ten sam przy ponowieniu: pomiń duplikat
if event["type"] == "render.succeeded":
pass # event["data"]["archive"]["url"] – ZIP ze wszystkimi wynikami
return "", 200Błędy
Każdy błąd ma ten sam kształt: statusCode, code (stały – do obsługi w kodzie), message (po polsku – do pokazania użytkownikowi), details (pola z problemem) i requestId (podaj go, pisząc do nas).
{
"statusCode": 400,
"code": "OWN_ART_SIZE",
"message": "Plik ma inny format niż wykrojnik etykiety – strona ze spadem powinna mieć 126 × 61 mm. Strona pliku: 126 × 126 mm. Pobierz wykrojnik i zaprojektuj na nim (z tłem do krawędzi strony). Plik może też mieć sam wymiar etykiety, bez spadu: 120 × 55 mm – spad dopełnimy.",
"details": {
"page": [126, 61],
"trim": [120, 55]
},
"requestId": "req-2f"
}{
"statusCode": 422,
"code": "UNKNOWN_OUTPUT",
"message": "Popraw zaznaczone pola.",
"details": {
"outputs": "Niedostępne wyniki: composition. Dostępne: color, front, left, back, trio, open, detail, dark, preview. Grafika „Skład” wymaga tabeli składu (composition) albo produktu z recepturą."
},
"requestId": "req-30"
}| Kod | HTTP | Kiedy |
|---|---|---|
API_KEY_MISSING | 401 | Brak nagłówka Authorization: Bearer … (albo X-API-Key) – także przy pobieraniu pliku bez podpisanego adresu. |
API_KEY_INVALID | 401 | Nieznany, wyłączony albo unieważniony klucz. |
RATE_LIMITED | 429 | Za dużo zapytań (klucza albo z adresu IP) – nagłówek Retry-After. |
QUOTA_EXCEEDED | 429 | Wykorzystany miesięczny limit renderów klucza – details: used, quota, resetsAt. |
QUEUE_FULL | 429 | Klucz ma już 50 renderów w kolejce albo w trakcie liczenia. |
UPLOAD_LIMIT | 429 | Jednym kluczem wysłano w ostatniej godzinie ponad 2 GB plików – nagłówek Retry-After. |
VALIDATION_FAILED | 422 | Błędne pole – details: nazwa pola → opis (np. brak packaging i product, zły JSON w composition). |
UNKNOWN_PACKAGING | 422 | Nie ma takiego opakowania – lista: GET /packagings. |
UNKNOWN_PRODUCT | 422 | Nie ma takiego produktu (identyfikatora ani SKU). |
PRODUCT_NOT_RENDERABLE | 422 | Produkt w opakowaniu bez wykrojnika – nie da się go narysować przez API. |
LABEL_REQUIRED | 422 | Brak pliku etykiety (pole label). |
BOX_REQUIRED | 422 | Blister: brak pliku kartonika (pole box). |
NO_LABEL_SHEET | 422 | Opakowanie bez etykiety (blister) albo bez wykrojnika etykiety. |
NO_BOX_SHEET | 422 | Opakowanie bez kartonika (np. słoik HDPE). |
UNKNOWN_CAP | 422 | Zakrętki nie ma na liście caps opakowania. |
UNKNOWN_CONTENTS | 422 | Zawartości nie ma na liście contents.options opakowania. |
CONTENTS_NOT_ALLOWED | 422 | contents razem z product – produkt ma zawartość z receptury. |
NAME_REQUIRED | 422 | Grafika „Skład” z własnej tabeli (composition) bez nazwy produktu (name) – to tytuł grafiki. |
INVALID_WEBHOOK_URL | 422 | webhookUrl nie jest publicznym adresem https z nazwą domeny albo klucz nie ma sekretu webhooków. |
UNKNOWN_OUTPUT | 422 | Wynik niedostępny dla tego opakowania (np. hero bez kartonika, composition bez tabeli składu). |
SIZE_NOT_ALLOWED | 422 | Rozmiar ponad limit klucza (klucz próbny – 1200 px). |
OWN_ART_SIZE | 400 | Strona pliku nie pasuje do wykrojnika – w komunikacie i w details oczekiwane wymiary (page, trim). |
OWN_ART_DPI | 400 | Obraz ma mniej niż 150 dpi w rozmiarze arkusza. |
OWN_ART_EMPTY | 400 | Plik jest pusty albo to sam wykrojnik, bez projektu. |
OWN_ART_TYPE | 400 | Plik nie jest PDF-em, PNG ani JPG-iem (sprawdzamy zawartość, nie rozszerzenie). |
OWN_ART_PX | 400 | Obraz ma więcej niż 20 000 px na dłuższym boku. |
INVALID_IMAGE | 400 | Uszkodzony albo niekompletny plik PNG. |
PDF_UNREADABLE | 400 | Nie udało się odczytać PDF-a – zapisz go ponownie (np. PDF/X). |
FILE_TOO_LARGE | 400 | Plik większy niż 25 MB. |
FILE_DOWNLOAD | 400 | Nie udało się pobrać pliku spod podanego adresu (odpowiedź inna niż 200, za duży, za długo, adres wewnętrzny). |
TOO_MANY_FILES | 400 | Więcej niż dwa pliki w jednym zleceniu. |
UNKNOWN_FILE_FIELD | 400 | Plik w polu innym niż label i box. |
DUPLICATE_FILE | 400 | Ten sam arkusz dwa razy (np. plik label i adres labelUrl). |
PAYLOAD_TOO_LARGE | 413 | Treść JSON ponad 32 MB – duże pliki wysyłaj jako multipart albo adres URL. |
PACKAGING_NOT_FOUND | 404 | GET /packagings/{id} – nie ma takiego opakowania. |
RENDER_NOT_FOUND | 404 | Nie ma takiego renderu albo należy do innego klucza. |
OUTPUT_NOT_FOUND | 404 | Wynik nie jest (jeszcze albo już) dostępny. |
LINK_EXPIRED | 403 | Podpisany adres wygasł albo jest zmieniony (i nie ma klucza API właściciela renderu) – pobierz render ponownie po nowe linki. |
STORAGE_FULL | 507 | Chwilowy brak miejsca na serwerze – spróbuj za kilka minut. |
W renderze nieudanym error.code to RENDER_FAILED; błędy pojedynczych wyników są w outputs[].error. Na błędy 5xx i zerwane połączenia odpowiadaj ponowieniem z tym samym Idempotency-Key.
Przykłady integracji
Zlecenie z plikiem etykiety i kartonika, czekanie na wynik i zapis ZIP-u – bez dodatkowych bibliotek (PHP: cURL, Python: requests).
// Node.js 18+ (fetch, FormData i Blob wbudowane) – zlecenie, czekanie na wynik, ZIP na dysk
import { readFile, writeFile } from 'node:fs/promises';
const API = 'https://kreator.auralab360.pl/api/v1';
const auth = { Authorization: 'Bearer ' + process.env.AURA_API_KEY };
const file = async (path) => new Blob([await readFile(path)], { type: 'application/pdf' });
const form = new FormData();
form.append('packaging', 'dropper-50');
form.append('name', 'Zielona herbata');
form.append('cap', 'zlota');
form.append('label', await file('etykieta.pdf'), 'etykieta.pdf');
form.append('box', await file('kartonik.pdf'), 'kartonik.pdf');
form.append('metadata', JSON.stringify({ sku: 'ZH-50' }));
let res = await fetch(API + '/renders', { method: 'POST', headers: { ...auth, 'Idempotency-Key': 'zh-50-etykieta-v1' }, body: form });
let render = await res.json();
if (!res.ok) throw new Error(render.code + ': ' + render.message);
console.log('Podgląd 3D:', render.viewer.url);
const deadline = Date.now() + 15 * 60_000;
while ((render.status === 'queued' || render.status === 'processing') && Date.now() < deadline) {
res = await fetch(API + '/renders/' + render.id + '?wait=30', { headers: auth });
render = await res.json();
}
if (render.status !== 'succeeded') throw new Error(render.error ? render.error.message : 'Render: ' + render.status);
const zip = await fetch(render.archive.url); // podpisany adres – bez klucza
await writeFile('packshoty-' + render.id + '.zip', Buffer.from(await zip.arrayBuffer()));
for (const o of render.outputs) console.log(o.name, o.status, o.url || o.error);<?php
// PHP 7.4+ z rozszerzeniem cURL – zlecenie, czekanie na wynik, ZIP na dysk
const API = 'https://kreator.auralab360.pl/api/v1';
$key = getenv('AURA_API_KEY');
function aura(string $method, string $url, string $key, $body = null, array $headers = []): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 90,
CURLOPT_HTTPHEADER => array_merge(['Authorization: Bearer ' . $key, 'Accept: application/json'], $headers),
]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, $body); // tablica z CURLFile = multipart/form-data
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode((string) $raw, true);
if ($status >= 400 || !is_array($data)) throw new RuntimeException(($data['code'] ?? $status) . ': ' . ($data['message'] ?? $raw));
return $data;
}
$render = aura('POST', API . '/renders', $key, [
'packaging' => 'dropper-50',
'name' => 'Zielona herbata',
'cap' => 'zlota',
'label' => new CURLFile('etykieta.pdf', 'application/pdf'),
'box' => new CURLFile('kartonik.pdf', 'application/pdf'),
'metadata' => json_encode(['sku' => 'ZH-50']),
], ['Idempotency-Key: zh-50-etykieta-v1']);
echo 'Podgląd 3D: ' . $render['viewer']['url'] . PHP_EOL;
$deadline = time() + 900;
while (in_array($render['status'], ['queued', 'processing'], true) && time() < $deadline) {
$render = aura('GET', API . '/renders/' . $render['id'] . '?wait=30', $key);
}
if ($render['status'] !== 'succeeded') throw new RuntimeException($render['error']['message'] ?? $render['status']);
// podpisany adres – bez klucza
$zip = fopen('packshoty-' . $render['id'] . '.zip', 'wb');
$ch = curl_init($render['archive']['url']);
curl_setopt_array($ch, [CURLOPT_FILE => $zip, CURLOPT_FAILONERROR => true, CURLOPT_TIMEOUT => 120]);
curl_exec($ch);
curl_close($ch);
fclose($zip);# Python 3.9+ i requests – zlecenie, czekanie na wynik, ZIP na dysk
import json, os, time
import requests
API = "https://kreator.auralab360.pl/api/v1"
auth = {"Authorization": "Bearer " + os.environ["AURA_API_KEY"]}
with open("etykieta.pdf", "rb") as label, open("kartonik.pdf", "rb") as box:
r = requests.post(
f"{API}/renders",
headers={**auth, "Idempotency-Key": "zh-50-etykieta-v1"},
data={"packaging": "dropper-50", "name": "Zielona herbata", "cap": "zlota", "metadata": json.dumps({"sku": "ZH-50"})},
files={"label": ("etykieta.pdf", label, "application/pdf"), "box": ("kartonik.pdf", box, "application/pdf")},
timeout=120,
)
render = r.json()
if r.status_code >= 400:
raise RuntimeError(f"{render['code']}: {render['message']}")
print("Podgląd 3D:", render["viewer"]["url"])
deadline = time.time() + 900
while render["status"] in ("queued", "processing") and time.time() < deadline:
render = requests.get(f"{API}/renders/{render['id']}", params={"wait": 30}, headers=auth, timeout=60).json()
if render["status"] != "succeeded":
raise RuntimeError((render.get("error") or {}).get("message") or render["status"])
zip_file = requests.get(render["archive"]["url"], timeout=120) # podpisany adres – bez klucza
zip_file.raise_for_status()
with open(f"packshoty-{render['id']}.zip", "wb") as f:
f.write(zip_file.content)OpenAPI i wersje
- Pełny opis API w formacie OpenAPI 3.1: https://kreator.auralab360.pl/api/v1/openapi.json – do importu w Postmanie lub Insomni (Import → adres) albo do wygenerowania klienta (np. openapi-generator).
- Wersja w adresie:
/api/v1. W jej ramach zmiany są tylko dodające – nowe pola, opakowania, ujęcia i kody błędów – więc ignoruj pola, których nie znasz. Zmianę niezgodną wstecz wprowadzimy jako/api/v2i zapowiemy z wyprzedzeniem. - Nowe opakowania pojawiają się w
GET /packagingssame, gdy dodamy je do katalogu.
Chcesz podłączyć API?
Napisz, do czego potrzebujesz podglądów i packshotów – przygotujemy klucz (najpierw próbny) i pomożemy w integracji.