Public API

This page extends the OpenAPI UI with ready-to-use requests, example responses, and notes about stable image URLs.

Schnellstart

These four endpoints are enough for most integrations.

Account
GET /user/profile

Liefert Profil, Limits und Nutzungswerte des Users.

Upload Session
POST /upload/sessions

Erstellt Upload-URL, Token und maximale Dateigröße.

Processing
GET /images/{uuid}/status

Zeigt, ob die Verarbeitung abgeschlossen oder fehlgeschlagen ist.

Finale Resource
GET /images/{uuid}

Liefert die finalen Bild- und Varianten-URLs.

1) Authentifizierung

Erstelle zuerst in den Benutzereinstellungen einen API-Schlüssel und sende ihn pro Request im Header X-API-Key.

export BASE_URL="https://pixelfox.cc"
export API_KEY="DEIN_API_KEY"

curl -sS "$BASE_URL/api/v1/user/profile" \
  -H "X-API-Key: $API_KEY" | jq

Alternative Header-Variante: Authorization: Bearer DEIN_API_KEY.

2) Upload-Session erzeugen

Vor jedem Upload wird ein zeitlich begrenztes Token ausgestellt. Zusätzlich kannst du pro Upload festlegen, welche Derivate erzeugt werden sollen. Das Original bleibt immer erhalten. Mit album_id kannst du den Upload direkt in ein bestehendes eigenes Album einsortieren. Mit is_nsfw markierst du den Upload als sensiblen Inhalt.

export FILE="./beispiel.jpg"
export FILE_SIZE=$(wc -c < "$FILE")
export ALBUM_ID=42

SESSION_JSON=$(curl -sS -X POST "$BASE_URL/api/v1/upload/sessions" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d "{
    \"file_size\": $FILE_SIZE,
    \"album_id\": $ALBUM_ID,
    \"is_nsfw\": true,
    \"processing\": {
      \"profile\": \"custom\",
      \"derivatives\": [
        { \"family\": \"webp\", \"size\": \"small\" },
        { \"family\": \"avif\", \"size\": \"small\" }
      ]
    }
  }")

echo "$SESSION_JSON" | jq
UPLOAD_URL=$(echo "$SESSION_JSON" | jq -r '.upload_url')
UPLOAD_TOKEN=$(echo "$SESSION_JSON" | jq -r '.token')

Lasse processing weg, wenn du die aktuellen User-Defaults verwenden willst. Mit profile=original_only wird nur das Original gespeichert. Lasse album_id weg, wenn der Upload keinem Album zugeordnet werden soll. Lasse is_nsfw weg, um den NSFW-Default aus deinen Einstellungen zu verwenden, oder setze es explizit auf false bzw. true für diesen Upload.

Processing-Profile

ProfileVerhalten
defaultUses the current user defaults from settings and freezes them for this exact upload session.
original_onlyStores only the uploaded original. No additional WebP, AVIF, or original thumbnails are generated.
customErzeugt exakt die in derivatives angeforderten Derivate, sofern Plan und Admin-Settings sie erlauben.
// Gleichbedeutend mit den aktuellen User-Defaults
{ "file_size": 1837421 }

// In ein bestehendes Album hochladen
{
  "file_size": 1837421,
  "album_id": 42,
  "is_nsfw": true
}

// Nur das Original behalten
{
  "file_size": 1837421,
  "processing": {
    "profile": "original_only"
  }
}

// Exakt kleine WebP- und AVIF-Varianten anfordern
{
  "file_size": 1837421,
  "processing": {
    "profile": "custom",
    "derivatives": [
      { "family": "webp", "size": "small" },
      { "family": "avif", "size": "small" }
    ]
  }
}

In Antworten kann bei älteren Bildern zusätzlich profile=legacy auftauchen. Das ist nur ein Rückwärtskompatibilitätswert für Uploads vor der neuen per-Request-Policy.

3) Bild hochladen und Resource abrufen

Lade die Datei per Multipart-Form an die upload_url aus Schritt 2 hoch. Anschließend prüfst du den Status und holst dir die finale Resource.

UPLOAD_RESULT=$(curl -sS -X POST "$UPLOAD_URL" \
  -H "Authorization: Bearer $UPLOAD_TOKEN" \
  -F "file=@$FILE")

echo "$UPLOAD_RESULT" | jq
IMAGE_UUID=$(echo "$UPLOAD_RESULT" | jq -r '.image_uuid')

curl -sS "$BASE_URL/api/v1/images/$IMAGE_UUID/status" \
  -H "X-API-Key: $API_KEY" | jq

curl -sS "$BASE_URL/api/v1/images/$IMAGE_UUID" \
  -H "X-API-Key: $API_KEY" | jq

Wiederhole den Status-Request, bis complete=true und failed=false zurückkommt.

Immediately after upload, often only the original file and some variants are available. Additional derivatives are generated asynchronously.

Beispiel: GET /api/v1/user/profile

{
  "id": 42,
  "username": "pixelpete",
  "email": "[email protected]",
  "status": "active",
  "plan": "premium",
  "created_at": "2026-03-16T08:30:00Z",
  "last_login_at": "2026-03-16T09:12:44Z",
  "api_key_last_used_at": "2026-03-16T09:15:02Z",
  "stats": {
    "images": {
      "count": 128,
      "storage_used_bytes": 734003200,
      "storage_remaining_bytes": 2415919104
    },
    "albums": {
      "count": 7
    }
  },
  "limits": {
    "max_upload_bytes": 52428800,
    "storage_quota_bytes": 3149926400,
    "can_multi_upload": true,
    "image_upload_enabled": true,
    "direct_upload_enabled": true,
    "allowed_thumbnail_formats": ["original", "webp", "avif"]
  },
  "preferences": {
    "thumbnail_original": true,
    "thumbnail_webp": true,
    "thumbnail_avif": true
  }
}

Account-Daten, Limits und aktuelle Nutzung des authentifizierten Users.

Beispiel: POST /api/v1/upload/sessions

{
  "upload_url": "https://pixelfox.cc/api/v1/upload",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "pool_id": 2,
  "album_id": 42,
  "is_nsfw": true,
  "expires_at": 1773655200,
  "max_bytes": 52428800,
  "processing": {
    "profile": "custom",
    "keep_original": true,
    "derivatives": [
      { "family": "webp", "size": "small" },
      { "family": "avif", "size": "small" }
    ]
  }
}

Das Token ist nur kurz gültig, auf die Dateigröße begrenzt und trägt die konkrete Processing-Policy dieser Session. Wenn album_id zurückkommt, ist die Session an dieses Album gebunden. is_nsfw zeigt an, ob der Upload als sensibel markiert wurde.

Beispiel: Upload-Antwort

{
  "image_uuid": "fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8",
  "view_url": "/i/a1b2c3d4e5f6g7h8",
  "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/original.jpg",
  "stable_url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/original.jpg",
  "storage_url": "https://cdn.pixelfox.cc/uploads/original/2026/03/16/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8.jpg",
  "is_nsfw": true,
  "available_variants": ["original"],
  "processing": {
    "profile": "custom",
    "keep_original": true,
    "derivatives": [
      { "family": "webp", "size": "small" },
      { "family": "avif", "size": "small" }
    ]
  },
  "variants": {
    "original": {
      "original": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/original.jpg"
      }
    }
  },
  "stable_variants": {
    "original": {
      "original": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/original.jpg",
        "ready": true
      }
    },
    "webp": {
      "small": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/webp/small.webp",
        "ready": false
      }
    },
    "avif": {
      "small": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/avif/small.avif",
        "ready": false
      }
    }
  },
  "planned_variants": ["original", "webp", "avif"],
  "duplicate": false
}

Direkt nach dem Upload ist die Resource bereits adressierbar. Fertige Varianten erscheinen schrittweise in variants.

Beispiel: Status und finale Image-Resource

// GET /api/v1/images/{uuid}/status
{
  "complete": true,
  "failed": false,
  "view_url": "/i/a1b2c3d4e5f6g7h8"
}

// GET /api/v1/images/{uuid}
{
  "image_uuid": "fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8",
  "view_url": "/i/a1b2c3d4e5f6g7h8",
  "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/original.jpg",
  "stable_url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/original.jpg",
  "storage_url": "https://cdn.pixelfox.cc/uploads/original/2026/03/16/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8.jpg",
  "is_nsfw": true,
  "available_variants": ["original", "webp", "avif"],
  "processing": {
    "profile": "custom",
    "keep_original": true,
    "derivatives": [
      { "family": "webp", "size": "small" },
      { "family": "avif", "size": "small" }
    ]
  },
  "variants": {
    "original": {
      "original": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/original.jpg"
      }
    },
    "webp": {
      "small": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/webp/small.webp"
      }
    },
    "avif": {
      "small": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/avif/small.avif"
      }
    }
  },
  "stable_variants": {
    "original": {
      "original": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/original.jpg",
        "ready": true
      },
      "medium": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/medium.jpg",
        "ready": true
      },
      "small": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/original/small.jpg",
        "ready": true
      }
    },
    "webp": {
      "original": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/webp/original.webp",
        "ready": true
      },
      "medium": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/webp/medium.webp",
        "ready": true
      },
      "small": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/webp/small.webp",
        "ready": true
      }
    },
    "avif": {
      "medium": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/avif/medium.avif",
        "ready": false
      },
      "small": {
        "url": "https://pixelfox.cc/f/fd6b0a44-c4bb-4c95-8f48-31d5ec9cc4d8/avif/small.avif",
        "ready": false
      }
    }
  },
  "planned_variants": ["original", "webp", "avif"]
}

/status ist bewusst klein gehalten. Für alle fertigen URLs und Varianten verwendest du danach GET /api/v1/images/{uuid}.

PHP Beispiele

For simple server integrations, the PHP cURL extension is enough. The first example shows an authenticated JSON request, the second the full upload flow.

GET /api/v1/user/profile

<?php

$baseUrl = 'https://pixelfox.cc';
$apiKey = 'DEIN_API_KEY';

$ch = curl_init($baseUrl . '/api/v1/user/profile');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'X-API-Key: ' . $apiKey,
    ],
]);

$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException('API request failed: ' . $raw);
}

$profile = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
echo $profile['username'] . PHP_EOL;
echo $profile['limits']['max_upload_bytes'] . PHP_EOL;

Upload Flow mit Status-Polling

<?php

$baseUrl = 'https://pixelfox.cc';
$apiKey = 'DEIN_API_KEY';
$file = __DIR__ . '/beispiel.jpg';

function apiJson(string $method, string $url, string $apiKey, ?array $payload = null): array
{
    $headers = [
        'Accept: application/json',
        'X-API-Key: ' . $apiKey,
    ];

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => $headers,
    ]);

    if ($payload !== null) {
        $headers[] = 'Content-Type: application/json';
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_THROW_ON_ERROR));
    }

    $raw = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    return [
        'status' => $status,
        'body' => json_decode($raw, true, 512, JSON_THROW_ON_ERROR),
    ];
}

$session = apiJson('POST', $baseUrl . '/api/v1/upload/sessions', $apiKey, [
    'file_size' => filesize($file),
    'album_id' => 42,
    'is_nsfw' => true,
]);

$upload = curl_init($session['body']['upload_url']);
curl_setopt_array($upload, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $session['body']['token'],
    ],
    CURLOPT_POSTFIELDS => [
        'file' => new CURLFile($file),
    ],
]);

$uploadRaw = curl_exec($upload);
curl_close($upload);

$uploadResult = json_decode($uploadRaw, true, 512, JSON_THROW_ON_ERROR);
$imageUuid = $uploadResult['image_uuid'];

do {
    sleep(1);
    $status = apiJson('GET', $baseUrl . '/api/v1/images/' . $imageUuid . '/status', $apiKey);
} while (!$status['body']['complete'] && !$status['body']['failed']);

if ($status['body']['failed']) {
    throw new RuntimeException('Image processing failed');
}

$image = apiJson('GET', $baseUrl . '/api/v1/images/' . $imageUuid, $apiKey);
echo $image['body']['url'] . PHP_EOL;

Konsistente Bild-URLs verstehen

PixelFox intentionally returns two URL types. You can use stable public links or directly access the current storage address.

FeldBedeutung
view_urlRelative URL zur HTML-Ansicht der Bildseite, zum Beispiel /i/abc123....
urlPrimäre öffentliche Bild-URL. In der Regel identisch zu stable_url und für Embeds, Hotlinks und API-Consumer die beste Standardwahl.
stable_urlStabile /f/{uuid}/...-Adresse. Diese URL bleibt konsistent, auch wenn sich die interne Storage-Struktur oder das Zielsystem später ändert.
variantsOnly the currently ready variants with stable public URLs.
processingDie effektive Processing-Policy dieses Uploads. Dort siehst du, ob der Upload mit default, original_only or custom erstellt wurde und welche Derivate konkret dazugehören.
stable_variantsVorhersagbare stabile Variant-URLs inklusive ready-Flag. Damit kannst du feste URLs vorhalten und trotzdem erkennen, ob die Datei schon existiert.
storage_urlDirekte aktuelle Storage-URL der Originaldatei. Praktisch für Debugging oder interne Tools, aber weniger zukunftssicher als stable_url.
storage_variantsDirekte Storage-URLs aller bereits erzeugten Varianten. Gleiches Prinzip wie storage_url.
planned_variantsWhich variant families are generally intended for this image according to the stored processing policy.

Recommendation for integrations

Verwende standardmäßig url oder die Einträge aus variants. Wenn du mit noch nicht fertigen Derivaten planst, nimm stable_variants und prüfe das jeweilige ready-Flag.

Wichtiger Unterschied

/f/... ist eine stabile öffentliche Adresse, die auf die aktuelle Datei weiterleitet. /uploads/... oder CDN-Pfade können sich dagegen mit Storage-Pools, Migrationen oder künftigen Infrastruktur-Änderungen ändern.

OpenAPI