MENU navbar-image

Introduction

API des aaronflash-Dashboards. Quelle der Wahrheit für api/openapi.yaml und das generierte TypeScript-SDK.

Alle Endpoints liegen unter `/api/v1` und antworten in JSON. Authentifiziert
wird über die Sanctum-SPA-Session: erst `GET /sanctum/csrf-cookie`, dann
`POST /api/v1/auth/login` — danach tragen Session-Cookie und `X-XSRF-TOKEN`
die Anmeldung. Brand-scoped Endpoints erwarten zusätzlich den Header
`X-Brand-Slug` (`dkd` oder `happyhug`).

<aside>Diese Doku wird aus dem Laravel-Code generiert (`make api`) — nicht von Hand pflegen.</aside>

Authenticating requests

To authenticate requests, include a Cookie header with the value "aaronflash_session={SESSION_COOKIE}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Session-Cookie aus dem Login (POST /api/v1/auth/login nach GET /sanctum/csrf-cookie). Das generierte SDK erledigt das über credentials: include.

Auth

Anmeldung des Dashboards über die Sanctum-SPA-Session: erst GET /sanctum/csrf-cookie, dann Login — danach trägt das Session-Cookie die Authentifizierung.

Login

Meldet mit E-Mail und Passwort an und startet die Session. Vorher muss der Client GET /sanctum/csrf-cookie geholt und den X-XSRF-TOKEN mitgeschickt haben.

Example request:
curl --request POST \
    "http://localhost:8000/api/v1/auth/login" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"gbailey@example.net\",
    \"password\": \"|]|{+-\"
}"
const url = new URL(
    "http://localhost:8000/api/v1/auth/login"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "gbailey@example.net",
    "password": "|]|{+-"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "user": {
        "id": 1,
        "name": "Aaron",
        "email": "aaron@example.com"
    }
}
 

Example response (422):


{
    "message": "Diese Zugangsdaten passen zu keinem Konto.",
    "errors": {
        "email": [
            "Diese Zugangsdaten passen zu keinem Konto."
        ]
    }
}
 

Request      

POST api/v1/auth/login

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

Must be a valid email address. Example: gbailey@example.net

password   string     

Example: |]|{+-

Response

Response Fields

user   object     

Die angemeldete Nutzerin.

id   integer     
name   string     
email   string     

Logout

requires authentication

Beendet die Session und macht das Cookie ungültig.

Example request:
curl --request POST \
    "http://localhost:8000/api/v1/auth/logout" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/auth/logout"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (204):

Empty response
 

Request      

POST api/v1/auth/logout

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Current user

requires authentication

Die angemeldete Nutzerin der laufenden Session.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/auth/me" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/auth/me"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "user": {
        "id": 1,
        "name": "Aaron",
        "email": "aaron@example.com"
    }
}
 

Request      

GET api/v1/auth/me

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

user   object     

Die angemeldete Nutzerin.

id   integer     
name   string     
email   string     

Brain

Der Wissens-Store einer Marke, read-only fürs Dashboard: die Einträge, aus denen Flashy seine Entwürfe baut, und der Status des trybe-Brain-Anschlusses. Gepflegt wird der Store über die Seeds — es gibt bewusst keine Schreib-Endpunkte. Alle Routen sind brand-scoped (X-Brand-Slug).

List brain entries

requires authentication

Die Wissens-Einträge der aktiven Marke, sortiert nach Art und Titel. Seitenweise zu 50.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/brain/entries?kind=faq&page=1" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/brain/entries"
);

const params = {
    "kind": "faq",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "kind": "faq",
            "title": "Ab wann sind die Tropfen geeignet?",
            "body": "Ab dem ersten Lebenstag — die Dosierung steht auf der Packung.",
            "source": "happyhug/desk-faq.md"
        }
    ],
    "links": {
        "first": "https://aaronflash.test/api/v1/brain/entries?page=1",
        "last": "https://aaronflash.test/api/v1/brain/entries?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "&laquo; Zurück",
                "active": false
            }
        ],
        "path": "https://aaronflash.test/api/v1/brain/entries",
        "per_page": 50,
        "to": 1,
        "total": 1
    }
}
 

Request      

GET api/v1/brain/entries

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

kind   string  optional    

Nur Einträge dieser Art (product, fact, faq, study oder service). Example: faq

page   integer  optional    

Die Seite (50 Einträge je Seite). Example: 1

Response

Response Fields

data   object[]     
id   integer     
kind   string     

product, fact, faq, study oder service.

title   string     
body   string     

Der Wissens-Text.

source   string     

Herkunft des Eintrags (Seed-Datei).

Get brain status

requires authentication

Woher das Wissen der aktiven Marke kommt: wie viele lokale Einträge es gibt, welcher trybe-Brain-Treiber läuft und ob das trybe Brain für diese Marke gerade antwortet. trybeConnected=false heißt: die Marke arbeitet rein mit dem lokalen Store — kein Fehler, der Normalfall für DKD.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/brain/status" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/brain/status"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "entryCount": 42,
        "trybeDriver": "off",
        "trybeConnected": false
    }
}
 

Request      

GET api/v1/brain/status

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
entryCount   integer     

Anzahl der lokalen Wissens-Einträge.

trybeDriver   string     

Konfigurierter Treiber (off, fake oder live).

trybeConnected   boolean     

Ob das trybe Brain für diese Marke konfiguriert ist und antwortet.

Brands

Die Marken des Dashboards. Der Brand-Switcher lädt sie einmal; der slug wandert danach als X-Brand-Slug in jede brand-scoped Anfrage.

List brands

requires authentication

Alle Marken, zwischen denen das Dashboard umschalten kann.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/brands" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/brands"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "slug": "dkd",
            "name": "DeinKinderdoc"
        },
        {
            "id": 2,
            "slug": "happyhug",
            "name": "happyhug"
        }
    ]
}
 

Request      

GET api/v1/brands

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object[]     
id   integer     
slug   string     

Stabiler Schlüssel, Wert für X-Brand-Slug.

name   string     

Anzeigename.

Flashy

KI-Entwürfe für den Posteingang: erzeugen, regenerieren, übernehmen. Die KI erzeugt Entwürfe, ein Mensch sendet — Kein Auto-Send (VISION §9). Alle Routen sind brand-scoped (X-Brand-Slug).

Generate draft

requires authentication

Erzeugt einen Antwort-Entwurf für den Thread — aus Tonprofil und Wissens-Store der Marke (plus trybe Brain, wenn an). Ein erneuter Aufruf regeneriert: neuer Entwurf mit generation+1, die älteren bleiben stehen. Mit Treiber anthropic läuft das NUR, wenn die Marke llm_enabled=true hat UND ein API-Key gesetzt ist — sonst 409, und kein Nachrichteninhalt verlässt das System (VISION §9).

Example request:
curl --request POST \
    "http://localhost:8000/api/v1/conversations/3/drafts" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/conversations/3/drafts"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 1,
        "conversationId": 3,
        "body": "Danke dir für deine Nachricht!\n\nMelde dich gern, wenn noch etwas offen ist.",
        "model": "fake",
        "generation": 1,
        "acceptedAt": null,
        "createdAt": "2026-09-08T14:30:00+02:00"
    }
}
 

Example response (404, Fremde Marke oder unbekannte Id):


{
    "message": "Diesen Thread gibt es hier nicht."
}
 

Example response (409, Treiber anthropic, aber llm_enabled=false):


{
    "message": "KI-Entwürfe über Anthropic sind für diese Marke ausgeschaltet. Solange das so ist, verlässt kein Nachrichteninhalt das System. Einschalten geht erst, wenn die Rechtsgrundlage geklärt ist (Art. 9 DSGVO) — bis dahin steht dir der Fake-Treiber zur Verfügung.",
    "reason": "llm_disabled"
}
 

Example response (502, Anthropic nicht erreichbar):


{
    "message": "Anthropic hat nicht geantwortet — der Entwurf wurde nicht erzeugt. Versuch es gleich noch einmal.",
    "reason": "driver_failed"
}
 

Request      

POST api/v1/conversations/{id}/drafts

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

Die Id des Threads. Example: 3

Response

Response Fields

data   object     

Der neue Entwurf.

id   integer     
conversationId   integer     
body   string     

Der Entwurfstext.

model   string     

Erzeugendes Modell (fake oder der Anthropic-Modellname).

generation   integer     

1 beim ersten Entwurf, +1 je Regeneration.

acceptedAt   string     

Immer null — übernommen wird über den Accept-Endpunkt.

createdAt   string     

Accept draft

requires authentication

Markiert den Entwurf als übernommen (acceptedAt). Es wird NICHTS gesendet — der Text wandert in den Composer, und der Mensch sendet über den Inbox-Endpunkt (Kein Auto-Send, VISION §9). Ein zweites Übernehmen ändert den Zeitstempel nicht.

Example request:
curl --request POST \
    "http://localhost:8000/api/v1/drafts/1/accept" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/drafts/1/accept"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 1,
        "conversationId": 3,
        "body": "Danke dir für deine Nachricht!\n\nMelde dich gern, wenn noch etwas offen ist.",
        "model": "fake",
        "generation": 1,
        "acceptedAt": "2026-09-08T14:31:00+02:00",
        "createdAt": "2026-09-08T14:30:00+02:00"
    }
}
 

Example response (404, Fremde Marke oder unbekannte Id):


{
    "message": "Diesen Entwurf gibt es hier nicht."
}
 

Request      

POST api/v1/drafts/{id}/accept

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

Die Id des Entwurfs. Example: 1

Response

Response Fields

data   object     

Der Entwurf mit gesetztem acceptedAt.

Get flashy settings

requires authentication

Der Stand der Marke: ist der LLM-Zugriff frei (llmEnabled), und welcher Entwurfs-Treiber läuft (draftDriver). Solange llmEnabled=false, verlässt kein Nachrichteninhalt das System — Entwürfe kommen dann nur vom Fake-Treiber.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/flashy/settings" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/flashy/settings"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "llmEnabled": false,
        "draftDriver": "fake"
    }
}
 

Request      

GET api/v1/flashy/settings

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
llmEnabled   boolean     

Der DSGVO-Art.-9-Schalter der Marke; Default false.

draftDriver   string     

Konfigurierter Entwurfs-Treiber (fake oder anthropic).

Get tone profile

requires authentication

Das Tonprofil der aktiven Marke — Prosa-Stimme, Beispiel-Formulierungen und verbotene Formulierungen.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/flashy/tone-profile" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/flashy/tone-profile"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 1,
        "voice": "Warm, auf Augenhöhe, per Du. Fachbegriffe werden erklärt statt vorausgesetzt.",
        "beispiele": [
            "Danke dir für deine Nachricht!"
        ],
        "verboteneFormulierungen": [
            "Sehr geehrte Damen und Herren"
        ],
        "updatedAt": "2026-09-08T10:00:00+02:00"
    }
}
 

Example response (404, Noch kein Profil):


{
    "message": "Für diese Marke ist noch kein Tonprofil angelegt."
}
 

Request      

GET api/v1/flashy/tone-profile

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
id   integer     
voice   string     

Prosa-Beschreibung der Marken-Stimme.

beispiele   string[]     

Beispiel-Formulierungen.

verboteneFormulierungen   string[]     

Formulierungen, die Entwürfe meiden.

updatedAt   string     

Letzte Änderung (ISO 8601).

Update tone profile

requires authentication

Ersetzt das Tonprofil der aktiven Marke komplett (PUT). Gibt es noch keines, wird es angelegt. Wirkt ab dem nächsten Entwurf — bestehende Entwürfe bleiben, wie sie sind.

Example request:
curl --request PUT \
    "http://localhost:8000/api/v1/flashy/tone-profile" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"voice\": \"Warm, auf Augenhöhe, per Du. Fachbegriffe werden erklärt statt vorausgesetzt.\",
    \"beispiele\": [
        \"Danke dir für deine Nachricht!\"
    ],
    \"verbotene_formulierungen\": [
        \"Sehr geehrte Damen und Herren\"
    ]
}"
const url = new URL(
    "http://localhost:8000/api/v1/flashy/tone-profile"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "voice": "Warm, auf Augenhöhe, per Du. Fachbegriffe werden erklärt statt vorausgesetzt.",
    "beispiele": [
        "Danke dir für deine Nachricht!"
    ],
    "verbotene_formulierungen": [
        "Sehr geehrte Damen und Herren"
    ]
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 1,
        "voice": "Warm, auf Augenhöhe, per Du. Fachbegriffe werden erklärt statt vorausgesetzt.",
        "beispiele": [
            "Danke dir für deine Nachricht!"
        ],
        "verboteneFormulierungen": [
            "Sehr geehrte Damen und Herren"
        ],
        "updatedAt": "2026-09-08T10:05:00+02:00"
    }
}
 

Request      

PUT api/v1/flashy/tone-profile

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

voice   string     

Prosa-Beschreibung der Marken-Stimme. Example: Warm, auf Augenhöhe, per Du. Fachbegriffe werden erklärt statt vorausgesetzt.

beispiele   string[]  optional    

Eine Beispiel-Formulierung.

verbotene_formulierungen   string[]  optional    

Eine verbotene Formulierung.

Response

Response Fields

data   object     

Das gespeicherte Profil.

Inbox

Der Posteingang einer Marke: Threadliste, Thread mit Kontakt und Verlauf, Zuweisen und Erledigen. Alle Routen sind brand-scoped (X-Brand-Slug).

List conversations

requires authentication

Die Threadliste, sortiert nach window_expires_at aufsteigend — was zuerst abläuft, steht oben (VISION §4). Threads ohne Fenster (noch keine eingehende Nachricht) kommen ans Ende. Seitenweise zu 50.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/conversations?unanswered=1&status=open&assignee_id=1&page=1" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/conversations"
);

const params = {
    "unanswered": "1",
    "status": "open",
    "assignee_id": "1",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 3,
            "status": "open",
            "unanswered": true,
            "assigneeId": null,
            "windowExpiresAt": "2026-09-06T09:12:00+02:00",
            "lastMessageAt": "2026-09-05T09:12:00+02:00",
            "contact": {
                "id": 3,
                "igUserId": "8900000000000003",
                "username": "papa.jonas",
                "followerCount": 87,
                "notes": null
            },
            "lastMessage": {
                "id": 3,
                "direction": "in",
                "kind": "text",
                "text": "Danke für den Tipp mit dem Vernebler, hat super geholfen!",
                "sentAt": "2026-09-05T09:12:00+02:00",
                "attachments": null
            }
        }
    ],
    "links": {
        "first": "https://aaronflash.test/api/v1/conversations?page=1",
        "last": "https://aaronflash.test/api/v1/conversations?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "&laquo; Zurück",
                "active": false
            }
        ],
        "path": "https://aaronflash.test/api/v1/conversations",
        "per_page": 50,
        "to": 1,
        "total": 1
    }
}
 

Request      

GET api/v1/conversations

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

unanswered   boolean  optional    

Nur unbeantwortete (1) oder nur beantwortete (0) Threads. Example: true

status   string  optional    

Nur Threads mit diesem Status (open oder done). Example: open

assignee_id   integer  optional    

Nur Threads dieser Nutzerin. Example: 1

page   integer  optional    

Die Seite (50 Threads je Seite). Example: 1

Response

Response Fields

data   object[]     

Threads mit Kontakt und jüngster Nachricht als Vorschau.

id   integer     
status   string     

open oder done.

unanswered   boolean     

Wahr, wenn die jüngste Nachricht vom Kontakt kam.

assigneeId   integer     

Zugewiesene Nutzerin, sonst null.

windowExpiresAt   string     

Ende des 24-h-Antwortfensters (ISO 8601), null ohne eingehende Nachricht.

lastMessageAt   string     

Zeitpunkt der jüngsten Nachricht (ISO 8601).

contact   object     

Der Kontakt hinter dem Thread.

lastMessage   object     

Die jüngste Nachricht als Vorschau.

Get conversation

requires authentication

Ein Thread mit Kontakt und komplettem Verlauf, älteste Nachricht zuerst.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/conversations/3" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/conversations/3"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 3,
        "status": "open",
        "unanswered": true,
        "assigneeId": null,
        "windowExpiresAt": "2026-09-06T09:12:00+02:00",
        "lastMessageAt": "2026-09-05T09:12:00+02:00",
        "contact": {
            "id": 3,
            "igUserId": "8900000000000003",
            "username": "papa.jonas",
            "followerCount": 87,
            "notes": null
        },
        "messages": [
            {
                "id": 3,
                "direction": "in",
                "kind": "text",
                "text": "Danke für den Tipp mit dem Vernebler, hat super geholfen!",
                "sentAt": "2026-09-05T09:12:00+02:00",
                "attachments": null
            }
        ]
    }
}
 

Example response (404, Fremde Marke oder unbekannte Id):


{
    "message": "Diesen Thread gibt es hier nicht."
}
 

Request      

GET api/v1/conversations/{id}

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

Die Id des Threads. Example: 3

Response

Response Fields

data   object     
id   integer     
status   string     

open oder done.

unanswered   boolean     
assigneeId   integer     

Zugewiesene Nutzerin, sonst null.

windowExpiresAt   string     

Ende des 24-h-Antwortfensters (ISO 8601).

lastMessageAt   string     
contact   object     
messages   object[]     

Der Verlauf, älteste zuerst.

Update conversation

requires authentication

Zuweisen und Erledigen: assignee_id setzt oder löst die Zuweisung (null = niemand), status schaltet zwischen open und done.

Example request:
curl --request PATCH \
    "http://localhost:8000/api/v1/conversations/3" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"status\": \"done\",
    \"assignee_id\": 1
}"
const url = new URL(
    "http://localhost:8000/api/v1/conversations/3"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "status": "done",
    "assignee_id": 1
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 3,
        "status": "done",
        "unanswered": false,
        "assigneeId": 1,
        "windowExpiresAt": "2026-09-06T09:12:00+02:00",
        "lastMessageAt": "2026-09-05T09:12:00+02:00",
        "contact": {
            "id": 3,
            "igUserId": "8900000000000003",
            "username": "papa.jonas",
            "followerCount": 87,
            "notes": null
        }
    }
}
 

Example response (404, Fremde Marke oder unbekannte Id):


{
    "message": "Diesen Thread gibt es hier nicht."
}
 

Request      

PATCH api/v1/conversations/{id}

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

Die Id des Threads. Example: 3

Body Parameters

status   string  optional    

Neuer Status des Threads (open oder done). Example: done

Must be one of:
  • open
  • done
assignee_id   integer  optional    

Zuweisung an diese Nutzerin — null löst die Zuweisung. Must match an existing stored value. Example: 1

Response

Response Fields

data   object     

Der Thread nach der Änderung, mit Kontakt.

Guess conversation context

requires authentication

Mögliche Bezüge der ERSTEN eingehenden Nachricht des Threads: Stories, die zu diesem Zeitpunkt liefen, und die jüngsten Beiträge davor. Leer, wenn das Konto nicht verbunden ist oder Meta nichts liefert.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/conversations/3/context-guess" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/conversations/3/context-guess"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "type": "story",
            "label": "Story von 06:00",
            "permalink": "https://www.instagram.com/stories/fake/1",
            "postedAt": "2026-09-09T06:00:00+02:00"
        },
        {
            "type": "media",
            "label": "Fieber bei Kindern: wann senken, wann abwarten?",
            "permalink": "https://www.instagram.com/reel/fake1/",
            "postedAt": "2026-09-09T05:00:00+02:00"
        }
    ]
}
 

Example response (404, Fremde Marke oder unbekannte Id):


{
    "message": "Diesen Thread gibt es hier nicht."
}
 

Request      

GET api/v1/conversations/{id}/context-guess

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

Die Id des Threads. Example: 3

Response

Response Fields

data   object[]     

Kandidaten, wahrscheinlichste zuerst.

type   string     

story oder media.

label   string     

Anzeigetext (Caption-Auszug bzw. Story + Uhrzeit).

permalink   string     

Link zum Inhalt auf Instagram, sonst null.

postedAt   string     

Veröffentlichungszeitpunkt (ISO 8601).

Send message

requires authentication

Sendet eine Text-Antwort in den Thread — über die Graph API, im Namen des verbundenen Kontos. Das 24-h-Fenster ist bewusst KEIN Guard vorm Senden (VISION §4): ob es offen ist, entscheidet Meta, und Metas Ablehnung kommt als 422 mit reason und Klartext zurück.

Example request:
curl --request POST \
    "http://localhost:8000/api/v1/conversations/3/messages" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"text\": \"Gern! Melde dich jederzeit.\"
}"
const url = new URL(
    "http://localhost:8000/api/v1/conversations/3/messages"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "text": "Gern! Melde dich jederzeit."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 9,
        "direction": "out",
        "kind": "text",
        "text": "Gern! Melde dich jederzeit.",
        "attachments": null,
        "sentAt": "2026-09-08T14:30:00+02:00"
    }
}
 

Example response (404, Fremde Marke oder unbekannte Id):


{
    "message": "Diesen Thread gibt es hier nicht."
}
 

Example response (422, Meta lehnt ab (z. B. Fenster abgelaufen)):


{
    "message": "Fake wie Meta (#10): Das 24-Stunden-Fenster ist abgelaufen — diese Nachricht wird nicht mehr zugestellt.",
    "reason": "outside_window"
}
 

Request      

POST api/v1/conversations/{id}/messages

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

Die Id des Threads. Example: 3

Body Parameters

text   string     

Die Antwort, die an den Kontakt geht (max. 1000 Zeichen). Must not be greater than 1000 characters. Example: Gern! Melde dich jederzeit.

Response

Response Fields

data   object     

Die gesendete Nachricht, wie sie im Verlauf steht.

id   integer     
direction   string     

Immer out.

kind   string     

Immer text.

text   string     
sentAt   string     

Instagram

Das Instagram-Konto einer Marke: Verbinden über den Instagram-Login (OAuth), Status einsehen, Trennen. Genau ein Konto je Marke.

Rückweg von Instagram

Meta schickt den Browser hierher — ohne Sitzung, ohne Marken-Header: die Marke steht im state, sonst nirgends. Die Antwort ist immer eine Weiterleitung ins Dashboard (?connected=1|0&reason=…) — ein Mensch vor dem Browser soll kein JSON sehen, auch nicht bei einem Fehler.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/instagram/callback?code=AQD3f0a9c1a&state=eyJpdiI6%E2%80%A6&error=access_denied" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/instagram/callback"
);

const params = {
    "code": "AQD3f0a9c1a",
    "state": "eyJpdiI6…",
    "error": "access_denied",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (302, Verbunden):



 

Example response (302):

Show headers
cache-control: no-cache, private
location: http://localhost:3000/einstellungen?connected=0&reason=invalid_state
content-type: text/html; charset=utf-8
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
access-control-allow-origin: http://localhost:3000
access-control-allow-credentials: true
 

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="refresh" content="0;url='http://localhost:3000/einstellungen?connected=0&amp;reason=invalid_state'" />

        <title>Redirecting to http://localhost:3000/einstellungen?connected=0&amp;reason=invalid_state</title>
    </head>
    <body>
        Redirecting to <a href="http://localhost:3000/einstellungen?connected=0&amp;reason=invalid_state">http://localhost:3000/einstellungen?connected=0&amp;reason=invalid_state</a>.
    </body>
</html>
 

Request      

GET api/v1/instagram/callback

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

code   string  optional    

Der Code von Meta. Example: AQD3f0a9c1a

state   string  optional    

Die verschlüsselte Mitgift aus dem Authorize-Schritt. Example: eyJpdiI6…

error   string  optional    

Steht hier etwas, hat der Mensch abgelehnt. Example: access_denied

Instagram-Login starten

requires authentication

Der Server leitet NICHT weiter: er gibt die Zustimmungs-Adresse zurück, und das Dashboard wechselt das Fenster. Der state bindet Marke und Mensch an den Rückweg.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/instagram/authorize" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/instagram/authorize"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "authorizeUrl": "https://www.instagram.com/oauth/authorize?client_id=1&redirect_uri=…&state=…"
    }
}
 

Example response (409, Keine Meta-App hinterlegt):


{
    "message": "Für Instagram ist noch keine Meta-App hinterlegt."
}
 

Request      

GET api/v1/instagram/authorize

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
authorizeUrl   string     

Die Dialog-Adresse für das Browser-Fenster.

Verbundenes Konto

requires authentication

Das Instagram-Konto der Marke mit Token-Ablauf und Webhook-Status — der Access-Token selbst bleibt im Backend.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/v1/instagram/account" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/instagram/account"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 1,
        "igUserId": "17841400000000001",
        "username": "deinkinderdoc",
        "tokenExpiresAt": "2026-11-07T10:00:00+01:00",
        "connectedAt": "2026-09-08T10:00:00+02:00",
        "webhookActive": false,
        "lastError": null
    }
}
 

Example response (404, Nicht verbunden):


{
    "message": "Für diese Marke ist kein Instagram-Konto verbunden."
}
 

Request      

GET api/v1/instagram/account

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
id   integer     
igUserId   string     

Id des professionellen Kontos bei Meta.

username   string     
tokenExpiresAt   string     

Wann der langlebige Token abläuft (ISO 8601).

connectedAt   string     
webhookActive   boolean     
lastError   string     

Letzter Fehler des Token-Refreshs, sonst null.

Konto trennen

requires authentication

Löscht Verbindung und Token der Marke. Neu verbinden geht jederzeit über den Instagram-Login.

Example request:
curl --request DELETE \
    "http://localhost:8000/api/v1/instagram/account" \
    --header "Cookie: aaronflash_session={SESSION_COOKIE}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/v1/instagram/account"
);

const headers = {
    "Cookie": "aaronflash_session={SESSION_COOKIE}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204, Getrennt):

Empty response
 

Request      

DELETE api/v1/instagram/account

Headers

Cookie        

Example: aaronflash_session={SESSION_COOKIE}

Content-Type        

Example: application/json

Accept        

Example: application/json