Ticket Transcript API
Deine Bots laden Ticket-Transcripts hoch, gebunden an die jeweilige Discord-Guild. Nutzer und Teammitglieder melden sich per Discord an und sehen genau die Transcripts, für die sie berechtigt sind. Die maschinenlesbare Spezifikation gibt es unter /api/openapi.json (OpenAPI 3.1 – importierbar in Postman, Insomnia, Bruno).
Schnellstart
- API-Key erstellen (Admin):
curl -X POST "$API/api/admin/keys" \ -H "Authorization: Bearer $ADMIN_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "Support-Bot", "guildIds": ["1100000000000000001"] }'Die Antwort enthältsecret(tt_…) – nur einmal sichtbar. Alternativ:pnpm key:create --name "Support-Bot" --guild 1100000000000000001 - Guild registrieren und Staff-Rollen setzen:
curl -X PUT "$API/api/v1/guilds/1100000000000000001" \ -H "Authorization: Bearer $TT_KEY" -H "Content-Type: application/json" \ -d '{ "name": "Mein Server", "staffRoleIds": ["1500000000000000001"] }' - Transcript hochladen:
curl -X POST "$API/api/v1/transcripts" \ -H "Authorization: Bearer $TT_KEY" \ -H "Content-Type: application/json" \ -d '{ "guildId": "1100000000000000001", "guild": { "name": "Mein Server" }, "externalId": "ticket-42", "ticketNumber": 42, "channel": { "id": "1200000000000000001", "name": "ticket-0042" }, "category": "Support", "status": "CLOSED", "opener": { "id": "1300000000000000001", "username": "max" }, "closedBy": { "id": "1300000000000000002", "username": "supporter" }, "closeReason": "Erledigt", "openedAt": "2026-10-08T12:00:00Z", "closedAt": "2026-10-08T12:30:00Z", "messages": [{ "id": "1400000000000000001", "author": { "id": "1300000000000000001", "username": "max" }, "content": "Hallo, ich brauche Hilfe!", "timestamp": "2026-10-08T12:00:05Z" }] }'Die Antwort enthälttranscript.url– den Link zum Web-Viewer, den dein Bot z.B. in einen Log-Kanal postet.
Authentifizierung
/api/v1/*Authorization: Bearer tt_… (alternativ X-API-Key). Jeder Key ist auf bestimmte Guilds beschränkt (oder allGuilds) und hat Scopes: transcripts:read, transcripts:write, transcripts:delete, guilds:read, guilds:write. Keys werden nur als SHA-256-Hash gespeichert./api/*/api/auth/login). Scopes: identify guilds guilds.members.read. Schreibende Requests müssen von der eigenen Origin kommen (CSRF-Schutz)./api/admin/*Authorization: Bearer $ADMIN_API_KEYZugriffsmodell
managerRoleIds-Rolle. Sieht alles, darf löschen, Einstellungen ändern und das Audit-Log sehen.staffRoleIds-Rolle. Sieht alle Transcripts der Guild.participants hinzugefügte Nutzer (canView: true). Sehen nur „ihre“ Transcripts – auch nach Verlassen des Servers. Abschaltbar per allowParticipantAccess: false.Rollen werden über Discord OAuth2 (/users/@me/guilds/{id}/member) ermittelt und MEMBER_CACHE_TTL_SECONDS lang zwischengespeichert. Für Fremde existieren Transcripts nicht (404 statt 403).
Konventionen
- Alle Discord-IDs sind Strings (Snowflakes), alle Zeitstempel ISO-8601 (UTC).
- Idempotenz:
externalIdist pro Guild eindeutig – erneutesPOST /transcriptsaktualisiert statt zu duplizieren. Nachrichten werden über ihre Discord-ID dedupliziert; Requests dürfen also wiederholt werden. - Limits: 1000 Nachrichten pro Request, Body max.
MAX_BODY_BYTES(Standard 15 MB),Content-Encoding: gzipwird unterstützt. - Pagination: Listen liefern
nextCursor; diesen als?cursor=übergeben, bis ernullist. - Rate-Limits: pro Key
RATE_LIMIT_BOT_PER_MINUTE(Standard 600/min), pro Nutzer 120/min. HeaderX-RateLimit-Limit/Remaining/Reset, bei 429 zusätzlichRetry-After. - Attachments: Discord-CDN-Links laufen ab. Die API lädt Dateien daher im Hintergrund herunter (
ARCHIVE_ATTACHMENTS) und liefert sie über/api/attachments/{id}aus – nur an Berechtigte. - Fehlerformat:
{ "error": { "code": "VALIDATION_ERROR", "message": "Die Anfrage ist ungültig.", "details": [{ "path": "messages.0.id", "code": "invalid_format", "message": "…" }], "requestId": "Xk2…" } }Codes:BAD_REQUEST,VALIDATION_ERROR,UNAUTHORIZED,FORBIDDEN,NOT_FOUND,CONFLICT,PAYLOAD_TOO_LARGE,UNSUPPORTED_MEDIA_TYPE,RATE_LIMITED,DISCORD_ERROR,INTERNAL_ERROR.
Beispiel: discord.js-Integration
import { Collection, type Message, type TextChannel } from "discord.js";
const API = process.env.TRANSCRIPT_API_URL!; // z.B. https://transcripts.example.com
const KEY = process.env.TRANSCRIPT_API_KEY!; // tt_...
async function api(path: string, body: unknown) {
const res = await fetch(API + path, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`Transcript-API ${res.status}: ${await res.text()}`);
return res.json();
}
/** Alle Nachrichten eines Kanals laden (älteste zuerst). */
async function fetchAll(channel: TextChannel): Promise<Message[]> {
const all: Message[] = [];
let before: string | undefined;
for (;;) {
const batch: Collection<string, Message> = await channel.messages.fetch({ limit: 100, before });
if (batch.size === 0) break;
all.push(...batch.values());
before = batch.last()!.id;
}
return all.reverse();
}
const toUser = (u: { id: string; username: string; bot: boolean; displayAvatarURL(): string }, displayName?: string) =>
({ id: u.id, username: u.username, displayName, bot: u.bot, avatarUrl: u.displayAvatarURL() });
const toMessage = (m: Message) => ({
id: m.id,
author: { ...toUser(m.author, m.member?.displayName), color: m.member?.displayColor || null },
content: m.content,
timestamp: m.createdAt.toISOString(),
editedTimestamp: m.editedAt?.toISOString() ?? null,
type: m.type,
pinned: m.pinned,
replyTo: m.reference?.messageId ?? null,
embeds: m.embeds.map((e) => e.toJSON()),
attachments: m.attachments.map((a) => ({
id: a.id, filename: a.name, url: a.url, contentType: a.contentType,
size: a.size, width: a.width, height: a.height, spoiler: a.spoiler,
})),
stickers: m.stickers.map((s) => ({ id: s.id, name: s.name })),
reactions: m.reactions.cache.map((r) => ({
emoji: { id: r.emoji.id, name: r.emoji.name ?? "?", animated: r.emoji.animated ?? false },
count: r.count,
})),
});
export async function saveTranscript(channel: TextChannel, ticket: { id: string; openerId: string; openedAt: Date }, closer: Message["author"], reason?: string) {
const messages = (await fetchAll(channel)).map(toMessage);
const opener = await channel.client.users.fetch(ticket.openerId);
// 1) Transcript mit den ersten 1000 Nachrichten anlegen
const { transcript } = await api("/api/v1/transcripts", {
guildId: channel.guild.id,
guild: { name: channel.guild.name, iconHash: channel.guild.icon },
externalId: ticket.id,
channel: { id: channel.id, name: channel.name },
status: "OPEN",
opener: toUser(opener),
openedAt: ticket.openedAt.toISOString(),
messages: messages.slice(0, 1000),
});
// 2) Restliche Nachrichten in Batches nachschieben
for (let i = 1000; i < messages.length; i += 1000) {
await api(`/api/v1/transcripts/${transcript.id}/messages`, { messages: messages.slice(i, i + 1000) });
}
// 3) Schließen
await api(`/api/v1/transcripts/${transcript.id}/close`, { closedBy: toUser(closer), closeReason: reason });
return transcript.url as string; // z.B. in den Log-Kanal oder per DM an den Nutzer senden
}Bot API
/api/v1/meAPI-Key prüfen
Gibt Informationen über den verwendeten Key zurück (Scopes, Guilds). Gut zum Testen der Verbindung.
Antworten
200Key-Informationen → ApiKeyResponse401Nicht authentifiziert → Error
/api/v1/guilds/{guildId}Guild registrieren / konfigurieren
Legt die Guild an oder aktualisiert sie. Hier werden u.a. die Staff-Rollen gesetzt, deren Mitglieder alle Transcripts sehen dürfen.
Request-Body (JSON Schema)
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"iconHash": {
"anyOf": [
{
"type": "string",
"maxLength": 64
},
{
"type": "null"
}
]
},
"staffRoleIds": {
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
}
},
"managerRoleIds": {
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
}
},
"allowParticipantAccess": {
"type": "boolean"
},
"retentionDays": {
"anyOf": [
{
"type": "integer",
"minimum": 1,
"maximum": 3650
},
{
"type": "null"
}
],
"description": "null = nie automatisch löschen"
}
},
"description": "GuildUpsert"
}Antworten
200Aktualisiert → GuildResponse201Angelegt → GuildResponse400Validierungsfehler → Error403Keine Berechtigung → Error
/api/v1/guilds/{guildId}Guild-Konfiguration abrufen
Antworten
200Guild → GuildResponse404Nicht gefunden → Error
/api/v1/guilds/{guildId}/statsStatistiken einer Guild
Antworten
200Statistiken → GuildStatsResponse404Nicht gefunden → Error
/api/v1/transcriptsTranscript erstellen (Upsert über externalId)
Erstellt ein Transcript inkl. bis zu 1000 Nachrichten. Ist `externalId` gesetzt und existiert bereits, wird das Transcript aktualisiert und neue Nachrichten werden angehängt (idempotent – Duplikate per Message-ID werden übersprungen). Für größere Tickets weitere Nachrichten über `POST /api/v1/transcripts/{id}/messages` nachschieben. Unterstützt `Content-Encoding: gzip`.
Request-Body (JSON Schema)
{
"type": "object",
"properties": {
"guildId": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"guild": {
"description": "Legt die Guild an bzw. aktualisiert Name/Icon",
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"iconHash": {
"anyOf": [
{
"type": "string",
"maxLength": 64
},
{
"type": "null"
}
]
}
},
"required": [
"name"
]
},
"externalId": {
"description": "Ticket-ID in deinem Bot. Gleiche externalId = Upsert (idempotent)",
"type": "string",
"minLength": 1,
"maxLength": 128
},
"ticketNumber": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"channel": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
}
},
"required": [
"id",
"name"
]
},
"category": {
"type": "string",
"maxLength": 100
},
"subject": {
"type": "string",
"maxLength": 500
},
"tags": {
"default": [],
"maxItems": 20,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 50
}
},
"status": {
"default": "CLOSED",
"type": "string",
"enum": [
"OPEN",
"CLOSED"
]
},
"opener": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"username"
],
"description": "DiscordUser"
},
"claimedBy": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"username"
],
"description": "DiscordUser"
},
"closedBy": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"username"
],
"description": "DiscordUser"
},
"closeReason": {
"type": "string",
"maxLength": 2000
},
"openedAt": {
"type": "string",
"format": "date-time"
},
"closedAt": {
"type": "string",
"format": "date-time"
},
"participants": {
"default": [],
"maxItems": 500,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
},
"role": {
"description": "Rolle im Ticket (Standard: aus Kontext abgeleitet)",
"type": "string",
"enum": [
"OPENER",
"STAFF",
"MEMBER",
"BOT"
]
},
"canView": {
"description": "Darf der Nutzer das Transcript im Web-Viewer sehen? (Standard: true)",
"type": "boolean"
}
},
"required": [
"id",
"username"
]
}
},
"resolved": {
"type": "object",
"properties": {
"users": {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"additionalProperties": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 100
}
},
"required": [
"name"
]
}
},
"roles": {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"additionalProperties": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 100
},
"color": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"name"
]
}
},
"channels": {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"additionalProperties": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 100
}
},
"required": [
"name"
]
}
}
},
"description": "Namen für Mentions (<@id>, <@&id>, <#id>) im Nachrichtentext"
},
"metadata": {
"type": "object",
"propertyNames": {
"type": "string",
"maxLength": 64
},
"additionalProperties": {},
"description": "Beliebige zusätzliche Daten deines Bots (max. 16 KB)"
},
"messages": {
"default": [],
"maxItems": 1000,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-Message-ID (dient der Deduplizierung)"
},
"author": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"username"
],
"description": "DiscordUser"
},
"content": {
"default": "",
"type": "string",
"maxLength": 10000
},
"timestamp": {
"description": "Erstellungszeitpunkt (ISO-8601 oder Unix-ms)",
"type": "string",
"format": "date-time"
},
"editedTimestamp": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
]
},
"type": {
"default": 0,
"description": "Discord-Message-Type",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"pinned": {
"default": false,
"type": "boolean"
},
"replyTo": {
"description": "ID der Nachricht, auf die geantwortet wird",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
{
"type": "null"
}
]
},
"embeds": {
"default": [],
"maxItems": 10,
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 256
},
"description": {
"type": "string",
"maxLength": 4096
},
"url": {
"type": "string",
"maxLength": 2048
},
"color": {
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
"timestamp": {
"type": "string",
"maxLength": 64
},
"author": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 256
},
"url": {
"type": "string"
},
"icon_url": {
"type": "string"
}
},
"required": [
"name"
],
"additionalProperties": {}
},
"footer": {
"type": "object",
"properties": {
"text": {
"type": "string",
"maxLength": 2048
},
"icon_url": {
"type": "string"
}
},
"required": [
"text"
],
"additionalProperties": {}
},
"image": {
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 4096
}
},
"required": [
"url"
],
"additionalProperties": {}
},
"thumbnail": {
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 4096
}
},
"required": [
"url"
],
"additionalProperties": {}
},
"fields": {
"maxItems": 25,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 256
},
"value": {
"type": "string",
"maxLength": 1024
},
"inline": {
"type": "boolean"
}
},
"required": [
"name",
"value"
]
}
}
},
"additionalProperties": {},
"description": "Discord-Embed im Format der Discord-API (snake_case)"
}
},
"attachments": {
"default": [],
"maxItems": 25,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"description": "Discord-Attachment-ID",
"type": "string",
"pattern": "^\\d{15,21}$"
},
"filename": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"url": {
"type": "string",
"maxLength": 4096,
"format": "uri",
"description": "Discord-CDN-URL – wird (falls aktiviert) automatisch archiviert"
},
"contentType": {
"anyOf": [
{
"type": "string",
"maxLength": 255
},
{
"type": "null"
}
]
},
"size": {
"default": 0,
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"width": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"height": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"spoiler": {
"default": false,
"type": "boolean"
}
},
"required": [
"filename",
"url"
]
}
},
"stickers": {
"default": [],
"maxItems": 3,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"name": {
"type": "string",
"maxLength": 100
},
"formatType": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
}
},
"required": [
"id",
"name"
]
}
},
"reactions": {
"default": [],
"maxItems": 50,
"type": "array",
"items": {
"type": "object",
"properties": {
"emoji": {
"type": "object",
"properties": {
"id": {
"anyOf": [
{
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
{
"type": "null"
}
]
},
"name": {
"type": "string",
"maxLength": 100
},
"animated": {
"default": false,
"type": "boolean"
}
},
"required": [
"name"
]
},
"count": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
}
},
"required": [
"emoji",
"count"
]
}
},
"components": {
"default": [],
"description": "Rohe Discord-Components (Buttons etc.)",
"maxItems": 10,
"type": "array",
"items": {}
}
},
"required": [
"id",
"author",
"timestamp"
]
}
}
},
"required": [
"guildId",
"channel",
"opener",
"openedAt"
],
"description": "TranscriptCreate"
}Antworten
200Bestehendes Transcript aktualisiert → TranscriptWriteResponse201Erstellt → TranscriptWriteResponse400Validierungsfehler → Error403Keine Berechtigung → Error413Body zu groß → Error
/api/v1/transcriptsTranscripts auflisten / suchen
Cursor-basierte Pagination: `nextCursor` der Antwort als `cursor` übergeben.
Query-Parameter
guildId | string | Discord-ID (Snowflake) als String |
status | string | |
userId | string | Nur Transcripts, an denen dieser Nutzer beteiligt ist |
openerId | string | Discord-ID (Snowflake) als String |
externalId | string | |
category | string | |
tag | string | |
q | string | Suche in Kanalname, Betreff, externalId |
searchMessages | string | Suche zusätzlich im Nachrichtentext |
from | string | openedAt >= from |
to | string | openedAt <= to |
limit | integer | |
cursor | string | nextCursor aus der vorherigen Antwort |
Antworten
200Liste → TranscriptList400Validierungsfehler → Error
/api/v1/transcripts/{transcriptId}Transcript abrufen (inkl. Teilnehmer, Metadaten)
Antworten
200Transcript → TranscriptResponse404Nicht gefunden → Error
/api/v1/transcripts/{transcriptId}Transcript aktualisieren
Request-Body (JSON Schema)
{
"type": "object",
"properties": {
"ticketNumber": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"channelName": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"category": {
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"subject": {
"anyOf": [
{
"type": "string",
"maxLength": 500
},
{
"type": "null"
}
]
},
"tags": {
"maxItems": 20,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 50
}
},
"status": {
"type": "string",
"enum": [
"OPEN",
"CLOSED"
]
},
"claimedBy": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"username"
],
"description": "DiscordUser"
},
{
"type": "null"
}
]
},
"closedBy": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"username"
],
"description": "DiscordUser"
},
{
"type": "null"
}
]
},
"closeReason": {
"anyOf": [
{
"type": "string",
"maxLength": 2000
},
{
"type": "null"
}
]
},
"closedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
]
},
"resolved": {
"anyOf": [
{
"type": "object",
"properties": {
"users": {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"additionalProperties": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 100
}
},
"required": [
"name"
]
}
},
"roles": {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"additionalProperties": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 100
},
"color": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
}
},
"required": [
"name"
]
}
},
"channels": {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"additionalProperties": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 100
}
},
"required": [
"name"
]
}
}
},
"description": "Namen für Mentions (<@id>, <@&id>, <#id>) im Nachrichtentext"
},
{
"type": "null"
}
]
},
"metadata": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string",
"maxLength": 64
},
"additionalProperties": {},
"description": "Beliebige zusätzliche Daten deines Bots (max. 16 KB)"
},
{
"type": "null"
}
]
}
},
"description": "TranscriptUpdate"
}Antworten
200Aktualisiert → TranscriptResponse404Nicht gefunden → Error
/api/v1/transcripts/{transcriptId}Transcript löschen (inkl. Dateien)
Antworten
204Gelöscht404Nicht gefunden → Error
/api/v1/transcripts/{transcriptId}/messagesNachrichten anhängen
Bis zu 1000 Nachrichten pro Request. Bereits vorhandene Message-IDs werden übersprungen – Requests können also gefahrlos wiederholt werden.
Request-Body (JSON Schema)
{
"type": "object",
"properties": {
"messages": {
"minItems": 1,
"maxItems": 1000,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-Message-ID (dient der Deduplizierung)"
},
"author": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"username"
],
"description": "DiscordUser"
},
"content": {
"default": "",
"type": "string",
"maxLength": 10000
},
"timestamp": {
"description": "Erstellungszeitpunkt (ISO-8601 oder Unix-ms)",
"type": "string",
"format": "date-time"
},
"editedTimestamp": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
]
},
"type": {
"default": 0,
"description": "Discord-Message-Type",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"pinned": {
"default": false,
"type": "boolean"
},
"replyTo": {
"description": "ID der Nachricht, auf die geantwortet wird",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
{
"type": "null"
}
]
},
"embeds": {
"default": [],
"maxItems": 10,
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 256
},
"description": {
"type": "string",
"maxLength": 4096
},
"url": {
"type": "string",
"maxLength": 2048
},
"color": {
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
"timestamp": {
"type": "string",
"maxLength": 64
},
"author": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 256
},
"url": {
"type": "string"
},
"icon_url": {
"type": "string"
}
},
"required": [
"name"
],
"additionalProperties": {}
},
"footer": {
"type": "object",
"properties": {
"text": {
"type": "string",
"maxLength": 2048
},
"icon_url": {
"type": "string"
}
},
"required": [
"text"
],
"additionalProperties": {}
},
"image": {
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 4096
}
},
"required": [
"url"
],
"additionalProperties": {}
},
"thumbnail": {
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 4096
}
},
"required": [
"url"
],
"additionalProperties": {}
},
"fields": {
"maxItems": 25,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 256
},
"value": {
"type": "string",
"maxLength": 1024
},
"inline": {
"type": "boolean"
}
},
"required": [
"name",
"value"
]
}
}
},
"additionalProperties": {},
"description": "Discord-Embed im Format der Discord-API (snake_case)"
}
},
"attachments": {
"default": [],
"maxItems": 25,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"description": "Discord-Attachment-ID",
"type": "string",
"pattern": "^\\d{15,21}$"
},
"filename": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"url": {
"type": "string",
"maxLength": 4096,
"format": "uri",
"description": "Discord-CDN-URL – wird (falls aktiviert) automatisch archiviert"
},
"contentType": {
"anyOf": [
{
"type": "string",
"maxLength": 255
},
{
"type": "null"
}
]
},
"size": {
"default": 0,
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"width": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"height": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"spoiler": {
"default": false,
"type": "boolean"
}
},
"required": [
"filename",
"url"
]
}
},
"stickers": {
"default": [],
"maxItems": 3,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"name": {
"type": "string",
"maxLength": 100
},
"formatType": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
}
},
"required": [
"id",
"name"
]
}
},
"reactions": {
"default": [],
"maxItems": 50,
"type": "array",
"items": {
"type": "object",
"properties": {
"emoji": {
"type": "object",
"properties": {
"id": {
"anyOf": [
{
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
{
"type": "null"
}
]
},
"name": {
"type": "string",
"maxLength": 100
},
"animated": {
"default": false,
"type": "boolean"
}
},
"required": [
"name"
]
},
"count": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
}
},
"required": [
"emoji",
"count"
]
}
},
"components": {
"default": [],
"description": "Rohe Discord-Components (Buttons etc.)",
"maxItems": 10,
"type": "array",
"items": {}
}
},
"required": [
"id",
"author",
"timestamp"
]
}
},
"participants": {
"default": [],
"maxItems": 500,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
},
"role": {
"description": "Rolle im Ticket (Standard: aus Kontext abgeleitet)",
"type": "string",
"enum": [
"OPENER",
"STAFF",
"MEMBER",
"BOT"
]
},
"canView": {
"description": "Darf der Nutzer das Transcript im Web-Viewer sehen? (Standard: true)",
"type": "boolean"
}
},
"required": [
"id",
"username"
]
}
}
},
"required": [
"messages"
],
"description": "AppendMessages"
}Antworten
200Alles Duplikate → AppendResult201Eingefügt → AppendResult404Nicht gefunden → Error
/api/v1/transcripts/{transcriptId}/messagesNachrichten abrufen (chronologisch, paginiert)
Query-Parameter
limit | integer | |
cursor | string | |
authorId | string | Discord-ID (Snowflake) als String |
Antworten
200Nachrichten → MessageList
/api/v1/transcripts/{transcriptId}/closeTicket schließen
Setzt Status CLOSED, closedAt, closedBy und closeReason. Optional mit letzter Nachrichten-Batch.
Request-Body (JSON Schema)
{
"type": "object",
"properties": {
"closedBy": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"username"
],
"description": "DiscordUser"
},
"closeReason": {
"type": "string",
"maxLength": 2000
},
"closedAt": {
"description": "Standard: jetzt",
"type": "string",
"format": "date-time"
},
"messages": {
"default": [],
"description": "Letzte Nachrichten-Batch",
"maxItems": 1000,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-Message-ID (dient der Deduplizierung)"
},
"author": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"username"
],
"description": "DiscordUser"
},
"content": {
"default": "",
"type": "string",
"maxLength": 10000
},
"timestamp": {
"description": "Erstellungszeitpunkt (ISO-8601 oder Unix-ms)",
"type": "string",
"format": "date-time"
},
"editedTimestamp": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
]
},
"type": {
"default": 0,
"description": "Discord-Message-Type",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"pinned": {
"default": false,
"type": "boolean"
},
"replyTo": {
"description": "ID der Nachricht, auf die geantwortet wird",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
{
"type": "null"
}
]
},
"embeds": {
"default": [],
"maxItems": 10,
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 256
},
"description": {
"type": "string",
"maxLength": 4096
},
"url": {
"type": "string",
"maxLength": 2048
},
"color": {
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
"timestamp": {
"type": "string",
"maxLength": 64
},
"author": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 256
},
"url": {
"type": "string"
},
"icon_url": {
"type": "string"
}
},
"required": [
"name"
],
"additionalProperties": {}
},
"footer": {
"type": "object",
"properties": {
"text": {
"type": "string",
"maxLength": 2048
},
"icon_url": {
"type": "string"
}
},
"required": [
"text"
],
"additionalProperties": {}
},
"image": {
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 4096
}
},
"required": [
"url"
],
"additionalProperties": {}
},
"thumbnail": {
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 4096
}
},
"required": [
"url"
],
"additionalProperties": {}
},
"fields": {
"maxItems": 25,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 256
},
"value": {
"type": "string",
"maxLength": 1024
},
"inline": {
"type": "boolean"
}
},
"required": [
"name",
"value"
]
}
}
},
"additionalProperties": {},
"description": "Discord-Embed im Format der Discord-API (snake_case)"
}
},
"attachments": {
"default": [],
"maxItems": 25,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"description": "Discord-Attachment-ID",
"type": "string",
"pattern": "^\\d{15,21}$"
},
"filename": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"url": {
"type": "string",
"maxLength": 4096,
"format": "uri",
"description": "Discord-CDN-URL – wird (falls aktiviert) automatisch archiviert"
},
"contentType": {
"anyOf": [
{
"type": "string",
"maxLength": 255
},
{
"type": "null"
}
]
},
"size": {
"default": 0,
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"width": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"height": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"spoiler": {
"default": false,
"type": "boolean"
}
},
"required": [
"filename",
"url"
]
}
},
"stickers": {
"default": [],
"maxItems": 3,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"name": {
"type": "string",
"maxLength": 100
},
"formatType": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
}
},
"required": [
"id",
"name"
]
}
},
"reactions": {
"default": [],
"maxItems": 50,
"type": "array",
"items": {
"type": "object",
"properties": {
"emoji": {
"type": "object",
"properties": {
"id": {
"anyOf": [
{
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
{
"type": "null"
}
]
},
"name": {
"type": "string",
"maxLength": 100
},
"animated": {
"default": false,
"type": "boolean"
}
},
"required": [
"name"
]
},
"count": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
}
},
"required": [
"emoji",
"count"
]
}
},
"components": {
"default": [],
"description": "Rohe Discord-Components (Buttons etc.)",
"maxItems": 10,
"type": "array",
"items": {}
}
},
"required": [
"id",
"author",
"timestamp"
]
}
}
},
"description": "TranscriptClose"
}Antworten
200Geschlossen → TranscriptWriteResponse404Nicht gefunden → Error
/api/v1/transcripts/{transcriptId}/participantsTeilnehmer auflisten
Antworten
200Teilnehmer → ParticipantList
/api/v1/transcripts/{transcriptId}/participantsTeilnehmer hinzufügen / Lesezugriff vergeben
Nutzer mit `canView: true` können das Transcript nach Discord-Login sehen, auch ohne Staff-Rolle.
Request-Body (JSON Schema)
{
"type": "object",
"properties": {
"participants": {
"minItems": 1,
"maxItems": 500,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
},
"username": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"displayName": {
"description": "Server-Nickname oder Global Name",
"anyOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "null"
}
]
},
"avatarUrl": {
"anyOf": [
{
"type": "string",
"maxLength": 4096,
"format": "uri"
},
{
"type": "null"
}
]
},
"bot": {
"default": false,
"type": "boolean"
},
"color": {
"description": "Farbe der höchsten Rolle als Integer",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 16777215
},
{
"type": "null"
}
]
},
"role": {
"description": "Rolle im Ticket (Standard: aus Kontext abgeleitet)",
"type": "string",
"enum": [
"OPENER",
"STAFF",
"MEMBER",
"BOT"
]
},
"canView": {
"description": "Darf der Nutzer das Transcript im Web-Viewer sehen? (Standard: true)",
"type": "boolean"
}
},
"required": [
"id",
"username"
]
}
}
},
"required": [
"participants"
],
"description": "ParticipantsUpsert"
}Antworten
200Teilnehmer → ParticipantList
/api/v1/transcripts/{transcriptId}/participants/{userId}Lesezugriff eines Teilnehmers entziehen
Antworten
204Entzogen404Nicht gefunden → Error
/api/v1/transcripts/{transcriptId}/attachmentsDatei hochladen
multipart/form-data mit Feld `file`, optional `messageId` (Discord-Message-ID) und `attachmentId` (ersetzt ein per Nachricht gemeldetes Attachment).
Body: multipart/form-data mit file (Pflicht), messageId, attachmentId
Antworten
201Hochgeladen → AttachmentResponse413Datei zu groß → Error
Web API
/api/users/meEingeloggter Nutzer + zugängliche Guilds
Antworten
200Nutzer → Me401Nicht authentifiziert → Error
/api/guildsZugängliche Guilds
`?refresh=true` gleicht Guild-Mitgliedschaften sofort mit Discord ab.
Antworten
200Guilds → AccessibleGuildList
/api/guilds/{guildId}Guild-Details (Stats für Staff, Einstellungen für Manager)
Antworten
200Guild404Nicht gefunden → Error
/api/guilds/{guildId}Guild-Einstellungen ändern (MANAGER)
Request-Body (JSON Schema)
{
"type": "object",
"properties": {
"staffRoleIds": {
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
}
},
"managerRoleIds": {
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
}
},
"allowParticipantAccess": {
"type": "boolean"
},
"retentionDays": {
"anyOf": [
{
"type": "integer",
"minimum": 1,
"maximum": 3650
},
{
"type": "null"
}
],
"description": "null = nie automatisch löschen"
}
},
"description": "GuildSettings"
}Antworten
200Aktualisiert → GuildResponse403Keine Berechtigung → Error
/api/guilds/{guildId}/transcriptsTranscripts einer Guild
STAFF/MANAGER sehen alle Transcripts, Mitglieder nur solche, an denen sie beteiligt sind.
Query-Parameter
status | string | |
userId | string | Nur Transcripts, an denen dieser Nutzer beteiligt ist |
openerId | string | Discord-ID (Snowflake) als String |
externalId | string | |
category | string | |
tag | string | |
q | string | Suche in Kanalname, Betreff, externalId |
searchMessages | string | Suche zusätzlich im Nachrichtentext |
from | string | openedAt >= from |
to | string | openedAt <= to |
limit | integer | |
cursor | string | nextCursor aus der vorherigen Antwort |
Antworten
200Liste → TranscriptList
/api/guilds/{guildId}/audit-logAudit-Log (MANAGER)
Query-Parameter
transcriptId | string | |
action | string | |
limit | integer | |
cursor | string |
Antworten
200Einträge403Keine Berechtigung → Error
/api/transcripts/{transcriptId}Transcript abrufen
Antworten
200Transcript404Nicht gefunden → Error
/api/transcripts/{transcriptId}Transcript löschen (MANAGER)
Antworten
204Gelöscht403Keine Berechtigung → Error
/api/transcripts/{transcriptId}/messagesNachrichten abrufen
Query-Parameter
limit | integer | |
cursor | string | |
authorId | string | Discord-ID (Snowflake) als String |
Antworten
200Nachrichten → MessageList
/api/transcripts/{transcriptId}/exportExport als HTML oder JSON
Query-Parameter
format | string | |
download | string |
Antworten
200Datei
/api/attachments/{attachmentId}Attachment herunterladen
Funktioniert mit Session-Cookie oder Bot-Key (`transcripts:read`). `?download` erzwingt einen Download.
Antworten
200Datei302Weiterleitung zum Discord-CDN (nicht archiviert)
Auth
/api/auth/loginDiscord-Login starten
Leitet zu Discord weiter. `?next=/pfad` bestimmt das Ziel nach dem Login.
Antworten
307Weiterleitung zu Discord
/api/auth/callbackOAuth2-Callback (von Discord aufgerufen)
Antworten
307Weiterleitung ins Dashboard
/api/auth/logoutAbmelden
Antworten
204Abgemeldet
Admin
/api/admin/keysAPI-Keys auflisten
Antworten
200Keys
/api/admin/keysAPI-Key erstellen
Der Klartext-Key (`secret`) wird **nur einmal** zurückgegeben.
Request-Body (JSON Schema)
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"scopes": {
"default": [
"transcripts:read",
"transcripts:write",
"transcripts:delete",
"guilds:read",
"guilds:write"
],
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"transcripts:read",
"transcripts:write",
"transcripts:delete",
"guilds:read",
"guilds:write"
]
}
},
"allGuilds": {
"default": false,
"type": "boolean"
},
"guildIds": {
"default": [],
"maxItems": 500,
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
}
},
"expiresAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"name"
],
"description": "ApiKeyCreate"
}Antworten
201Erstellt → ApiKeyCreated
/api/admin/keys/{keyId}API-Key ändern
Request-Body (JSON Schema)
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"scopes": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"transcripts:read",
"transcripts:write",
"transcripts:delete",
"guilds:read",
"guilds:write"
]
}
},
"allGuilds": {
"type": "boolean"
},
"guildIds": {
"maxItems": 500,
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{15,21}$",
"description": "Discord-ID (Snowflake) als String"
}
},
"expiresAt": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
]
}
},
"description": "ApiKeyUpdate"
}Antworten
200Aktualisiert → ApiKeyResponse
/api/admin/keys/{keyId}API-Key widerrufen
Antworten
200Widerrufen → ApiKeyResponse
/api/cron/retentionAufräum-Job ausführen
Löscht Transcripts gemäß `retentionDays`, abgelaufene Sessions, alte Caches und Audit-Einträge (> 365 Tage).
Antworten
200Ergebnis
/api/healthHealth-Check
Antworten
200OK503Datenbank nicht erreichbar