Collector API
REST-API fuer die Browser Extension, die Instagram-Profile scraped und Ergebnisse zurueckliefert.
Authentifizierung
Die API nutzt Laravel Sanctum Token-Authentifizierung mit erweitertem Token-Lifecycle (Soft-Revoke, IP-Whitelist, Usage-Tracking).
Token erstellen
Tokens werden ueber die Admin-Seite /admin/api-management oder per Artisan-Command erstellt:
php artisan collector:token "Chrome Extension 1"
# Gibt den Plaintext-Token aus: 1|abc123def456...
Der Token wird im Authorization-Header mitgesendet:
Authorization: Bearer 1|abc123def456...
Middleware-Chain
auth:sanctum → RejectRevokedTokens → throttle:collector → TrackApiTokenUsage
| Middleware | Pruefung |
|---|---|
auth:sanctum | Token existiert und ist nicht abgelaufen |
RejectRevokedTokens | Token nicht revoziert (revoked_at IS NULL) + IP-Whitelist |
throttle:collector | 600 Requests pro Minute pro Client |
TrackApiTokenUsage | Stuendliche Nutzungs-Aggregation in api_token_usage_logs |
Token-Lifecycle
stateDiagram-v2
[*] --> Active: Token erstellt
Active --> Revoked: Admin deaktiviert
Revoked --> Deleted: Admin loescht
Active --> Expired: expires_at erreicht
note right of Active: API-Zugriff erlaubt
note right of Revoked: API-Zugriff blockiert (401)
note right of Expired: API-Zugriff blockiert (Sanctum)
IP-Whitelist
Tokens koennen optional auf bestimmte IPs beschraenkt werden (allowed_ips JSON-Array). Leere/null Whitelist erlaubt alle IPs. Requests von nicht-gelisteten IPs erhalten 403 Forbidden.
Token-Management
Admin-Dashboard unter /admin/api-management:
- Token erstellen (Name, Abilities, Ablaufdatum, IP-Whitelist)
- Token deaktivieren (Soft-Revoke mit
revoked_at+revoked_by) - Token loeschen (nur revozierte, inkl. verwaister CollectorClients)
- IP-Whitelist bearbeiten
- Nutzungs-Charts (24h, 7d, 30d) pro Token und gesamt
- Collector-Client Health-Status
Deaktivierte Clients: Collector-Clients ohne aktive Tokens (alle revoziert) werden automatisch aus der API-Liste, dem Update-Status-Dashboard und dem Health-Check (/up_system) gefiltert. Der withActiveTokens-Scope auf CollectorClient verwendet einen expliziten uuid::text Cast für PostgreSQL-Kompatibilität (SQLite toleriert den Typ-Unterschied zwischen UUID und varchar).
Location: routes/api.php, app/Livewire/Admin/ApiManagement/Index.php, app/Services/ApiTokenService.php
Endpoints
POST /api/collector/jobs/lease
Least den naechsten verfuegbaren Job. Jobs mit hoeherer Prioritaet werden bevorzugt, danach FIFO. Parallele Collectors blockieren sich nicht gegenseitig dank FOR UPDATE SKIP LOCKED (PostgreSQL) — jeder Collector ueberspringt bereits gelockte Rows und bekommt sofort den naechsten verfuegbaren Job. Fallback auf normales FOR UPDATE fuer SQLite (Tests).
Request:
{
"source": "instagram"
}
Response (200 - Job verfuegbar):
{
"job": {
"id": "9c3f4a2b-...",
"source": "instagram",
"status": "leased",
"priority": 30,
"payload": {
"job_type": "watcher_import",
"run_id": 22,
"workspace_id": 1,
"created_by": 1,
"input_url": "https://www.instagram.com/example/",
"dedupe_key": "handle:example"
},
"lease_expires_at": "2026-02-09T14:30:00Z"
}
}
Response (200 - kein Job verfuegbar):
{
"job": null
}
Expired Leases werden automatisch beim naechsten lease-Call recycled: Jobs mit Status leased deren lease_expires_at in der Vergangenheit liegt, werden erneut vergeben.
Location: app/Http/Controllers/Api/CollectorJobController.php (Methode lease)
GET /api/collector/jobs/stats
Queue-Statistiken gruppiert nach Plattform.
Response:
{
"stats": {
"instagram": {
"queued": 142,
"leased": 3,
"total": 145
},
"youtube": {
"queued": 0,
"leased": 0,
"total": 0
}
}
}
Location: app/Http/Controllers/Api/CollectorJobController.php (Methode stats)
POST /api/collector/jobs/{job}/complete
Markiert einen geleasten Job als abgeschlossen und uebergibt das Scrape-Ergebnis.
Request (watcher_import -- oeffentliches Profil):
{
"result": {
"handle": "example",
"canonical_url": "https://www.instagram.com/example/",
"followers_count": 15420,
"title": "Example Creator",
"is_private": false,
"is_verified": true,
"profile_type": "creator",
"following_count": 312,
"post_count": 847,
"thumbnail_url": "https://scontent-xxx.cdninstagram.com/...",
"profile_data": { "is_private": false },
"daily_raw": { "post_count": 847 }
}
}
Required Fields (immer):
| Feld | Typ | Beschreibung |
|---|---|---|
handle | string | Instagram-Handle (ohne @) |
canonical_url | string | Kanonische Profil-URL |
followers_count | integer | Follower-Anzahl |
title | string | Display-Name |
is_private | boolean | Privates Profil? |
is_verified | boolean | Verifiziertes Profil? |
profile_type | string | creator, business, private, personal |
Required Fields (nur bei oeffentlichen Profilen, is_private=false):
| Feld | Typ | Beschreibung |
|---|---|---|
following_count | integer | Anzahl gefolgter Profile |
post_count | integer | Anzahl der Posts |
Private Profile Handling
Private Profile liefern eingeschraenkte Daten:
{
"result": {
"handle": "private_user",
"canonical_url": "https://www.instagram.com/private_user/",
"followers_count": 200,
"title": "Private User",
"is_private": true,
"is_verified": false,
"profile_type": "private",
"following_count": null,
"post_count": null
}
}
Location: app/Services/Collector/InstagramWatcherImportProcessor.php (Methode validateResult)
Payload-Unterschiede: watcher_import vs. daily_scrape
| Feld | watcher_import | daily_scrape |
|---|---|---|
job_type | "watcher_import" | "daily_scrape" |
run_id | Import-Run-ID | -- |
workspace_id | Ziel-Workspace | -- |
created_by | User-ID | -- |
social_profile_id | -- | Profil-ID |
input_url | Instagram-URL | Instagram-URL |
dedupe_key | "handle:xxx" | -- |
Response (200):
{
"job": {
"id": "9c3f4a2b-...",
"status": "completed",
"result": { ... }
}
}
Error Responses:
| Code | Beschreibung |
|---|---|
| 409 | Job nicht im Status leased, Lease abgelaufen, oder anderer Client |
| 422 | Validierungsfehler im Result-Payload |
POST /api/collector/jobs/{job}/fail
Markiert einen geleasten Job als fehlgeschlagen.
Request:
{
"error_code": "scrape_error",
"error_message": "Login required - session expired"
}
Error Codes:
| Code | Beschreibung | Re-Queue? |
|---|---|---|
scrape_error | Allgemeiner Scrape-Fehler | Ja (ausser 404) |
rate_limited | Instagram Rate Limit | Nein |
not_found | Profil existiert nicht | Nein |
Re-Queue-Logik: Jobs mit scrape_error werden am Ende der Queue erneut eingereiht (Prioritaet 0), ausser die Fehlermeldung enthaelt not found, user not found, http 404 oder status 404 -- diese werden permanent als fehlgeschlagen markiert.
Location: app/Http/Controllers/Api/CollectorJobController.php (Methode fail)
POST /api/collectors/heartbeat
Aktualisiert den last_seen_at-Timestamp des Collector-Clients.
Request: Leerer Body genuegt.
Response (200):
{
"message": "ok"
}
Location: app/Http/Controllers/Api/CollectorClientController.php
Token Health Monitoring (Plan 37)
Per-Token-Monitoring das fehlerhafte Collector-Tokens automatisch erkennt, pausiert und nach Selbstheilung wieder aktiviert.
Funktionsweise
- Error-Tracking: Jeder
complete()/fail()Call inkrementiert Cache-Counter pro Collector-Client (Success/Fail/Error-Code-Breakdown). - Evaluation:
collector:evaluate-token-health(alle 5 Min) berechnet die Fehlerrate im Rolling Window (60 Min, min. 20 Requests). - Auto-Suspension: Bei >40% Fehlerrate wird der Token suspendiert — der Lease-Endpoint gibt HTTP 429 mit
Retry-After: 900zurueck. - Re-Test: 5 Test-Jobs werden an den suspendierten Token gesendet. Bei 80% Erfolg wird der Token automatisch reaktiviert.
- Re-Test-Isolation (F3): Re-Test-Jobs zaehlen nur im Retest-Tracker, nicht in der regulaeren Health-Statistik. Dadurch koennen Re-Test-Failures die Fehlerrate nicht weiter verschlechtern.
Schwellwerte
| Level | Fehlerrate | Aktion |
|---|---|---|
| Healthy | <20% | Keine |
| Warning | 20-40% | Admin-Alarm (Toast) |
| Critical | >40% | Auto-Suspension + E-Mail-Alarm + Job-Umverteilung |
Zusaetzlich: Error-Code-spezifische Schwellwerte (tab_closed >30%, parse_error >10%, profile_not_found >5%, scrape_error >25%).
Anomalie-Erkennung
Z-Score-basiert: Vergleich der aktuellen Fehlerrate mit dem 24h-Durchschnitt. Alarm bei Z-Score >2 (2 Standardabweichungen ueber Mittel).
Fehler-Korrelation
Erkennt ob Fehler collector-spezifisch oder plattformweit sind. Wenn >50% aller aktiven Collectors gleichzeitig >30% Fehlerrate haben → "Plattform-Problem"-Alarm statt individueller Token-Suspension.
Config
// config/collector.php → token_health
'warning_threshold' => 0.20,
'critical_threshold' => 0.40,
'min_sample_size' => 20,
'evaluation_window_minutes' => 60,
'suspension_ttl_hours' => 24,
'retest_job_count' => 5,
'retest_success_threshold' => 0.80,
Location: app/Services/Collector/CollectorTokenHealthService.php, app/Console/Commands/EvaluateTokenHealth.php
Bonus Scraping (Plan 36)
Wenn alle taeglichen Pflicht-Scrapes abgearbeitet sind, nutzt das System die verbleibende Collector-Kapazitaet um Profile aus zukuenftigen Rotation-Buckets vorab zu scrapen.
Funktionsweise
- Idle-Detection: Queue-basiert — zaehlt offene Priority- und Low-Priority-Jobs. System gilt als idle wenn beide unter konfigurierbaren Schwellwerten liegen.
- Kandidaten-Auswahl: Profile aus zukuenftigen
rotation_buckets, sortiert nach Bucket-Proximity und Follower-Count. - Dispatch: Jobs mit
job_type=bonus_scrape,priority=-10,low_priority=true— Duty-Jobs haben immer Vorrang. - Metriken-Isolation: Bonus-Scrapes inkrementieren
DailyScrapeProgressnicht, damit die regulaere Fortschrittsanzeige korrekt bleibt.
Tageslimit
Globaler Cache-Counter bonus_scrapes:{date} begrenzt die taegliche Anzahl (Default: max_daily_jobs in Config). Wird von Instagram und YouTube Bonus-Scraping geteilt.
YouTube Bonus
Eigener Command social:bonus-youtube-scrapes (alle 2h, 10-22 UTC). Prueft >20% Pool-Quota-Budget vor Dispatch. Re-prueft Quota alle 100 Profile.
Collector Exclusion
Bei transientem Fehler wird excluded_collector_id gesetzt — ein anderer Collector uebernimmt den Job. Nach dem 2. Retry wird die Exclusion aufgehoben um Starvation bei nur 1 aktivem Collector zu verhindern.
Config
// config/postbox.php → bonus_scraping
'enabled' => true,
'min_hour_utc' => 8,
'max_daily_jobs' => 500,
'idle_threshold_priority' => 5,
'idle_threshold_lowprio' => 20,
'job_priority' => -10,
'chunk_size' => 25,
Location: app/Services/Collector/BonusScrapeService.php, app/Console/Commands/QueueBonusYouTubeScrapes.php
Admin-Features
Platform Filter
Admins koennen die Collector Queue und Logs nach Plattform filtern (all, instagram, youtube).
Priority Boost
Einzelne Jobs koennen per Admin-UI in der Prioritaet hochgestuft werden: priority = max(queued priority) + 1. Der Job wird damit als naechstes geleased.
Rescrape Shortcut
Admins koennen in /admin/social-profiles direkt ein priorisiertes Rescrape ausloesen -- der zugehoerige Watcher wird in die Prioritaets-Queue gestellt.
Location: app/Livewire/Admin/LogQueue/Index.php, app/Livewire/Admin/SocialProfiles/Index.php