Insights · Engineering · 2026-05-30 · 9 Min Lesezeit

Recruitee API: Bewerber automatisch aus Google Sheets anlegen

Wie aus einer Tabellenzeile ein Kandidat im Recruitee-Talent-Pool wird, ohne Copy-Paste und ohne Dubletten

Bewerbungen landen oft erst in einer Google-Tabelle, etwa aus einem Formular. Statt sie von Hand in Recruitee zu tippen, legt ein kleines Skript sie automatisch als Kandidaten an. Dieser Artikel zeigt Sheet als Quelle, den Recruitee-Candidate-Endpoint, das Feld-Mapping, Dubletten-Schutz und einen Cron-Lauf.

Warum die Tabelle oft der wahre Eingang ist

In vielen Teams kommt eine Bewerbung nicht direkt im Bewerbermanagement an, sondern zuerst in einer Google-Tabelle. Ein Bewerbungsformular auf der Website schreibt seine Einsendungen dorthin, eine Messe-Liste wird in ein Sheet getippt, ein Empfehlungsformular sammelt Kandidaten. Das Sheet ist niedrigschwellig und für alle sichtbar, deshalb landet es oft am Anfang der Kette.

Der Bruch kommt danach: Jemand muss diese Zeilen von Hand in Recruitee übertragen. Das kostet Zeit, ist fehleranfällig und passiert oft zu spät, sodass gute Kandidaten kalt werden. Genau diese Lücke schließt ein kleines Sync-Skript.

Die Idee ist schlicht: Das Sheet ist die Quelle, Recruitee das Ziel. Ein Skript liest neue Zeilen, mappt die Felder auf das Recruitee-Kandidatenmodell und legt sie über die API an. Was vorher Copy-Paste war, läuft dann unbeaufsichtigt.

Das Sheet als saubere Datenquelle

Damit ein Skript die Tabelle lesen kann, brauchst du programmatischen Zugriff. Der saubere Weg führt über einen Service-Account in der Google Cloud Console. Du erstellst ihn, lädst seinen JSON-Key herunter und teilst die Zieltabelle mit der E-Mail-Adresse des Service-Accounts, so als wäre er ein Kollege.

Im Skript liest du dann mit der google-api-python-client-Bibliothek einen Bereich aus, etwa Tabelle1!A2:F, und bekommst die Zeilen als Liste von Listen zurück. Wichtig ist eine feste Spaltenordnung: Vorname, Nachname, E-Mail, Telefon, Stelle, Status. So weißt du, welcher Index welches Feld trägt.

Zwei Dinge zahlen sich hier aus. Erstens eine Statusspalte, in die das Skript nach dem Anlegen einen Marker schreibt, damit dieselbe Zeile nicht erneut verarbeitet wird. Zweitens eine grobe Validierung: Zeilen ohne E-Mail oder ohne Namen überspringst du, statt unvollständige Kandidaten anzulegen.

Der Recruitee-Candidate-Endpoint

Recruitee bietet eine REST-API. Kandidaten legst du per POST an https://api.recruitee.com/c/COMPANY_ID/candidates an. Authentifiziert wird per Bearer-Token im Authorization-Header, das Token kommt aus einer Umgebungsvariable.

Der Payload ist ein JSON-Objekt mit einem candidate-Schlüssel. Darin stehen mindestens Name und E-Mail, optional Telefon und weitere Felder. Eine minimale Struktur sieht so aus: {"candidate": {"name": "Anna Beispiel", "emails": ["[email protected]"], "phones": ["+4915100000000"]}}. Beachte, dass emails und phones Listen sind, nicht einzelne Strings.

Willst du den Kandidaten direkt einer Stelle zuordnen, hängst du beim Anlegen oder in einem zweiten Call die Offer-ID an. Die Antwort der API enthält die neue Candidate-ID, die du dir merken solltest, etwa um sie zurück ins Sheet zu schreiben. So hast du eine Brücke zwischen Tabellenzeile und Recruitee-Datensatz.

Felder sauber mappen

Mapping klingt trivial, ist aber die Stelle, an der die meisten Syncs später kippen. Eine Tabellenspalte heißt anders als das API-Feld, ein Format passt nicht, ein Pflichtfeld fehlt. Deshalb lohnt es sich, das Mapping explizit als eine Stelle im Code zu führen.

Konkret übersetzt du je Zeile:

  • Vorname und Nachname zu einem zusammengesetzten name.
  • E-Mail in die Liste emails, vorher getrimmt und kleingeschrieben.
  • Telefon in die Liste phones, möglichst ins E.164-Format gebracht.
  • Stelle aus dem Sheet auf eine Recruitee-Offer-ID, idealerweise über eine kleine Lookup-Tabelle im Code.

Halte das Mapping an einer einzigen Stelle, dann kannst du Spalten ergänzen, ohne den Rest anzufassen. Wenn das Mapping wächst, etwa mit Quelle, Tags oder Notizen, ist das ein guter Moment, die Logik in eine kleine Automatisierung zu gießen, die auch Sonderfälle sauber behandelt.

Dubletten-Schutz als harte Regel

Der gefährlichste Fehler bei jedem Sheet-zu-API-Sync ist die Dublette. Läuft das Skript zweimal über dieselbe Zeile, oder steht ein Bewerber versehentlich doppelt im Sheet, hast du ohne Schutz zwei Kandidaten in Recruitee. Das verwässert deinen Talent-Pool und nervt die Recruiter.

Der Schutz hat zwei Ebenen. Erstens auf Sheet-Seite: Nach erfolgreichem Anlegen schreibt das Skript einen Marker in die Statusspalte, etwa die Candidate-ID oder ein simples done. Beim nächsten Lauf überspringst du jede Zeile, die bereits einen Marker trägt.

Zweitens auf Recruitee-Seite: Vor dem Anlegen prüfst du, ob die E-Mail schon als Kandidat existiert, etwa über die Suche der API. Existiert sie, legst du nicht neu an, sondern überspringst oder aktualisierst. Diese zweite Ebene fängt auch Fälle ab, in denen der Sheet-Marker verloren ging. Zusammen machen beide Ebenen den Lauf idempotent: Egal wie oft er läuft, jeder Bewerber landet genau einmal in Recruitee.

Cron und der unbeaufsichtigte Betrieb

Damit der Sync ohne Klick läuft, hängst du ihn an einen Zeitplan. Auf einem Server reicht ein klassischer Cron-Eintrag, etwa stündlich oder alle paar Minuten, je nach Aufkommen. Auf macOS nimmt man dafür gern einen launchd-Job, in der Cloud einen Scheduled Worker oder eine kleine Function mit Zeitauslöser.

Drei Dinge entscheiden, ob der unbeaufsichtigte Betrieb ruhig bleibt. Erstens Logging: Jeder Lauf schreibt mit, wie viele Zeilen gelesen, übersprungen und angelegt wurden, damit du Fehler siehst, ohne live zuzuschauen. Zweitens Fehlertoleranz: Ein einzelner kaputter Datensatz darf nicht den ganzen Lauf abbrechen, er wird übersprungen und protokolliert. Drittens Überlappungsschutz: Zwei gleichzeitige Läufe dürfen sich nicht in die Quere kommen, ein simples Lock verhindert das.

So entsteht aus einer Tabelle ein verlässlicher Eingang in dein Recruiting, der von allein arbeitet. Genau solche Syncs baue ich bei adsbird als Festpreis-Modul: eine Person plant, baut und übergibt, der Code bleibt dein Eigentum. Wenn du deine Bewerber-Tabelle an Recruitee koppeln willst, schreib an [email protected] oder schau unter Preise.

Häufige Fragen

Bevor du fragst.

Brauche ich für die Recruitee API einen speziellen Zugang?
Du brauchst ein Personal API Token und deine Company-ID. Beides findest du in den Recruitee-Einstellungen unter dem API-Bereich. Das Token gehört in eine Umgebungsvariable, nicht in den Code. Damit rufst du den Endpoint deiner Company auf. Wenn du das in eine größere API-Pipeline einbetten willst, plane ich das Token-Handling gleich mit.
Wie lese ich Google Sheets programmatisch aus?
Über die Google Sheets API mit einem Service-Account. Du legst in der Google Cloud Console einen Service-Account an, lädst dessen JSON-Key herunter und teilst die Tabelle mit der Service-Account-E-Mail. Dann liest du die Zeilen mit der google-api-python-client-Bibliothek aus.
Wie verhindere ich, dass ein Bewerber doppelt angelegt wird?
Mit einem Dubletten-Schutz auf Basis eines eindeutigen Merkmals, meist der E-Mail. Du markierst verarbeitete Zeilen im Sheet oder pflegst eine kleine Statusspalte, und vor dem Anlegen prüfst du, ob die E-Mail schon als Kandidat existiert. So bleibt der Lauf idempotent. Fragen dazu an [email protected].
Wie oft sollte der Sync laufen?
Das hängt von deinem Bewerbungsaufkommen ab. Für die meisten Teams reicht ein Cron-Lauf alle paar Minuten oder stündlich. Wichtiger als die Frequenz ist, dass der Lauf idempotent ist und sich Überschneidungen nicht in die Quere kommen. Eine durchdachte Automatisierung plane ich passend zum Volumen.

Weiterlesen

Verwandte
Artikel.

Tiefer in angrenzende Themen, Architektur, Tools, Praxis.

Konkret werden

Klingt nach
eurem Projekt?

30 Minuten Gespräch, danach weißt du, ob ein vergleichbares Setup für dich Sinn ergibt, und was es realistisch kostet.

Erstgespräch Weitere Artikel