---
seo_title: API-Schlüssel-Authentifizierung: X-API-Key und JWT | EnConvert
meta_desc: EnConvert hat zwei Schlüsseltypen: private sk_-Schlüssel für Server, öffentliche pk_-Schlüssel für Browser (gegen JWT getauscht), plus GET /v1/auth/verify.
keywords: api key authentifizierung x-api-key header, jwt bearer token authentifizierung, sk_ vs pk_ api schlüssel, öffentlichen schlüssel gegen jwt tauschen, /v1/auth/token endpunkt, api schlüssel gültigkeit prüfen, api schlüssel rotieren ohne downtime, domain whitelist api schlüssel, 403 forbidden allowed_endpoints, api schlüssel im browser verwenden
---

# Authentifizierung

EnConvert kennt zwei Arten von API-Schlüsseln, und wo dein Code läuft, entscheidet darüber, welchen du verwendest. Ein privater Schlüssel (`sk_`) gehört in den `X-API-Key`-Header und wird von einem Server gesendet, den du kontrollierst; ein öffentlicher Schlüssel (`pk_`) wird gegen ein kurzlebiges JWT getauscht, das Browser-Code als `Authorization: Bearer <token>` sendet.

---

## Den Schlüsseltyp wählen

| | Privater Schlüssel | Öffentlicher Schlüssel + JWT |
|---|---|---|
| Präfix | `sk_` | `pk_` |
| Gesendet als | `X-API-Key: sk_your_private_key` | `Authorization: Bearer <token>` |
| Läuft in | Servern, Skripten, CI-Jobs, Containern | Browsern, eingebetteten Widgets, allem, was an einen Client ausgeliefert wird |
| Abgelehnt, wenn | die Anfrage einen `Origin`-Header trägt (`403`) | er etwas anderes als `/v1/auth/token` oder `/v1/auth/branding` aufruft (`403`) |
| Reichweite | jeder Endpunkt: synchron, asynchron, Batch, Webhooks | ein Element pro Anfrage, synchron, Download per Presigned URL |
| Geltungsbereich | optional `allowed_endpoints` | `allowed_domains` plus `allowed_endpoints` |
| Lebensdauer | Schlüssel gültig bis zum Widerruf | Schlüssel gültig bis zum Widerruf, Access-Token 1 Stunde, Refresh-Cookie 7 Tage |

Entscheide nach Deployment-Ziel. Ein Backend-Service, ein Skript, ein Cron-Job oder ein internes Tool bekommt einen privaten Schlüssel. Eine browserbasierte App oder ein eingebettetes Widget bekommt einen öffentlichen Schlüssel mit JWT. Es gibt keine Möglichkeit, einen privaten Schlüssel in Frontend-Code zu verstecken: Das Gateway lehnt ihn allein aufgrund des Vorhandenseins eines `Origin`-Headers ab, bevor es überhaupt etwas anderes prüft.

Ein Schlüssel besteht aus seinem Präfix, gefolgt von einem zufälligen Token, insgesamt 46 Zeichen. Die Platzhalternamen in den folgenden Beispielen (`sk_your_private_key`, `pk_your_public_key`) stehen für dieses Token; ein echter Schlüssel enthält kein Umgebungssegment wie `live` oder `test`. Alles, was kürzer als 45 Zeichen ist, wird mit `401 Invalid API Key format` abgelehnt, noch bevor das Präfix überhaupt gelesen wird.

Auf dem Server wird nur ein SHA-256-Hash jedes Schlüssels gespeichert, dazu ein sieben Zeichen langer Präfix-Stub, damit du deine Schlüssel im Dashboard auseinanderhalten kannst.

---

## Private Schlüssel {: #private-keys }

Private Schlüssel sind für **serverseitige Anwendungen** gedacht, bei denen dein API-Schlüssel geheim gehalten werden kann. Sie gewähren vollen Zugriff auf alle API-Endpunkte und -Funktionen.

- **Header:** `X-API-Key: sk_your_private_key`
- **Zugriff:** Voller Zugriff auf alle Endpunkte, einschließlich synchroner und asynchroner Operationen, Stapelverarbeitung und aller Konvertierungstypen.
- **Sicherheit:** Schlüssel werden auf dem Server als SHA-256-Hashes gespeichert. Der Klartextschlüssel wird nur einmal bei der Erstellung angezeigt.

Ein Token-Austausch oder Session-Management ist nicht erforderlich. Füge den Schlüssel in jede Anfrage ein:

```bash
curl -X POST https://api.enconvert.com/v1/convert/url-to-pdf \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'
```

### Header-Format

Füge deinen privaten Schlüssel bei jeder Anfrage in den `X-API-Key`-Header ein:

```http
X-API-Key: sk_your_private_key
```

Private Schlüssel beginnen immer mit dem Präfix `sk_`. Du kannst deine Schlüssel im EnConvert-Dashboard generieren und verwalten.

### Beispiel: Dateikonvertierung

Konvertiere eine JSON-Datei mit einem privaten Schlüssel in XML:

```bash
curl -X POST https://api.enconvert.com/v1/convert/json-to-xml \
  -H "X-API-Key: sk_your_private_key" \
  -F "file=@data.json"
```

Antwort:

```json
{
  "presigned_url": "https://econverter.nyc3.cdn.digitaloceanspaces.com/...",
  "object_key": "live/files/12345/json-to-xml/data_20250202_120530123.xml",
  "filename": "data_20250202_120530123.xml",
  "file_size": 1024,
  "conversion_time_seconds": 0.45
}
```

- `presigned_url`: eine temporäre, herunterladbare URL zum Abrufen der konvertierten Datei.
- `object_key`: der Speicherpfad der konvertierten Datei (z. B. `live/files/12345/json-to-xml/...`). Dies ist keine URL.
- `filename`: der generierte Dateiname der konvertierten Datei.
- `file_size`: die Größe der Ausgabedatei in Bytes.
- `conversion_time_seconds`: die Zeit, die die Konvertierung benötigt hat.

Diese Download-URLs sind kurzlebig. Ihre Ablauf- und Aufbewahrungsregeln stehen unter [Signierte URLs](/de/docs/concepts/signed-urls.md).

### Endpunkt-Beschränkungen

Standardmäßig hat ein privater Schlüssel Zugriff auf alle API-Endpunkte. Du kannst einen Schlüssel optional über die Einstellung **allowed_endpoints** bei der Erstellung auf bestimmte Endpunkte beschränken.

Wenn `allowed_endpoints` konfiguriert ist, kann der Schlüssel nur die aufgeführten Endpunkte aufrufen. Anfragen an jeden anderen Endpunkt werden mit einem `403 Forbidden`-Fehler abgelehnt: `Endpoint '{path}' not allowed for this API key`.

Beispielkonfiguration:

```json
{
  "allowed_endpoints": [
    "/v1/convert/url-to-pdf",
    "/v1/convert/json-to-xml",
    "/v1/convert/html-to-pdf"
  ]
}
```

Das ist nützlich, wenn du einen Schlüssel mit eingeschränktem Umfang ausstellen möchtest, zum Beispiel einen Schlüssel, der nur PDF-Konvertierungen durchführen kann.

Eine Handvoll Pfade bleibt erreichbar, egal was in der Liste steht, denn ein Schlüssel, der einen Job starten kann, muss ihn auch abschließen können:

- `/v1/auth/token`, `/v1/auth/verify` und `/v1/whoami`
- `/v1/convert/status/{job_id}`, `/v1/convert/batch/{batch_id}` und `/v1/convert/download/{object_key}`
- `/v1/extension/*`, für JWT-authentifizierte Anfragen
- die jobspezifischen V2-Pfade für perceive, ingest und watch

Ein Schlüssel, der mit dem einzelnen Eintrag `["*"]` erstellt wurde, meint jeden Endpunkt, auch solche, die erst nach der Erstellung des Schlüssels ausgeliefert werden.

Die Liste wird bei der Erstellung festgelegt. Es gibt keinen Aufruf, der die Beschränkungen eines bestehenden Schlüssels bearbeitet; den Geltungsbereich eines Schlüssels einzuschränken oder zu erweitern bedeutet also, einen neuen Schlüssel zu erstellen und den alten zu widerrufen. Siehe [Schlüssel sicher aufbewahren](#keeping-keys-safe).

<div class="alert alert-warning">
<strong>Verwende private Schlüssel nicht in clientseitigem Code.</strong> Die API erkennt den von Browsern gesendeten <code>Origin</code>-Header und lehnt Anfragen mit einem privaten Schlüssel aus einer Browser-Umgebung mit <code>403 Private API keys cannot be used from browsers</code> ab. Verwende für clientseitige Integrationen stattdessen einen <a href="#public-keys-and-jwt">öffentlichen Schlüssel mit JWT</a>.
</div>

---

## Öffentliche Schlüssel und JWT {: #public-keys-and-jwt }

Die Public-Key-JWT-Authentifizierung ermöglicht es clientseitigen (Browser-)Apps, die EnConvert-API aufzurufen: Du tauschst deinen öffentlichen Schlüssel (`pk_`) über `POST /v1/auth/token` gegen ein kurzlebiges JWT-Access-Token ein und sendest dieses Token dann bei API-Anfragen im Header `Authorization: Bearer <token>`. Da ein öffentlicher Schlüssel für Endnutzer sichtbar ist, kann er die API nicht direkt aufrufen. Für sich allein erreicht er genau zwei Pfade: `/v1/auth/token` und `/v1/auth/branding`. Alles andere liefert `403` mit einer Meldung, die dir sagt, dass du den Schlüssel zuerst gegen ein Token tauschen musst.

1. **Tausche** deinen öffentlichen Schlüssel (`pk_`) über `POST /v1/auth/token` gegen ein JWT-Access-Token.
2. **Verwende** das JWT-Token bei API-Anfragen im Header `Authorization: Bearer <token>`.
3. **Erneuere** das Token automatisch vor Ablauf über `POST /v1/auth/refresh`.
4. **Domain-Whitelisting** stellt sicher, dass nur Anfragen von deinen genehmigten Domains akzeptiert werden.

### Schritt 1: Öffentlichen Schlüssel gegen JWT tauschen

```http
POST /v1/auth/token
```

| Header | Wert | Beschreibung |
|---|---|---|
| `X-API-Key` | `pk_your_public_key` | Dein öffentlicher API-Schlüssel |

Der Endpunkt erwartet ein JSON-Body-Objekt. Wird gar kein Body gesendet, kommt `422` mit `{"type":"missing","loc":["body"],"msg":"Field required"}` zurück; sende also `{}`, wenn du nichts zu übergeben hast. Das einzige optionale Feld ist `turnstile_token`, das nur bei Anfragen vom Widget-Origin von EnConvert geprüft und überall sonst ignoriert wird.

```javascript
async function getToken() {
  const response = await fetch("https://api.enconvert.com/v1/auth/token", {
    method: "POST",
    headers: {
      "X-API-Key": "pk_your_public_key",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({}),
    credentials: "include",
  });

  if (!response.ok) {
    throw new Error(`Token exchange failed: ${response.status}`);
  }

  const data = await response.json();
  return data.token;
}
```

<div class="alert alert-warning">
<strong>Wichtig:</strong> Du musst <code>credentials: "include"</code> in den fetch-Optionen angeben. Das stellt sicher, dass das Refresh-Token-Cookie vom Browser gespeichert wird, was für die automatische Token-Erneuerung erforderlich ist.
</div>

Antwort:

```json
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

Die Antwort setzt außerdem ein `HttpOnly`-Cookie, das das Refresh-Token enthält. Dieses Cookie wird automatisch vom Browser verwaltet und beim Erneuern des Access-Tokens verwendet.

Ein privater Schlüssel, der an diesen Endpunkt gesendet wird, wird mit `400 Only public API keys can exchange for tokens. Private keys should be used directly.` abgelehnt. Das ist die API, die dir sagt, dass du den Austauschschritt weglassen sollst, und kein defekter Schlüssel.

### Schritt 2: Das JWT-Token verwenden

Füge das JWT-Token bei allen nachfolgenden API-Anfragen als Bearer-Token in den `Authorization`-Header ein.

```javascript
async function convertUrlToPdf(token, url) {
  const response = await fetch("https://api.enconvert.com/v1/convert/url-to-pdf", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ url }),
  });

  return await response.json();
}

// Usage
const token = await getToken();
const result = await convertUrlToPdf(token, "https://example.com");
console.log(result.presigned_url);
```

### Schritt 3: Automatische Token-Erneuerung

Access-Tokens laufen nach einer Stunde ab. Verwende den Refresh-Endpunkt, um ein neues Access-Token zu erhalten, ohne dass sich der Nutzer erneut authentifizieren muss.

```http
POST /v1/auth/refresh
```

Das Refresh-Token wird automatisch über das `HttpOnly`-Cookie gesendet, das beim ursprünglichen Token-Austausch gesetzt wurde. Es sind kein Request-Body und keine zusätzlichen Header erforderlich.

```javascript
class EnconvertClient {
  constructor(publicKey) {
    this.publicKey = publicKey;
    this.token = null;
    this.tokenExpiry = null;
  }

  async getToken() {
    const response = await fetch("https://api.enconvert.com/v1/auth/token", {
      method: "POST",
      headers: {
        "X-API-Key": this.publicKey,
        "X-Parent-Origin": window.location.origin,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({}),
      credentials: "include",
    });

    const data = await response.json();
    this.token = data.token;
    // Set expiry to 55 minutes (refresh before the 1-hour expiry)
    this.tokenExpiry = Date.now() + 55 * 60 * 1000;
    return this.token;
  }

  async refreshToken() {
    const response = await fetch("https://api.enconvert.com/v1/auth/refresh", {
      method: "POST",
      credentials: "include",
    });

    if (!response.ok) {
      // Refresh token expired, re-authenticate
      return await this.getToken();
    }

    const data = await response.json();
    this.token = data.token;
    this.tokenExpiry = Date.now() + 55 * 60 * 1000;
    return this.token;
  }

  async getValidToken() {
    if (!this.token || Date.now() >= this.tokenExpiry) {
      if (this.token) {
        return await this.refreshToken();
      }
      return await this.getToken();
    }
    return this.token;
  }

  async convert(endpoint, body) {
    const token = await this.getValidToken();
    const response = await fetch(`https://api.enconvert.com${endpoint}`, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });

    return await response.json();
  }
}

// Usage
const client = new EnconvertClient("pk_your_public_key");
const result = await client.convert("/v1/convert/url-to-pdf", {
  url: "https://example.com",
});
```

Vier Dinge, die du wissen solltest, bevor du eine fehlschlagende Erneuerung debuggst:

- Die Erneuerung ermittelt dein Projekt aus dem Cookie und sucht dann nach irgendeinem aktiven öffentlichen Schlüssel in diesem Projekt. Widerrufst du jeden öffentlichen Schlüssel, liefert die Erneuerung `401`, selbst wenn das Cookie noch innerhalb seiner sieben Tage liegt.
- Die Refresh-Route prüft die Domain-Whitelist nicht erneut. Diese Prüfung findet statt, wenn das Token ausgestellt wird.
- Die Erneuerung bindet das neue Token nicht erneut an den Schlüssel, den du ursprünglich verwendet hast. Hält das Projekt mehrere öffentliche Schlüssel, kann das erneuerte Token mit den Beschränkungen eines anderen Schlüssels zurückkommen.
- Ausstellung und Erneuerung von Tokens haben eine eigene Drosselung pro IP, getrennt von den [Rate-Limits](/de/docs/reference/rate-limits.md) deines Tarifs. Ein Client, der in einer Refresh-Schleife feststeckt, wird das merken.

### Token-Lebensdauer

| Token | Lebensdauer | Speicherung |
|---|---|---|
| Access-Token | 1 Stunde | Wird im JSON-Antwortkörper zurückgegeben; im Speicher (memory) halten |
| Refresh-Token | 7 Tage | Wird als `HttpOnly`-Cookie gesetzt; vom Browser verwaltet |

### Domain-Whitelisting

Öffentliche Schlüssel sind auf bestimmte Domains beschränkt, die du in deinem Dashboard konfigurierst.

Der Abgleich vergleicht nur Host und Port. Das Schema wird zuvor auf beiden Seiten entfernt, `https://example.com` und `http://example.com` sind für die Whitelist also derselbe Origin. Der Port wird nicht entfernt und ist Teil des Abgleichs.

- **Exakte Übereinstimmung:** `https://example.com` stimmt mit dem reinen Host `example.com` unter jedem Schema überein.
- **Wildcard-Subdomains:** `https://*.example.com` stimmt mit `https://app.example.com`, `https://staging.example.com` und auch mit der Apex-Domain `https://example.com` überein.
- **Portspezifisch:** `http://localhost:3000` stimmt nur mit genau diesem Host und Port überein.

| Whitelist-Eintrag | Stimmt überein mit | Stimmt nicht überein mit |
|---|---|---|
| `https://example.com` | `https://example.com`, `http://example.com` | `https://www.example.com` |
| `https://*.example.com` | `https://app.example.com`, `https://dev.example.com`, `https://example.com` | `https://example.net` |
| `http://localhost:3000` | `http://localhost:3000` | `http://localhost:8080` |

Eine Anfrage von einem Origin, der nicht auf der Liste steht, erhält `403 Domain {origin} not authorized`, und der Projektinhaber wird per E-Mail darüber informiert (höchstens einmal pro Schlüssel und 24 Stunden). Füllt sich dein Postfach, ist meist ein veralteter Whitelist-Eintrag die Ursache.

Zwei Origins überspringen die Domain-Prüfung vollständig: ein `chrome-extension://...`-Origin, damit Browser-Erweiterungen die API aufrufen können, und der eigene Widget-Origin von EnConvert, bei dem die Prüfung stattdessen auf die `X-Parent-Origin`-Validierung des Widgets übergeht.

### Sicherheitsfunktionen

- **Kurzlebige Tokens:** Access-Tokens laufen nach 1 Stunde ab, wodurch das Zeitfenster im Falle eines kompromittierten Tokens begrenzt wird.
- **HttpOnly-Refresh-Cookies:** Refresh-Tokens werden in `HttpOnly`-Cookies gespeichert, wodurch sie für JavaScript unzugänglich und resistent gegen XSS-Angriffe sind.
- **Domain-Beschränkungen:** Tokens werden nur ausgestellt, wenn die Anfrage von einer whitelisteten Domain stammt.
- **Kein direkter API-Zugriff:** Öffentliche Schlüssel allein können keine Konvertierungsendpunkte aufrufen. Ein gültiges JWT ist immer erforderlich.

### Einschränkungen öffentlicher Schlüssel

Die Public-Key-Authentifizierung hat im Vergleich zu privaten Schlüsseln folgende Einschränkungen:

- **Nur synchron:** Es sind nur synchrone Konvertierungsendpunkte verfügbar. Der Async-Modus und Webhook-Callbacks sind es nicht; `notification_email` und `callback_url` werden bei Konvertierungen mit einem Browser-Schlüssel geleert.
- **Ein Element pro Anfrage:** Jede Anfrage darf nur eine URL oder Datei konvertieren. Das Senden eines Arrays liefert `400 Public keys only support a single URL input`.
- **Direkter Download:** Antworten liefern eine `presigned_url` zum sofortigen Download. Es gibt keine Option für benutzerdefinierte Speicherziele.
- **Job-Status ja, Batch-Status nein:** `GET /v1/convert/status/{job_id}` funktioniert mit einem Browser-Token; so holt sich das Widget ein Ergebnis nach einer abgebrochenen Verbindung zurück. `GET /v1/convert/batch/{batch_id}` wird mit `403 Batch status requires a private API key` abgelehnt.

Das Einreichen von Batches selbst wird durch das Batch-Limit deines Tarifs geregelt und nicht durch den Schlüsseltyp; da ein Browser-Schlüssel aber auf ein Element pro Anfrage begrenzt ist, brauchen Batches in der Praxis einen privaten Schlüssel. Siehe [Stapelverarbeitung](/de/docs/guides/batch-processing.md).

<div class="alert alert-warning">
<strong>Best Practices:</strong>
<ul>
  <li>Speichere Access-Tokens ausschließlich im Arbeitsspeicher. Persistiere sie niemals in <code>localStorage</code> oder <code>sessionStorage</code>.</li>
  <li>Implementiere eine automatische Token-Erneuerung, um Unterbrechungen während Nutzersitzungen zu vermeiden.</li>
  <li>Halte deine Liste whitelisteter Domains so spezifisch wie möglich. Vermeide breite Wildcards.</li>
  <li>Verwende <code>credentials: "include"</code> bei allen fetch-Anfragen, damit Cookies korrekt gesendet und empfangen werden.</li>
  <li>Behandle fehlgeschlagene Token-Erneuerungen elegant, indem du auf eine vollständige Neuauthentifizierung mit dem öffentlichen Schlüssel zurückfällst.</li>
</ul>
</div>

Wenn du den Browser-Flow willst, ohne ihn selbst zu schreiben: Das einbettbare Widget stellt seine Tokens selbst aus und erneuert sie. Siehe [Web-Widgets](/de/docs/guides/integrations.md#web-widgets).

---

## Deine Credentials prüfen {: #verify-your-credentials }

`GET /v1/auth/verify` prüft, ob deine aktuelle Authentifizierung gültig ist, und meldet, wofür die API sie hält. Der Endpunkt funktioniert mit privaten Schlüsseln, die im `X-API-Key`-Header gesendet werden, und mit JWT-Bearer-Tokens im `Authorization`-Header. Eine gültige Anfrage liefert deine `project_id`, `tier`, `key_type` sowie etwaige Domain- oder Endpunkt-Beschränkungen; ein ungültiger oder abgelaufener Schlüssel bzw. Token liefert `401 Unauthorized`.

```http
GET /v1/auth/verify
```

| Header | Wert | Beschreibung |
|---|---|---|
| `X-API-Key` | `sk_your_private_key` | Authentifizierung mit einem privaten Schlüssel |
| `Authorization` | `Bearer <token>` | Authentifizierung mit einem JWT-Token |

Verwende einen der beiden obigen Header, nicht beide.

<div class="alert alert-warning">
<strong>Einen öffentlichen Schlüssel kannst du nicht direkt prüfen.</strong> Sendest du <code>X-API-Key: pk_...</code> an diesen Endpunkt, kommt <code>403</code> zurück, denn ein öffentlicher Schlüssel darf nur <code>/v1/auth/token</code> und <code>/v1/auth/branding</code> aufrufen. Stelle zuerst ein Token aus und prüfe dann das Token. Das ist der eine Fall, in dem ein <code>403</code> hier nicht bedeutet, dass dein Schlüssel defekt ist.
</div>

Mit einem privaten Schlüssel:

```bash
curl https://api.enconvert.com/v1/auth/verify \
  -H "X-API-Key: sk_your_private_key"
```

Mit einem JWT-Token:

```bash
curl https://api.enconvert.com/v1/auth/verify \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```

Antwort:

```json
{
  "authenticated": true,
  "project_id": "12345",
  "tier": "pro",
  "key_type": "public",
  "allowed_domains": ["https://example.com", "https://*.example.com"],
  "allowed_endpoints": ["/v1/convert/url-to-pdf", "/v1/convert/jpeg-to-png"]
}
```

| Feld | Typ | Beschreibung |
|---|---|---|
| `authenticated` | boolean | Immer `true` bei einer gültigen Anfrage |
| `project_id` | string | Deine Projekt-ID |
| `tier` | string | Dein Abonnement-Tarif (z. B. `free`, `starter`, `pro`, `business`) |
| `key_type` | string | `private`, `public` oder `dashboard` |
| `allowed_domains` | Array oder null | Whitelistete Domains (nur öffentliche Schlüssel, sonst `null`) |
| `allowed_endpoints` | Array oder null | Eingeschränkte Endpunkte (nur öffentliche Schlüssel, sonst `null`) |

Zwei Details, über die viele stolpern. `key_type` hat einen dritten Wert, `dashboard`: Den stellt das Backend für eine angemeldete Dashboard- oder Playground-Sitzung aus; wie bei einem privaten Schlüssel sind beide Listen `null`. Und `tier` ist der Plan-Slug, nicht der Name auf der Preisseite: Ein Studio-Abonnement meldet `"tier": "pro"`. Die Slugs `free`, `starter`, `pro`, `business` und `enterprise` entsprechen Founding, Indie, Studio, Production und Enterprise.

Ist der Schlüssel oder das Token ungültig oder abgelaufen, liefert die API stattdessen einen `401 Unauthorized`-Fehler. Die vollständige Liste der Authentifizierungs-Fehlermeldungen steht unter [Fehler](/de/docs/reference/errors.md).

### GET /v1/whoami

Es gibt einen zweiten, kleineren Identitäts-Endpunkt. Er erfordert einen privaten Schlüssel:

```bash
curl https://api.enconvert.com/v1/whoami \
  -H "X-API-Key: sk_your_private_key"
```

```json
{
  "project_id": "12345",
  "plan_slug": "pro"
}
```

Er liefert bewusst nichts weiter: keinen Schlüsseltyp, keine Domains, keine Limits. Ein JWT oder ein öffentlicher Schlüssel erhält `403 GET /v1/whoami requires a private API key (sk_...).` Mehrere Integrationen nutzen ihn als Credential-Test, darunter der n8n-Node.

### Anwendungsfälle

- **API-Schlüssel testen:** Bestätige, dass ein neu erstellter Schlüssel aktiv und korrekt konfiguriert ist.
- **Domain-Beschränkungen prüfen:** Überprüfe, welche Domains für einen öffentlichen Schlüssel whitelistet sind.
- **Authentifizierungsprobleme debuggen:** Ermittle, ob ein Anfragefehler durch die Authentifizierung oder etwas anderes verursacht wird.

---

## Schlüssel sicher aufbewahren {: #keeping-keys-safe }

- **Gehashte Speicherung:** Private Schlüssel werden auf dem Server als SHA-256-Hashes gespeichert. Der Klartextschlüssel wird nur einmal bei der Erstellung angezeigt. Wenn du ihn verlierst, musst du einen neuen Schlüssel generieren.
- **Umgebungsvariablen:** Speichere deinen Schlüssel in einer Umgebungsvariable (z. B. `ENCONVERT_API_KEY`), statt ihn fest in deinem Quellcode zu hinterlegen.
- **Geltungsbereich bei der Erstellung:** `allowed_endpoints` und `allowed_domains` werden bei der Erstellung des Schlüssels festgelegt und lassen sich danach nicht mehr bearbeiten. Entscheide über den Geltungsbereich, bevor du auf Erstellen klickst.

### Einen Schlüssel rotieren

Rotation heißt erst erstellen, dann widerrufen; in dieser Reihenfolge kostet sie keine Downtime:

1. Erstelle den neuen Schlüssel im Dashboard mit dem gewünschten Geltungsbereich.
2. Deploye ihn und bestätige dann mit `GET /v1/auth/verify`, dass der neue Schlüssel aktiv ist.
3. Widerrufe den alten Schlüssel.

Während Schritt 2 funktionieren beide Schlüssel, es gibt also kein Zeitfenster, in dem dein Service nicht authentifiziert ist. Da Beschränkungen unveränderlich sind, ist das Ändern des Geltungsbereichs eines Schlüssels dasselbe Verfahren wie eine Rotation.

### Wenn ein Schlüssel durchsickert

Widerrufe ihn zuerst, ermittle danach das Schadensausmaß. Der Widerruf ist der einzige Notausschalter, denn der Geltungsbereich eines aktiven Schlüssels lässt sich nicht nachträglich einschränken; ein widerrufener Schlüssel wird mit `401 API Key revoked` abgelehnt.

- Ein durchgesickerter **privater Schlüssel** kann jeden Endpunkt aufrufen, für den er freigegeben war, und verbraucht deine monatlichen Ops. Widerrufe ihn, erstelle einen Ersatz und prüfe deine Nutzung im Dashboard auf Aufrufe, die nicht von dir stammen.
- Ein durchgesickerter **öffentlicher Schlüssel** ist per Design weniger dringend. Er kann Konvertierungsendpunkte überhaupt nicht aufrufen und stellt Tokens nur für Origins auf seiner Whitelist aus. Diese Whitelist zu verschärfen bedeutet, einen enger gefassten Schlüssel zu erstellen und den durchgesickerten zu widerrufen, denn die Liste eines bestehenden Schlüssels lässt sich nicht bearbeiten.
- Ein durchgesickertes **Access-Token** stirbt innerhalb einer Stunde und lässt sich nicht von einem anderen Origin aus verwenden als dem, für den es ausgestellt wurde. Das längerlebige Problem ist sein Refresh-Cookie: Die Erneuerung gelingt, solange das Projekt irgendeinen aktiven öffentlichen Schlüssel hat. Den Schlüssel zu widerrufen, aus dem das Token stammt, beendet das Cookie also nicht, es sei denn, es war dein letzter öffentlicher Schlüssel.

<div class="alert alert-warning">
<strong>Ein nach git gepushter Schlüssel ist bereits öffentlich.</strong> Widerrufe ihn im Dashboard, bevor du die History umschreibst. Den Commit zu löschen macht die Veröffentlichung des Schlüssels nicht rückgängig.
</div>

---

## Häufig gestellte Fragen

### Wie authentifiziere ich mich bei einer REST-API mit einem X-API-Key-Header?

Sende deinen privaten Schlüssel bei jeder Anfrage im Header `X-API-Key`, zum Beispiel `X-API-Key: sk_your_private_key`. Private Schlüssel gewähren ganz ohne Token-Austausch vollen Zugriff auf alle Endpunkte (einschließlich synchroner und asynchroner Operationen, Stapelverarbeitung und aller Konvertierungstypen).

### Was ist der Unterschied zwischen sk_ und pk_ API-Schlüsseln?

Schlüssel mit dem Präfix `sk_` sind private Schlüssel für die Server-zu-Server-Nutzung und gewähren vollen API-Zugriff über den Header `X-API-Key`. Schlüssel mit dem Präfix `pk_` sind öffentliche Schlüssel für clientseitige (Browser-)Apps: Sie können die API nicht direkt aufrufen und müssen zuerst über `POST /v1/auth/token` gegen ein kurzlebiges JWT getauscht werden.

### Kann ich meinen privaten API-Schlüssel (sk_) in einem Browser oder einer mobilen App verwenden?

Nein. Die API erkennt den von Browsern gesendeten `Origin`-Header und lehnt Anfragen mit privaten Schlüsseln aus Browser-Umgebungen mit `403 Private API keys cannot be used from browsers` ab. Nutze stattdessen einen öffentlichen Schlüssel (`pk_`) mit dem JWT-Flow für clientseitige Integrationen.

### Wie bekomme ich ein JWT-Bearer-Token für die clientseitige API-Authentifizierung?

Tausche deinen öffentlichen Schlüssel (`pk_`) gegen ein JWT, indem du `POST /v1/auth/token` mit dem Schlüssel im Header `X-API-Key` und `{}` als JSON-Body aufrufst. Nutze das zurückgegebene Token im Header `Authorization: Bearer <token>` für API-Anfragen und erneuere es vor Ablauf über `POST /v1/auth/refresh`.

### Kann ich einen privaten API-Schlüssel auf bestimmte Endpunkte beschränken?

Ja. Setze `allowed_endpoints` bei der Erstellung des Schlüssels und liste Pfade wie `/v1/convert/url-to-pdf` auf. Anfragen an Endpunkte, die nicht auf der Liste stehen, werden mit einem `403 Forbidden`-Fehler abgelehnt, abgesehen von den Auth-, Status-, Download- und jobspezifischen Pfaden, die für jeden Schlüssel erreichbar bleiben.

### Was passiert, wenn ich meinen privaten API-Schlüssel verliere?

Private Schlüssel werden auf dem Server als SHA-256-Hashes gespeichert, und der Klartextschlüssel wird nur einmal bei der Erstellung angezeigt. Wenn du ihn verlierst, musst du einen neuen Schlüssel generieren. Du kannst mehrere Schlüssel erstellen und alte im Dashboard ohne Downtime widerrufen.

### Wie lange sind Access-Tokens und Refresh-Tokens gültig?

Access-Tokens laufen nach 1 Stunde ab und sollten ausschließlich im Arbeitsspeicher gehalten werden. Refresh-Tokens sind 7 Tage gültig und werden als `HttpOnly`-Cookie gesetzt, das vom Browser verwaltet wird.

### Warum schlägt meine Token-Erneuerung ohne credentials: "include" fehl?

Das Refresh-Token wird in einem `HttpOnly`-Cookie gespeichert, das beim ursprünglichen Token-Austausch gesetzt wird, und `POST /v1/auth/refresh` verlässt sich darauf, dass der Browser dieses Cookie automatisch sendet. Wenn du `credentials: "include"` in deinen fetch-Anfragen weglässt, wird das Cookie nicht gespeichert oder gesendet. Falle bei einer fehlgeschlagenen Erneuerung auf eine vollständige Neuauthentifizierung mit deinem öffentlichen Schlüssel zurück.

### Kann ich Wildcard-Subdomains in der Domain-Whitelist verwenden?

Ja. `https://*.example.com` stimmt mit `https://app.example.com`, `https://staging.example.com` und der Apex-Domain `https://example.com` überein. Exakte Hosts und portspezifische Origins wie `http://localhost:3000` werden ebenfalls unterstützt. Der Abgleich ignoriert das Schema, nicht aber den Port.

### Kann ich einen öffentlichen Schlüssel für asynchrone oder Batch-Konvertierungen verwenden?

Nein. Die Public-Key-Authentifizierung unterstützt nur synchrone Konvertierungsendpunkte mit einer einzelnen URL oder Datei pro Anfrage; der Async-Modus, Webhooks und das Polling des Batch-Status erfordern einen privaten Schlüssel. Antworten liefern eine `presigned_url` zum sofortigen Download.

### Wie teste ich, ob mein API-Schlüssel gültig ist?

Sende eine Anfrage an `GET /v1/auth/verify` mit einem privaten Schlüssel im Header `X-API-Key` oder einem JWT im Header `Authorization`. Ein gültiges Credential liefert `authenticated: true` zusammen mit deiner `project_id` und `tier`; ein ungültiges oder abgelaufenes liefert `401 Unauthorized`. Ein öffentlicher Schlüssel lässt sich so nicht prüfen und liefert `403`.

### Warum sind allowed_domains und allowed_endpoints in der verify-Antwort null?

Beide Felder werden nur für öffentliche Schlüssel befüllt und liefern `null` für private Schlüssel und für Dashboard-Sitzungen. Bei öffentlichen Schlüsseln listet `allowed_domains` die whitelisteten Domains auf, und `allowed_endpoints` listet etwaige Endpunkt-Beschränkungen auf.

### Welche Authentifizierungsmethode sollte ich für meine Integration wählen?

Nutze einen privaten Schlüssel (`sk_`) für Backend-Services, Skripte oder interne Tools, denn er ist einfacher und gewährt vollen Zugriff. Nutze einen öffentlichen Schlüssel (`pk_`) mit JWT für browserbasierte Apps oder Widgets, da er Credentials sicher hält und den Zugriff auf zugelassene Domains beschränkt.
