Image Generation API · Für Entwickler

Eine API, die aussieht wie erwartet.

Template plus Modifications — dasselbe Muster, das Sie von Bannerbear kennen. Die Antwort ist synchron, das Bild liegt nach rund 0,3 Sekunden bereit, und abgerechnet wird pro Datensatz statt pro Bild: Jedes weitere Format kostet nichts.

Das Wichtigste in Sätzen

  • Ein POST auf /v1/images mit Vorlage und Modifications gibt synchron die URL der fertigen Grafik zurück.
  • Abgerechnet wird pro Datensatz, nicht pro Bild — zwanzig Formate je Datensatz kosten so viel wie eines.
  • Preis, Fläche und Ort werden als echter Text gesetzt, nicht von einem Bildmodell gemalt; eine Ziffer kann darum nicht abweichen.
  • Passt der Text nicht, wird in einer festgelegten Reihenfolge reduziert, und die Antwort sagt, welche Stufe griff.
  • Der Bildausschnitt folgt dem Motiv: photo_focus als „x,y“ zwischen 0 und 1, oder auto, dann bestimmt ein Bildmodell den Punkt einmal je Foto. Beim Auslesen, im Import und im Bestand geschieht das von selbst.
  • Ihr Logo ist ein Bildfeld wie das Foto: logo im Datensatz, gesetzt ans Ende der Absenderzeile, nie beschnitten, in jedem Format mit Platz dafür.
  • Vier Schriften stehen zur Wahl — font im Stil: inter, spectral, poppins, barlow-condensed. Ohne Angabe bleibt es bei Inter.
  • Jede Grafik gibt es in drei Behältern: .png, .jpg und .webp unter derselben Adresse, umgewandelt beim ersten Abruf und danach eine Datei.
  • Eine Adresse wird nie gedruckt, aber befragt: address und postcode im Datensatz ergeben aus OpenStreetMap das Feld nearby — „Bahnhof 400 m · Supermarkt 200 m“ —, einmal je Adresse, ohne Aufpreis. Steht nearby schon im Datensatz, bleibt Ihre Zeile.
  • Jede Grafik gibt es als PNG zum Hochladen und als SVG mit Schrift in Pfaden, für Druck und jede Größe.
  • Ein Porträt kommt als runder Ausschnitt neben die Absenderzeile — Feld agent_photo, der Zuschnitt sitzt auf dem Gesicht, wenn portrait_focus es kennt oder der Bestand es bestimmt hat.
  • Das OG-Image für die Link-Vorschau ist ein Format wie jedes andere: 1200×630, synchron, mit Preis als Text — og-wide.
  • Ein Export aus Shopify, Shopware oder WooCommerce wird an seinen Spalten erkannt; POST /v1/imports/plan sagt vor dem ersten Cent, welche Spalte welche Rolle bekommt.
  • Zapier, Make und n8n brauchen keine eigene App: der HTTP-Baustein an /v1/ingest genügt. Welche Anbindungen heute laufen und welche geplant sind.
  • Zwei Formulare sind eingebaut: form: "product" setzt ein Produkt mit Marke, Variante und Kategorie, form: "property" ein Objekt mit Zimmern und Fläche — kein Produkt sieht je „Zimmer“.
  • Eine signierte Adresse rendert ohne Schlüssel: die Felder stehen in der URL, eine HMAC-Signatur mit Ihrem Geheimnis macht sie gültig, der zweite Abruf ist eine Datei.
  • Der Vertrag steht als OpenAPI 3.1 unter /openapi.json, erzeugt aus denselben Schemas, die jede Anfrage prüfen.

Ein Aufruf

# Objektdaten rein, Bild raus
curl -X POST https://api.listing-shot.com/v1/images \
  -H "Authorization: Bearer ls_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "ig-post",
    "modifications": [
      { "name": "title",       "text": "Altbauwohnung mit Balkon" },
      { "name": "price",       "text": "€ 685.000" },
      { "name": "location",    "text": "Leipzig-Südvorstadt" },
      { "name": "photo",       "image_url": "https://…/objekt.jpg" },
      { "name": "brand_color", "color": "#0B5D4E" }
    ]
  }'

# → { "status": "completed", "image_url": "/images/…png",
#     "render_ms": 312, "usage": { "images_this_month": 12 } }

Dasselbe für ein Produkt, mit dem eingebauten Produktformular statt der festen Felder:

curl -X POST https://api.listing-shot.com/v1/images \
  -H "Authorization: Bearer ls_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "template": "ig-post", "form": "product",
        "values": { "title": "Wanderschuh Alpin Pro GTX", "price": "199,95 €",
                    "brand": "Nordwerk", "variant": "Gr. 38–47",
                    "photo": "https://…/schuh.jpg" },
        "style": { "brand_color": "#0B5D4E" } }'

Endpunkte

MethodePfadWofür
POST/v1/imagesEine Grafik erzeugen. Synchron, Antwort enthält die Bild-URL.
POST/v1/ingestExposé-Text, PDF oder Foto einlesen und die Felder erkennen.
POST/v1/importsMassenimport aus CSV oder einer Objektliste, über eine Warteschlange.
POST/v1/feedsEinen Bestand anschließen: die Adresse einer CSV, im Intervall geprüft; nur geänderte Zeilen werden neu gesetzt.
GET/v1/feeds/:id/changesWas jede Prüfung tat — neu, geändert, entfernt, unverändert. Dasselbe Dokument geht signiert an Ihren Webhook.
POST/v1/feeds/:id/approveFreigabe: Ein Bestand mit approval „manual“ rendert Änderungen, zeigt sie unter /pending, und erst die Freigabe bewegt die ausgelieferte Grafik.
PUT/v1/feeds/:id/rows/:key/overridesWerte je Zeile: ein Badge, ein Handlungsaufruf, ein korrigierter Titel, der über die Quelle gewinnt — bei jedem Rendern, bis er gelöscht wird.
POST/v1/form-linksEin Formular ohne Anmeldung anlegen: ein Link, der die Felder zeigt und die Grafiken liefert. Tageslimit je Link, jederzeit schließbar.
GET/b/:feed/:key/:format.pngDie feste Adresse: die neueste Grafik einer Zeile in einem Format, ohne Schlüssel, mit ETag. Für Kataloge, CMS und Newsletter.
GET/s/:account/:format.pngBild-URL mit Signatur: die Felder in der Adresse, HMAC-signiert mit Ihrem Geheimnis, ohne Schlüssel abrufbar; beim ersten Abruf gerendert, danach eine Datei.
GET/images/:uid.(png|jpg|webp)Dieselbe Grafik in drei Behältern. Beim ersten Abruf umgewandelt, danach eine Datei; WebP spart rund zwei Drittel der Bytes.
GET/v1/forms/builtinDie zwei eingebauten Formulare, Produkt und Immobilie, mit ihren Feldern — als form_id oder form überall dort verwendbar, wo ein gespeichertes Formular gilt.
GET/openapi.jsonDie API als OpenAPI 3.1, erzeugt aus dem Vertrag, öffentlich — für Client-Generatoren und Postman.
GET/v1/templatesDie 20 Formate mit Maßen und Feldern.
POST/v1/arrangementsDieselben Angaben in jeder Anordnung, gemessen und sortiert.
POST/v1/styles/deriveFarben, Formensprache und Anordnung aus einem Referenzbild lesen — etwa Ihrem Canva-Design. Die Antwort trägt den gelesenen Stil und seine Nachbarn zur Auswahl: helles Panel, andere Ecken, Versalien, zweite Akzentfarbe.
GET/v1/accountGuthaben und Verbrauch.

Was Sie herunterladen

Jede Grafik entsteht zweimal: als Rasterbild und als Vektor. Nicht als Konvertierung des einen ins andere — beide kommen aus demselben Satz, mit denselben Maßen und derselben Zeile Text an derselben Stelle.

DateiWofürGröße, gemessen
.png Zum Hochladen bei Instagram, Facebook, WhatsApp. Verlustfrei, überall akzeptiert. 1,1 MB · 1080×1080
.svg Für Druck und jede Größe. Die Schrift liegt als Pfad vor, also scharf, ohne dass eine Schriftart installiert sein muss. Der Grund, warum ein A4-Aushang als Format überhaupt Sinn ergibt. 308 KB · beliebig skalierbar

Die Antwort auf /v1/images nennt beide: image_url für das PNG, svg_url für den Vektor.

Ein Aufruf je Format, ein Datensatz für alle

Jeder POST /v1/images nennt eine Vorlage; der Datensatz ist derselbe. Die Antwort trägt uid, image_url, svg_url, width, height und render_ms — das SVG mit Schrift in Pfaden, für Druck und jede Größe.

Instagram Post des Sneakers, 1080 mal 1080 Pixel Instagram Post · 1080×1080 — Sitzt gut.
Instagram Story des Sneakers, 1080 mal 1920 Pixel Instagram Story · 1080×1920 — Sitzt gut.
Facebook / Link Preview des Sneakers, 1200 mal 630 Pixel Facebook / Link Preview · 1200×630 — Sitzt gut.
Instagram Hochformat des Sneakers, 1080 mal 1350 Pixel Instagram Hochformat · 1080×1350 — Sitzt gut.
Pinterest des Sneakers, 1000 mal 1500 Pixel Pinterest · 1000×1500 — Sitzt gut.
Anzeige 1,91:1 des Sneakers, 1200 mal 628 Pixel Anzeige 1,91:1 · 1200×628 — Sitzt gut.
Anzeige 1:1 des Sneakers, 1200 mal 1200 Pixel Anzeige 1:1 · 1200×1200 — Sitzt gut.
Anzeige 4:5 des Sneakers, 960 mal 1200 Pixel Anzeige 4:5 · 960×1200 — Sitzt gut.
Aushang A4 des Sneakers, 1240 mal 1754 Pixel Aushang A4 · 1240×1754 — Sitzt gut.
Bildschirm 16:9 des Sneakers, 1920 mal 1080 Pixel Bildschirm 16:9 · 1920×1080 — Sitzt gut.
20 Formate aus einem Datensatz. Ein Datensatz, 20 Zuschnitte, jeder einzeln gesetzt und gemessen — kein Format ist ein skaliertes anderes. Hier die 10 großen, für Feed, Story, Pinterest, Aushang und Bildschirm. Erzeugt mit pnpm site-proofs.
Display-Anzeige des Sneakers, 300 mal 250 Pixel Display-Anzeige · 300×250 — Sitzt gut.
Display-Anzeige groß des Sneakers, 336 mal 280 Pixel Display-Anzeige groß · 336×280 — Sitzt gut.
Leaderboard des Sneakers, 728 mal 90 Pixel Leaderboard · 728×90 — Sitzt gut.
Leaderboard breit des Sneakers, 970 mal 90 Pixel Leaderboard breit · 970×90 — Sitzt gut.
Billboard des Sneakers, 970 mal 250 Pixel Billboard · 970×250 — Sitzt gut.
Skyscraper des Sneakers, 160 mal 600 Pixel Skyscraper · 160×600 — Sitzt gut.
Halbseite des Sneakers, 300 mal 600 Pixel Halbseite · 300×600 — Sitzt gut.
Mobilanzeige des Sneakers, 320 mal 50 Pixel Mobilanzeige · 320×50 — Sitzt gut.
Mobilanzeige hoch des Sneakers, 320 mal 100 Pixel Mobilanzeige hoch · 320×100 — Sitzt gut.
E-Mail-Kopf des Sneakers, 600 mal 200 Pixel E-Mail-Kopf · 600×200 — Sitzt gut.
Dazu 10 Anzeigenformate, in Originalgröße. Display-Anzeige, Leaderboard, Skyscraper und E-Mail-Kopf, jedes 1:1 so groß wie es ausgespielt wird. Jedes ist eine eigene Komposition, die bewusst Felder weglässt — ein Streifen von 90 Pixeln Höhe trägt keinen Handlungsaufruf, und tut nicht so. Erzeugt mit pnpm site-proofs.

Abrechnung

Die Einheit ist der Datensatz — ein Objekt, ein Produkt, eine Zeile Ihrer CSV —, nicht die Datei. Ein Datensatz braucht zwanzig Formate; wer pro Bild abrechnet, macht das vierte davon zu einer Entscheidung — und die fällt regelmäßig gegen das Format aus. Was das in Zahlen bedeutet, steht im Beitrag Was kostet ein Bild?

  • 20 ct je Datensatz aus Exposé, Produkttext, PDF oder Link
  • 4 ct je Datensatz aus strukturierten Daten (CSV, Shop-Export, API)
  • kostenlos jede Grafik, in jedem Format, beliebig oft
  • 40 ct je Ableitung aus einem Referenzbild (/v1/styles/derive)
  • 50,00 € im Monat je angeschlossenem Bestand (/v1/feeds), bis 500 Datensätze; je weitere angefangene 1.000 Datensätze 20,00 €
  • Kein Abo fürs Erzeugen, keine Grundgebühr, Guthaben verfällt nicht

logo nimmt eine Bild-Adresse oder ein Bild als Data-URL, wie das Foto. Gezeichnet wird es am rechten Ende der Absenderzeile: unbeschnitten, in seinem eigenen Seitenverhältnis, in einer Box, die es nie verzerrt. Dort und nirgends sonst, weil die Fußzeile die eine Stelle ist, deren Hintergrund jede Anordnung kennt — ein Logo in einer Fotoecke verschwindet beim dritten Objekt. Die schmalen Anzeigenstreifen lassen es weg, wie sie das Porträt weglassen.

Anordnung als Wahl

style.arrangement nennt eine der drei Anordnungen und gilt dann für jedes Format, dessen Leinwand sie verträgt: full-bleed (Foto randlos, Text unten darauf), stacked (Foto als Band, darunter eine Fläche mit Knopf) und split (Textspalte neben dem Foto). Ohne Angabe behält jedes Format die Anordnung, mit der es gebaut wurde. Eine schmale Anzeige behält ihre ohnehin: ein Streifen von 90 Pixeln Höhe verträgt genau eine, und eine Wahl, die dort nicht passt, wird still ignoriert statt den Lauf abzubrechen.

POST /v1/styles/derive liest die Anordnung aus einem Referenzbild mit und schreibt sie in den Stil, den es zurückgibt. Was ein Kunde übernimmt, ist damit der ganze Look: Farben, Schrift und Anordnung.

Eigene Anordnung, als Beschreibung

style.layout nimmt eine Beschreibung statt einer der drei Anordnungen: wo das Foto sitzt (full, band-top, column-left, column-right, inset für das Produkt in der Mitte mit Text darüber und darunter, none), wie viel es nimmt (photo_share zwischen 0,3 und 0,75), worauf der Text steht (text_on), wo er in seiner Fläche sitzt (text_at: oben, mittig, unten oder über die ganze Höhe verteilt), ob er linksbündig oder mittig steht, wo das Badge sitzt, ob die Fakten neben oder unter dem Preis stehen, ob ein Knopf und eine Absenderzeile gezeichnet werden, ob die Textfläche neutral oder in der Markenfarbe gefüllt ist (panel: ink oder brand), ob Überschrift und Preis ausgewogen sind oder eines von beiden dominiert (emphasis: balanced, price, headline), und ein Maßfaktor zwischen 0,8 und 1,25. Auf einer Fläche in Markenfarbe wechseln Preis, Knopf und Schrift auf die Farbe, die darauf lesbar ist — weiß auf Petrol, dunkel auf Gelb.

Das ist kein Editor und soll keiner werden. Was in der Beschreibung nicht vorkommt, lässt sich nicht bauen: kein zweites Foto, keine frei gesetzte Fläche, kein QR-Code. Dafür kann jede Beschreibung, die wir annehmen, in jedem großen Format gezeichnet werden, wird wie jede andere Anordnung gemessen, und was nicht passt, wird in der festgelegten Reihenfolge reduziert und gemeldet. Eine Beschreibung, die wir nicht zeichnen können — Text auf einem Foto, das es gar nicht gibt — wird abgelehnt statt stillschweigend in etwas anderes verwandelt.

# Textspalte links, Foto rechts, Fakten unter dem Preis
{ "template": "ig-post",
  "data": { … },
  "style": {
    "layout": {
      "photo": "column-right", "photo_share": 0.53,
      "text_on": "panel", "text_at": "top",
      "badge_at": "text", "facts": "below-price",
      "cta": false, "footer": true
    } } }

Die Beschreibung gilt für jedes Format, dessen Leinwand sie verträgt, und schlägt eine gewählte Anordnung, weil sie die genauere Aussage über dasselbe ist. Die schmalen Anzeigenformate behalten auch hier ihre eigene Zeichnung. Eine Seitenspalte passt nur bis zum Hochformat 4:5 — in einer Story wäre sie ein schmaler Streifen mit viel Leere darunter. Dort steht das Foto deshalb oben statt daneben, und alles andere aus der Beschreibung gilt weiter: Farbe des Textfelds, Gewichtung, Knopf, Lage des Textes. POST /v1/layouts/derive nimmt mit templates bis zu sechs Formate und zeigt die Beschreibung in allen zugleich.

Eine Anordnung aus einem Satz

POST /v1/layouts/derive nimmt einen Satz auf Deutsch und antwortet mit einer geprüften Beschreibung, einer Vorschau Ihres eigenen Datensatzes darin und einer Liste dessen, was der Satz verlangt hat und diese Sprache nicht ausdrücken kann. Diese Liste ist der Punkt der ganzen Sache: Ein Editor in Satzform, der stillschweigend weglässt, was er nicht kann, ist eine Maschine zum Enttäuschen. Fragt jemand nach einem Logo oben links, einem zweiten Foto oder einem QR-Code, steht genau das in der Antwort, und zwar auch dann, wenn das Modell es vergisst: Diese Prüfung ist mechanisch, nicht im Prompt.

POST https://api.listing-shot.com/v1/layouts/derive

{ "description": "Foto rechts, links eine Fläche in Markenfarbe, Preis groß, Fakten darunter",
  "templates": ["ig-post", "ig-story", "og-wide"],
  "data": { "title": "Halden Low — Off-White", "price": "179,00 €" } }

# → { "layout": { "photo": "column-right", "panel": "brand", … },
#     "unmet": [], "note": "Textspalte in Markenfarbe links neben dem Foto.",
#     "previews": [ { "template": "ig-post", "image_url": "/images/….png" }, … ] }

# Dasselbe für ein Exposé — die Beschreibung kennt keine Branche:
{ "description": "Foto oben als Band, Text darunter mittig, mit Knopf",
  "data": { "title": "Altbauwohnung mit Fischgrätparkett", "price": "685.000 €" } }

Kostet 20 ct wie jede andere Entscheidung, die ein Modell trifft; die Vorschau ist eine Grafik und damit kostenlos. Die Beschreibung, die zurückkommt, ist dieselbe, die Sie unter style.layout dauerhaft hinterlegen — der Satz ist nur ein bequemerer Weg zu ihr.

Nachschärfen in Worten

Wer eine Beschreibung hat, muss nicht von vorn anfangen. Schicken Sie sie mit — als layout oder als layout_id —, und der nächste Satz ändert nur, was er verlangt: „Preis größer“, „Foto lieber links“, „noch einen Knopf dazu“. Die Antwort nennt in changed jede Achse, die sich bewegt hat, mit altem und neuem Wert und einem Satz für Menschen in said — „Preis größer“, „Foto jetzt links“ —, den Sie Ihren Nutzern so anzeigen können. Diese Liste berechnen wir selbst aus dem Vergleich der beiden Beschreibungen; sie hängt nicht davon ab, dass das Modell ehrlich berichtet. Was sich nicht ausdrücken lässt, ändert nichts und steht in unmet.

POST https://api.listing-shot.com/v1/layouts/derive

{ "description": "Preis größer",
  "layout_id": "lay_9f2c4a1b7e03" }

# → { "layout": { …, "emphasis": "price" },
#     "changed": [{ "axis": "emphasis", "from": "balanced", "to": "price",
#                   "said": "Preis größer" }],
#     "unmet": [] }

Eine Anordnung aus einem Bild

Dieselbe Adresse nimmt statt des Satzes auch files: eine Grafik, die Ihnen gefällt, oder eine Skizze auf Papier, dann zusätzlich sketch: true. Gelesen wird nur die Anordnung — wo das Foto sitzt, wie viel es nimmt, worauf der Text steht, wo Badge und Fakten stehen. Farben und Schrift kommen aus Ihrer Marke und bleiben unberührt; dafür gibt es /v1/styles/derive. Die Antwort sagt in read_from, was sie gelesen hat.

Gemessen, bevor wir es anbieten: Ein Skript im Repository zeichnet sechzehn bekannte Beschreibungen, gibt die Bilder zurück und vergleicht Achse für Achse. Achsen, die das Bild gar nicht beantwortet — zwei Werte, ein Bild — werden nicht mitgezählt, sondern ausgewiesen. Aus unseren eigenen Grafiken kommen Fotoplatzierung, Fotoanteil, Ausrichtung und Textfläche vollständig zurück, Badge und Fakten fast immer; die ganze Beschreibung stimmte in zwölf von sechzehn Fällen.

Bei Skizzen ist die Lage ehrlicherweise eine andere, und wir nennen deshalb keine Zahl. Gemessen haben wir bisher an maschinell gezeichneten Drahtgittern, nicht an Papier — und ein Drahtgitter ist ordentlicher als jede Skizze, die jemand tatsächlich zeichnet. Was sich dabei gezeigt hat: Die Struktur kommt zurück, wo Foto und Text sitzen und wie breit sie sind; die kleineren Entscheidungen, Badge und Fakten, oft nicht. Eine Skizze ist damit ein guter Anfang und keine fertige Vorlage — im Bildschirm „Anordnung“ stehen die Listen deshalb direkt neben der Vorschau, und zwei Klicks später stimmt es.

POST https://api.listing-shot.com/v1/layouts/derive

{ "files": [{ "media_type": "image/png", "data": "iVBORw0…" }],
  "sketch": true }

# → { "layout": { "photo": "band-top", "text_on": "panel", … },
#     "read_from": "sketch", "unmet": ["QR-Code"],
#     "preview": { "image_url": "/images/….png" } }

Eine Anordnung unter einem Namen

POST /v1/layouts legt eine Beschreibung unter einem Namen ab und gibt eine layout_id zurück. Danach genügt diese Nummer überall dort, wo auch eine form_id steht: beim einzelnen Bild, beim Import einer Datei, beim Bestand und beim Formularlink. Das ist dieselbe Trennung wie beim Formular: Ein Satz schlägt etwas vor, gespeichert wird erst, wenn Sie es gesehen haben — zweimal derselbe Satz muss nicht zweimal dasselbe ergeben, und genau deshalb braucht eine vereinbarte Anordnung eine Nummer.

POST https://api.listing-shot.com/v1/layouts

{ "name": "Spalte rechts",
  "layout": { "photo": "column-right", "text_on": "panel", … } }

# → { "id": "lay_9f2c4a1b7e03", "version": 1, … }

POST /v1/images  { "template": "ig-post", "values": { … },
                     "layout_id": "lay_9f2c4a1b7e03" }

Beim einzelnen Bild wird die Nummer bei jeder Anfrage neu gelesen — ändern Sie die Anordnung, ändert sich das nächste Bild. Bestand und Formularlink lesen sie einmal und behalten, was sie bekommen haben: Beide laufen monatelang ohne Zuschauer, und eine Änderung am Layout darf nicht unbemerkt hunderte Grafiken neu zeichnen, die längst auf einem Portal stehen. Soll ein laufender Bestand anders aussehen, ist das ein PATCH und damit eine Entscheidung.

Vier Schriften

style.font wählt die Familie für alle Formate zugleich: inter (neutrale Grotesk, die Vorgabe), spectral (Serif mit Kontrast), poppins (geometrisch, rund) und barlow-condensed (schmal, bringt lange Titel unter). Alle vier stehen unter der Open Font License, und Inter läuft immer mit: ein Zeichen, das die gewählte Familie nicht hat, wird daraus gesetzt statt zum leeren Kästchen. Eine eigene Schrift zum Hochladen gibt es nicht — wir liefern SVG mit Schrift in Pfaden, und das deckt kaum eine gekaufte Lizenz.

# Produkt, Logo, Serif — alles in einem Aufruf
POST https://api.listing-shot.com/v1/images

{ "template": "ig-post", "form": "product",
  "values": { "title": "Halden Low — Off-White", "price": "179,00 €",
              "brand": "Nordwerk", "photo": "https://…/schuh.jpg",
              "logo": "https://…/nordwerk-logo.png" },
  "style": { "font": "spectral", "brand_color": "#0F856E" } }

PNG, JPEG, WebP

Jede gespeicherte Grafik liegt unter drei Endungen: /images/<uid>.png, .jpg und .webp. Dasselbe gilt für die feste Adresse einer Bestandszeile und für die signierte Adresse. Umgewandelt wird beim ersten Abruf, aus dem PNG, und danach ist es eine Datei — das Rendern, die Abrechnung und das Änderungsdokument bleiben davon unberührt. JPEG bekommt einen weißen Grund, weil es keine Transparenz kennt. Dazu wie bisher das SVG mit Schrift in Pfaden.

Zwei Formulare eingebaut

Ein Formular sagt, welche Felder ein Datensatz hat und welche Rolle jedes auf der Grafik spielt. Zwei sind eingebaut und gelten für jedes Konto: property mit Ort, Zimmern, Fläche, Absender und Maklerfoto, und product mit Marke, Variante, Kategorie, Shop und Shop-Adresse. Beide sind überall gültig, wo ein gespeichertes Formular gilt — form_id: "product" oder form: "product" bei /v1/images, /v1/ingest, /v1/imports, /v1/feeds und den Formular-Links. GET /v1/forms/builtin zeigt beide mit ihren Feldern. Eigene Formulare speichern Sie über POST /v1/forms.

Die signierte Adresse

GET /s/<konto>/<format>.png?…&sig=… rendert die Felder in der Adresse, ohne Bearer-Schlüssel. <konto> ist die öffentliche Kennung Ihres Kontos, sig eine HMAC-SHA256-Signatur mit Ihrem Signier-Geheimnis; beide zeigt GET /v1/account, und POST /v1/account/signing-secret dreht das Geheimnis, wonach jede alte Adresse ungültig ist. Der erste Abruf rendert und zählt eine Grafik, jeder weitere liefert die Datei mit demselben ETag.

# Was signiert wird: drei Zeilen — Konto, Format, die Felder ohne sig,
# nach Namen sortiert, jedes als encodeURIComponent(name)=encodeURIComponent(wert), mit & verbunden.
nachricht = konto + "\n" + format + "\n" + "price=199%2C95%20%E2%82%AC&title=Wanderschuh"
sig       = hex( HMAC_SHA256( geheimnis, nachricht ) )

# Die Adresse: Felder in derselben Reihenfolge, sig zuletzt.
https://listing-shot.com/s/acc_…/og-wide.png?price=199%2C95%20%E2%82%AC&title=Wanderschuh&sig=…

# form=product wählt das Produktformular, brand_color=#… die Farbe; beide gehören in die Signatur.

OpenAPI

/openapi.json ist der Vertrag als OpenAPI 3.1 — erzeugt aus den Schemas, mit denen die API jede Anfrage prüft, nie von Hand geschrieben. Jede Route unter /v1 steht darin; fehlt eine, scheitert unser eigener Test.

Was die API nicht tut

Sie erzeugt keine Fotos. Das Bild kommt von Ihnen oder aus Ihrem Datensatz; wir setzen es zusammen mit den Angaben. Und sie erzeugt keine Videos — dafür gibt es Anbieter in dieser Kategorie, wir sind keiner davon. Warum die Trennung zwischen erzeugtem Bild und gesetztem Text die tragende ist, steht im Beitrag KI schreibt gute Anzeigen. Die Zahlen setzt der Datensatz.

Häufige Fragen

Wie funktioniert die ListingShot-API?

Ein POST auf /v1/images mit einer Vorlagen-Kennung und einer Liste von Modifications — je Feld ein Name und ein Wert. Die Antwort kommt synchron und enthält die URL des fertigen Bildes. Das ist dasselbe Muster, das Bannerbear und ähnliche Dienste verwenden; ein Wechsel ist ein Nachmittag, kein Projekt.

Wird pro Bild oder pro Datensatz abgerechnet?

Pro Datensatz — ein Objekt, ein Produkt, eine Zeile Ihrer CSV. Ein ausgelesener Datensatz aus Exposé, Produkttext, PDF oder Link kostet 20 ct, ein strukturierter aus CSV, Shop-Export oder API 4 ct. Jede Grafik ist kostenlos, in allen zwanzig Formaten und beliebig oft. Wer pro Bild abrechnet, macht jedes weitere Format zu einer Kaufentscheidung — bei uns ist es ein Häkchen.

Wie schnell antwortet die API?

Eine einzelne Grafik ist in etwa 0,3 Sekunden fertig — gemessen zwischen 0,18 und 0,36 Sekunden über achtzehn Grafiken. Es fällt keine Bilderzeugung und keine GPU an: die Grafik wird gesetzt, nicht generiert. Eine vollständige Kampagne aus drei Formaten ist typischerweise in unter zwei Sekunden fertig.

Was passiert bei einem zu langen Titel?

Die Grafik wird in einer festgelegten Reihenfolge reduziert: erst das unwichtigste Detail weggelassen, dann die Details verkleinert bis zu einer Untergrenze, zuletzt die Überschrift gekürzt. Die Antwort sagt, welche Stufe gegriffen hat. Kein Text läuft aus dem Bild, und keine Grafik wird stillschweigend unbrauchbar.

Gibt es einen Massenimport?

Ja. Ein POST auf /v1/imports nimmt eine CSV oder eine Liste von Objekten entgegen und arbeitet sie über eine Warteschlange ab; der Status ist über /v1/imports/{id} abrufbar. Vierhundert Zeilen sind ein Aufruf, kein Skript mit vierhundert Aufrufen.

In welchen Dateiformaten bekomme ich die Grafiken?

Als PNG und als SVG, aus demselben Satz erzeugt statt ineinander umgerechnet. Das PNG laden Sie bei Instagram oder Facebook hoch — ein Post mit 1080×1080 wiegt rund 1,1 MB. Das SVG trägt die Schrift als Pfade, ist damit in jeder Größe scharf und braucht keine installierte Schriftart; deshalb ergibt der A4-Aushang als Format überhaupt Sinn. Die Antwort auf /v1/images nennt beide URLs.

Kann ich meinen Bestand als Feed anschließen?

Ja. POST /v1/feeds nimmt die Adresse einer CSV entgegen, liest sie einmal zur Probe und antwortet mit der Spaltenzuordnung, der erkannten Schlüsselspalte und der ersten Zeile. Danach prüft ein Worker die Adresse im gewählten Abstand zwischen 15 Minuten und einer Woche, mit If-None-Match, sodass ein unverändertes „304" nichts kostet. Jede Zeile wird an ihrer Schlüsselspalte erkannt; nur neue und geänderte Zeilen werden neu gesetzt, verschwundene als entfernt geführt. GET /v1/feeds/:id/changes listet jede Prüfung; ein optionaler Webhook bekommt dasselbe Dokument mit einer HMAC-Signatur im Stil von Stripe. Preis: 50,00 € im Monat je Feed bis 500 Datensätze, jede weitere angefangene 1.000 20,00 €, vom Guthaben abgebucht; Aktualisierungen sind enthalten.

Kann ich damit OG-Images für Link-Vorschauen erzeugen?

Ja. Das Format og-wide ist 1200×630, die Größe, die Facebook, LinkedIn, WhatsApp und Slack für die Link-Vorschau lesen; ad-landscape liefert dieselbe Szene in 1200×628 für Google. Ein POST auf /v1/images mit Titel, Preis und Foto antwortet synchron mit der Bildadresse, die Sie als og:image hinterlegen. Ändert sich der Preis, setzen Sie das Bild neu und erhalten eine neue Adresse — so behält der Cache der Plattform nicht das alte Bild.

Versteht die API den Export meines Shopsystems?

Ja, ohne eigenes Formular: die Produktdatei aus Shopify, Shopware oder WooCommerce und der Google-Produktfeed werden an ihren Spalten erkannt. POST /v1/imports/plan antwortet mit dem erkannten System, der Rolle jeder Spalte und der ersten Zeile, wie sie gesetzt würde. Ein Shopify-Produkt mit mehreren Varianten- und Bildzeilen ergibt einen Datensatz, und der Vergleichspreis steht Mustern als {compare_price} zur Verfügung.

Gibt es eine OpenAPI-Spezifikation?

Ja, unter /openapi.json, öffentlich und ohne Schlüssel: OpenAPI 3.1, erzeugt aus den Schemas, die jede Anfrage prüfen. Jede Route unter /v1 steht darin, mit Anfrage- und Antwortschema, wo der Vertrag eines hat. Ein Client-Generator oder Postman liest sie direkt.

Kann ich ein Bild direkt über eine URL erzeugen, ohne Schlüssel?

Ja, über die signierte Adresse: die Felder stehen in der URL, eine HMAC-SHA256-Signatur mit Ihrem Signier-Geheimnis macht sie gültig, und niemand, der die Adresse liest, lernt das Geheimnis. Der erste Abruf rendert, jeder weitere liefert die Datei. Das Geheimnis zeigt GET /v1/account, und POST /v1/account/signing-secret dreht es.

Gibt es ein Formular für Produkte, ohne dass ich eines anlegen muss?

Ja. Zwei Formulare sind eingebaut: product mit Marke, Variante, Kategorie, Shop und Shop-Adresse, property mit Ort, Zimmern, Fläche und Absender. form_id: "product" oder form: "product" gilt bei jedem Aufruf, der ein Formular nimmt, und GET /v1/forms/builtin zeigt beide mit ihren Feldern.

Wie erhalte ich einen API-Schlüssel?

Über das Zugangsformular. Sie erhalten einen Schlüssel und 5,00 € Startguthaben — das sind 25 ausgelesene Datensätze; fürs Erzeugen gibt es keine Grundgebühr, ein Abonnement nur für einen angeschlossenen Bestand. Die API ist derzeit für angemeldete Konten freigeschaltet.

API-Key anfragen Für Immobilien → Für Produkte →