Logo StartupKit
FR

Référence API

Authentifiez-vous et interrogez l'API de recrutement pour les offres d'emploi, les candidats et les candidatures.

Pourquoi c’est important

L’API de recrutement offre aux systèmes externes un accès en lecture à vos données de recrutement — les sites d’emploi récupèrent les offres publiées, les systèmes SIRH synchronisent les fiches candidats, les tableaux de bord suivent les indicateurs du pipeline. Les portées des jetons garantissent que chaque intégration ne voit que ce dont elle a besoin.

URL de base

Tous les points d’accès de l’API sont accessibles via :

https://startupkit.app/api/v1

Authentification

Chaque requête nécessite un jeton API dans l’en-tête Authorization :

Authorization: Bearer YOUR_TOKEN

Format alternatif (les deux fonctionnent) :

Authorization: token YOUR_TOKEN

Créez des jetons dans Paramètres > API. Chaque jeton doit disposer de portées explicites pour accéder aux points d’accès de recrutement.

Portée Accès
job_postings:read Offres d’emploi et étapes
candidates:read Données personnelles des candidats (nom, e-mail, téléphone), métachamps des candidatures, téléchargement de CV
applications:read Candidatures, historique des étapes, téléchargement de CV

Les jetons sans portée ne peuvent pas accéder aux points d’accès de recrutement.

Format et cycle de vie des jetons

  • Format : chaîne base58 de 24 caractères (par ex. k3Jf9pQ2mZ7xR4nL8vT6bWyH)
  • Affiché une seule fois : le jeton brut s’affiche une seule fois, sur l’écran de confirmation juste après sa création. Copiez-le à ce moment-là ; il est impossible de le récupérer ensuite.
  • Stockage du seul condensé : Kit ne stocke qu’un condensé SHA-256 du jeton, jamais la valeur en clair. Si vous perdez le jeton, révoquez-le et créez-en un nouveau ; la valeur d’origine ne peut pas être récupérée.
  • Suivi de la dernière utilisation : chaque requête réussie met à jour last_used_at
  • Aucune date d’expiration : les jetons n’expirent pas au bout d’un délai. Le formulaire de création ne propose aucune option d’expiration, et Paramètres > API n’en affiche aucune — un jeton reste valide jusqu’à sa révocation.
  • Alertes d’inactivité : les jetons non utilisés depuis plus de 30 jours déclenchent des notifications administrateur
  • Révocation automatique : les jetons sont automatiquement révoqués lorsqu’un utilisateur est retiré du compte

Bonne pratique : créez des jetons distincts par intégration avec les portées minimales nécessaires.

Portée du compte

Chaque jeton API est lié à un compte spécifique. Le compte est déterminé automatiquement à partir du jeton — aucun paramètre account_id n’est nécessaire.

Lorsqu’un membre de l’équipe est retiré d’un compte, ses jetons pour ce compte sont automatiquement révoqués.

Réponses d’erreur

Statut Signification Causes courantes
400 Requête incorrecte Contexte de compte manquant (cas limite avec l’authentification par session)
401 Non autorisé Jeton manquant, invalide ou révoqué
403 Interdit Le jeton ne dispose pas de la portée requise pour ce point d’accès
404 Non trouvé La ressource n’existe pas ou n’appartient pas à votre compte
422 Entité non traitable Erreurs de validation (principalement pour les points d’accès hors recrutement)

Pagination

Les points d’accès de liste renvoient des résultats paginés :

{
  "data": [...],
  "pagination": {
    "current_page": 1,
    "total_pages": 3,
    "total_count": 42,
    "per_page": 20
  }
}

Ajoutez ?page=2 pour récupérer les pages suivantes.


Offres d’emploi

Portée requise : job_postings:read

Lister les offres d’emploi

GET /api/v1/job_postings

Exemple :

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://startupkit.app/api/v1/job_postings?status=published

Paramètres :

Paramètre Type Description
status string Filtrer par draft, published, paused ou closed
page integer Numéro de page

Réponse :

{
  "data": [
    {
      "id": "job_abc123",
      "title": "Senior Rails Developer",
      "status": "published",
      "department": "Engineering",
      "location": "New York, NY",
      "employment_type": "full_time",
      "remote": true,
      "salary_min": "120000.0",
      "salary_max": "180000.0",
      "salary_currency": "USD",
      "salary_period": "year",
      "published_at": "2026-01-15T10:00:00Z",
      "closed_at": null,
      "created_at": "2026-01-10T08:30:00Z",
      "updated_at": "2026-02-01T14:20:00Z",
      "stages": [
        {
          "id": "stg_def456",
          "name": "Screening",
          "stage_type": "default",
          "position": 0
        }
      ],
      "counts": {
        "total_applications": 24,
        "active_applications": 18,
        "rejected": 6
      }
    }
  ],
  "pagination": { ... }
}

Obtenir une offre d’emploi

GET /api/v1/job_postings/:id

Renvoie la même structure qu’un élément de la réponse de liste.

Lister les étapes

GET /api/v1/job_postings/:job_posting_id/stages

Réponse :

{
  "data": [
    {
      "id": "stg_def456",
      "name": "Screening",
      "stage_type": "default",
      "position": 0
    }
  ]
}

Les étapes sont classées par position. Pas de pagination.


Candidats

Portée requise : candidates:read

Lister les candidats

GET /api/v1/candidates

Exemple :

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://startupkit.app/api/v1/[email protected]

Paramètres :

Paramètre Type Description
search string Rechercher par nom ou e-mail
page integer Numéro de page

Réponse :

{
  "data": [
    {
      "id": "cand_ghi789",
      "first_name": "John",
      "last_name": "Doe",
      "email": "[email protected]",
      "phone": "+1-555-0100",
      "status": "active",
      "github_username": "johndoe",
      "created_at": "2026-01-20T09:15:00Z",
      "applications": [
        {
          "id": "app_jkl012",
          "job_posting_id": "job_abc123",
          "job_posting_title": "Senior Rails Developer",
          "current_stage_name": "Interview",
          "status": "active",
          "submitted_at": "2026-01-20T09:15:00Z"
        }
      ]
    }
  ],
  "pagination": { ... }
}

Obtenir un candidat

GET /api/v1/candidates/:id

Renvoie la même structure qu’un élément de la réponse de liste.


Candidatures

Portée requise : applications:read

Lister les candidatures

GET /api/v1/applications

Exemple :

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://startupkit.app/api/v1/applications?job_posting_id=job_abc123&status=active

Paramètres :

Paramètre Type Description
job_posting_id string Filtrer par identifiant préfixé de l’offre d’emploi
status string Filtrer par active ou rejected
page integer Numéro de page

Réponse :

{
  "data": [
    {
      "id": "app_jkl012",
      "job_posting_id": "job_abc123",
      "candidate_id": "cand_ghi789",
      "current_stage_name": "Interview",
      "status": "active",
      "submitted_at": "2026-01-20T09:15:00Z",
      "created_at": "2026-01-20T09:15:00Z",
      "updated_at": "2026-02-10T11:30:00Z",
      "candidate": {
        "id": "cand_ghi789",
        "first_name": "John",
        "last_name": "Doe",
        "email": "[email protected]"
      },
      "stage_history": [
        {
          "id": "sp_mno345",
          "stage_name": "Screening",
          "stage_type": "default",
          "status": "completed",
          "started_at": "2026-01-20T09:15:00Z",
          "completed_at": "2026-01-25T16:00:00Z"
        },
        {
          "id": "sp_pqr678",
          "stage_name": "Interview",
          "stage_type": "interview",
          "status": "in_progress",
          "started_at": "2026-01-25T16:00:00Z",
          "completed_at": null
        }
      ],
      "metafields": [
        {
          "key": "years_of_experience",
          "label": "Years of Experience",
          "field_type": "number",
          "value": "8",
          "source": "ai",
          "confidence": 0.92,
          "extracted_at": "2026-01-20T09:18:00Z",
          "edited": false,
          "original_value": null
        },
        {
          "key": "salary_expectation",
          "label": "Salary Expectation",
          "field_type": "number",
          "value": "150000",
          "source": "ai",
          "confidence": 0.75,
          "extracted_at": "2026-01-20T09:18:00Z",
          "edited": true,
          "original_value": "140000"
        }
      ]
    }
  ],
  "pagination": { ... }
}

Données des métachamps

Portée requise : candidates:read

Les valeurs des métachamps peuvent contenir des données personnelles de candidats extraites par l’IA : l’accès au tableau metafields est donc conditionné à la portée candidates:read — celle-là même qui conditionne le bloc candidate — et non à applications:read. Un jeton disposant de applications:read mais pas de candidates:read reçoit des réponses de candidature dépourvues de toute clé metafields. L’exemple de réponse ci-dessus correspond à ce que voit un jeton disposant des deux portées.

Lorsque le jeton dispose bien de candidates:read, chaque candidature inclut un tableau metafields contenant les champs de données structurées extraits ou saisis pour cette candidature. Chaque entrée comporte :

Champ Type Description
key string Identifiant lisible par machine du champ
label string Nom d’affichage lisible par un humain
field_type string Type de données — l’une de ces neuf valeurs exactement : text, textarea, number, date, select, boolean, url, rating, tags
value string | null Valeur actuelle (toujours sérialisée sous forme de chaîne)
source string Comment la valeur a été définie — l’une de deux valeurs exactement : manual (saisie ou modifiée par une personne) ou ai (extraite ou définie par l’IA)
confidence float | null Score de confiance de l’IA (0.0–1.0) lorsque source vaut ai ; null sinon
extracted_at timestamp | null Date à laquelle la valeur a été définie ou extraite
edited boolean true uniquement lorsqu’une valeur d’origine IA a ensuite été remplacée par autre chose que l’extraction brute de l’IA (en pratique, par un appel MCP hiring_update_metafield_value). Les modifications faites par une personne dans l’interface de Kit enregistrent source: manual et indiquent toujours edited: false.
original_value string | null La valeur d’origine extraite par l’IA avant modification humaine ; null si jamais modifiée

Pour un jeton disposant de candidates:read, le tableau metafields n’est pas filtré davantage selon l’indicateur de visibilité managers_only. Chaque métachamp défini sur l’offre d’emploi — y compris les champs marqués managers_only dans la configuration de l’offre d’emploi — est renvoyé, quel que soit le rôle du propriétaire du jeton. La portée candidates:read constitue donc le seul contrôle sur l’exposition des métachamps : si un champ contient des informations sensibles que vous ne souhaitez pas exposer à une intégration, n’accordez pas la portée candidates:read à cette intégration, car l’API n’applique pas le filtrage de visibilité réservé aux managers utilisé ailleurs dans l’application. À cet égard, n’accorder que applications:read est sans risque : cette portée renvoie l’historique des étapes et le statut de la candidature, sans les métachamps.

Obtenir une candidature

GET /api/v1/applications/:id

Renvoie la même structure qu’un élément de la réponse de liste. Lorsque le jeton dispose de la portée candidates:read et qu’un CV est joint à la candidature, la réponse inclut les métadonnées du CV :

{
  "id": "app_jkl012",
  "resume_filename": "john_doe_resume.pdf",
  "resume_content_type": "application/pdf",
  "resume_byte_size": 245760,
  ...
}

Ces champs sont omis lorsqu’aucun CV n’est joint ou lorsque la portée candidates:read est absente.

Télécharger le CV

Portées requises : applications:read + candidates:read

GET /api/v1/applications/:application_id/resume

Télécharge le CV du candidat pour la candidature donnée. Renvoie une redirection 302 vers une URL signée à durée limitée (valide 30 minutes).

Exemple :

curl -L -H "Authorization: Bearer YOUR_TOKEN" \
  -o resume.pdf \
  https://startupkit.app/api/v1/applications/app_jkl012/resume

Réponse :

  • 302 Found — Redirection vers l’URL de téléchargement signée. Utilisez -L (suivre les redirections) avec curl.
  • 403 Forbidden — Le jeton ne dispose pas de la portée applications:read ou candidates:read.
  • 404 Not Found — Candidature non trouvée, ou aucun CV joint.

Notes :

  • Les URL signées expirent après 30 minutes. En cas d’expiration, effectuez une nouvelle demande.
  • L’en-tête Content-Disposition est défini sur attachment (déclenche le téléchargement par le navigateur).
  • Types de fichiers de CV : PDF, DOC, DOCX (tels que téléchargés par le candidat).

Masquage des données personnelles des candidats

Lorsqu’un jeton dispose de la portée applications:read mais pas de candidates:read, les données du candidat dans les réponses de candidature sont réduites au seul identifiant :

{
  "candidate": {
    "id": "cand_ghi789"
  }
}

Les champs first_name, last_name et email ne sont pas inclus. Le tableau metafields et les champs de métadonnées du CV sont omis intégralement en vertu de la même règle, car ils peuvent eux aussi contenir des données personnelles du candidat. Cela permet aux intégrations de sites d’emploi de suivre le statut des candidatures sans exposer les coordonnées des candidats.


Dépannage

Problèmes d’authentification

401 Unauthorized

Si vous recevez une réponse 401, vérifiez :

  1. Format du jeton : assurez-vous que le jeton correspond à la valeur affichée une seule fois lors de sa création (chaîne base58 de 24 caractères). Si vous ne l’avez plus, révoquez le jeton et créez-en un nouveau — Kit ne conserve qu’un condensé haché et ne peut pas l’afficher de nouveau.
  2. En-tête Authorization : doit être Authorization: Bearer TOKEN ou Authorization: token TOKEN
  3. Le jeton existe toujours : vérifiez qu’il figure encore dans Paramètres > API — un jeton révoqué est supprimé, et non désactivé
  4. Appartenance au compte : vérifiez que le propriétaire du jeton est toujours membre du compte

403 Forbidden

Lorsque vous recevez une erreur 403, le jeton est valide mais ne dispose pas de la portée requise :

  1. Vérifiez les portées requises : chaque point d’accès indique la portée nécessaire (par ex. job_postings:read)
  2. Ajoutez les portées manquantes : modifiez le jeton dans Paramètres > API et ajoutez la portée requise
  3. Format des portées : doit correspondre exactement — job_postings:read, et non job_postings ou read

Problèmes d’accès aux données

Tableau data vide dans les réponses

Si l’API renvoie {"data": [], "pagination": {...}} alors que vous attendez des résultats :

  1. Isolation des comptes : les jetons API ne voient que les données de leur compte associé
  2. Vérifiez le compte : consultez à quel compte le jeton appartient dans Paramètres > API
  3. Accès inter-comptes : les jetons ne peuvent pas accéder aux données d’autres comptes, même si l’utilisateur en est membre

Les données personnelles du candidat sont masquées

Si vous ne voyez que {"candidate": {"id": "cand_..."}} sans nom ni e-mail :

  1. Comportement attendu : cela se produit lorsque le jeton dispose de la portée applications:read mais pas de candidates:read
  2. Ajoutez la portée : pour voir les informations complètes du candidat, ajoutez la portée candidates:read à votre jeton
  3. Confidentialité par conception : cela permet aux intégrations de suivre les candidatures sans exposer les coordonnées

Le tableau metafields est absent des réponses de candidature

L’accès au tableau metafields est conditionné à la portée candidates:read, et non à applications:read, car les valeurs des métachamps peuvent contenir des données personnelles de candidats extraites par l’IA. Si vos réponses ne comportent aucune clé metafields, c’est que le jeton ne dispose pas de candidates:read. N’ajoutez cette portée que si l’intégration doit également voir les noms, les e-mails et les CV des candidats — les deux vont de pair.

Problèmes de requêtes et de filtrage

Erreur 404 lors de la recherche d’enregistrements spécifiques

  1. Utilisez les identifiants préfixés : les offres d’emploi utilisent le format job_abc123, et non des identifiants de base de données comme 42
  2. Sensibilité à la casse : les identifiants préfixés sont sensibles à la casse
  3. Portée du compte : la ressource doit appartenir au compte du jeton

Les paramètres de filtre ne fonctionnent pas

  1. Valeurs de statut valides :
    • Offres d’emploi : draft, published, paused, closed (sensible à la casse)
    • Candidatures : active, rejected (sensible à la casse)
  2. Format des paramètres : utilisez ?status=published, et non ?status=Published ou ?filter[status]=published

Questions fréquentes

Comment fonctionne la pagination ?

Tous les points d’accès de liste renvoient 20 éléments par page (fixe). Utilisez ?page=2 pour la page suivante. L’objet pagination indique total_pages et total_count.

Les jetons expirent-ils automatiquement ?

Non. Aucune option d’expiration n’est proposée lors de la création d’un jeton. Les jetons restent donc valides jusqu’à ce que :

  • Ils soient révoqués manuellement dans Paramètres > API
  • L’utilisateur soit retiré du compte
  • Le compte soit supprimé

Dois-je utiliser un seul jeton pour plusieurs intégrations ?

Non. La bonne pratique est d’utiliser un jeton par intégration avec des portées minimales. Cela facilite :

  • L’audit des requêtes par intégration (via last_used_at)
  • La révocation de l’accès à une intégration spécifique sans affecter les autres
  • L’attribution de permissions différentes à différents systèmes

Quel est le lien entre les webhooks et l’API ?

Les webhooks vous notifient des événements (nouvelle candidature, changement d’étape). L’API vous permet d’interroger l’état actuel. Utilisez les webhooks pour déclencher votre système, puis l’API pour récupérer les détails complets.

Puis-je obtenir plus de 20 éléments par page ?

Non. La taille de page est fixée à 20 éléments pour garantir des performances constantes. Utilisez le paramètre page pour parcourir l’ensemble des résultats.

Aide-mémoire

  • Créez un jeton API dans Paramètres > API — chaque jeton est lié à ce compte
  • Attribuez uniquement les portées nécessaires à votre intégration (bonne pratique : un jeton par intégration avec des portées minimales)
  • N’accordez la portée candidates:read que si l’intégration doit accéder aux données personnelles des candidats — cette portée régit aussi le tableau metafields et l’accès aux CV
  • Incluez Authorization: Bearer TOKEN dans chaque requête
  • Utilisez les identifiants préfixés (par ex. job_abc123) dans les URL, et non les identifiants de base de données
  • Gérez les réponses 400 et 422 pour les erreurs de validation
  • Gérez les réponses 401 — votre jeton a peut-être été révoqué
  • Gérez les réponses 403 — votre jeton peut ne pas disposer de la portée requise
  • Utilisez le paramètre page pour paginer les ensembles de résultats volumineux (20 éléments par page)

Tapez pour rechercher...