## Pourquoi c'est important

Le [portail carrière](/docs/setting-up-your-career-portal) hébergé par Kit et le [widget intégrable](/docs/embedding-your-career-portal) couvrent la plupart des besoins. Mais si vous voulez un contrôle total sur le design — un site carrière sur mesure, une landing page dédiée par poste ou un site d'emploi assorti à votre produit — la **Public Jobs API** vous permet de lire vos offres publiées et d'envoyer les candidatures directement dans votre pipeline Kit, pendant que Kit continue de gérer la présélection, les étapes, les entretiens et la communication avec les candidats.

Il existe aussi un **template Next.js** officiel déployable en un clic et un **SDK TypeScript**, pour livrer un site d'emploi personnalisé en quelques minutes.

## Clés API

Créez une paire de clés sous **Hiring → Career Portal → Public API Keys**. Chaque paire comprend :

- **Clé publiable (`pk_…`)** — peut être embarquée sans risque dans un navigateur. Elle peut lire les offres publiées et soumettre des candidatures, rien de plus. Elle n'expose jamais les données des candidats. Vous pouvez la restreindre à des origines spécifiques et la protéger avec votre propre widget Cloudflare Turnstile.
- **Clé secrète (`sk_…`)** — réservée à un usage côté serveur (par exemple une Server Action Next.js). Elle contourne les vérifications navigateur d'origine et de Turnstile. **Ne l'exposez jamais dans du code côté client.** Aucune des deux clés ne peut lire les données personnelles des candidats.

La clé secrète n'est affichée qu'une seule fois, à sa création ou à sa rotation. Vous pouvez la faire tourner à tout moment depuis la page de paramètres de la clé ; l'ancienne clé secrète cesse de fonctionner immédiatement.

Authentifiez chaque requête avec un en-tête bearer :

```
Authorization: Bearer sk_your_secret_key
```

## Endpoints

URL de base : `https://app.startupkit.app` (ou votre domaine carrière personnalisé).

### Lister les offres publiées

```
GET /api/public/v1/jobs?department=&location=&employment_type=&remote=&page=&per_page=
```

Retourne uniquement les postes **publiés** de votre compte.

```json
{
  "data": [
    {
      "id": "JdK2hQ8…",
      "title": "Senior Rails Developer",
      "department": "Engineering",
      "location": "Remote",
      "employment_type": "full_time",
      "remote": true,
      "published_at": "2026-06-01T12:00:00Z",
      "url": "https://careers.yourco.com/JdK2hQ8…",
      "salary": { "min": 120000, "max": 160000, "currency": "USD", "period": "YEAR" }
    }
  ],
  "pagination": { "current_page": 1, "total_pages": 3, "total_count": 42, "per_page": 20 }
}
```

L'`id` est le jeton public de l'offre — utilisez-le pour les endpoints de détail et de candidature.

### Récupérer une offre et son formulaire de candidature

```
GET /api/public/v1/jobs/:public_token
```

Retourne l'offre, accompagnée d'un `application_form` décrivant précisément les champs et questions à afficher, la mention de consentement à présenter, les types et la taille de CV acceptés, et si Turnstile est requis.

```json
{
  "id": "JdK2hQ8…",
  "title": "Senior Rails Developer",
  "description_html": "<p>We're hiring…</p>",
  "accepting_applications": true,
  "stages": [{ "name": "Application Review", "type": "application_form" }],
  "application_form": {
    "fields": [
      { "name": "cover_letter", "type": "textarea", "label": "Cover letter", "required": false }
    ],
    "questions": [
      { "key": "why_us", "type": "text", "prompt": "Why do you want to join?", "required": true, "max_length": 2000 }
    ],
    "consent_disclosure_html": "<p>By applying you agree…</p>",
    "resume": {
      "content_types": ["application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
      "max_byte_size": 10485760
    },
    "turnstile": { "required": false, "sitekey": null }
  }
}
```

### Téléverser un CV (URL présignée)

Les CV sont téléversés directement vers le stockage : ils ne transitent jamais par votre serveur (ce qui évite les limites de taille de corps des environnements serverless).

```
POST /api/public/v1/direct_uploads
{ "blob": { "filename": "cv.pdf", "byte_size": 102400, "checksum": "<base64 MD5>", "content_type": "application/pdf" } }
```

```json
{
  "signed_id": "eyJf…",
  "direct_upload": { "url": "https://…s3…", "headers": { "Content-Type": "application/pdf", "Content-MD5": "…" } }
}
```

Envoyez les octets du fichier en `PUT` vers `direct_upload.url` avec les `headers` retournés, puis transmettez le `signed_id` comme `resume_signed_id` au moment de soumettre la candidature.

### Soumettre une candidature

```
POST /api/public/v1/jobs/:public_token/applications
{
  "application": {
    "email": "candidate@example.com",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "phone": "+1 555 0100",
    "responses": { "cover_letter": "…", "why_us": "…" },
    "resume_signed_id": "eyJf…"
  },
  "turnstile_token": "<token>"
}
```

Retourne `201` avec une confirmation minimale, sans données personnelles :

```json
{ "id": "app_9fQ…", "status": "submitted", "job": "JdK2hQ8…", "submitted_at": "2026-06-11T09:30:00Z" }
```

Le `turnstile_token` n'est nécessaire que pour les soumissions depuis un navigateur (`pk_`) lorsque Turnstile est configuré sur la clé ; les appels côté serveur (`sk_`) s'en passent.

## Erreurs

Les erreurs sont retournées dans une enveloppe cohérente :

```json
{ "error": { "code": "validation_failed", "message": "Email can't be blank", "fields": { "email": ["can't be blank"] } } }
```

| Statut | Code | Signification |
|---|---|---|
| 401 | `invalid_key` | Clé API manquante ou invalide |
| 403 | `origin_not_allowed` | Origine du navigateur absente de la liste autorisée de la clé |
| 404 | `not_found` | Offre introuvable ou non publiée |
| 409 | `already_applied` | Cette adresse e-mail a déjà postulé à cette offre |
| 422 | `validation_failed` | Champs de candidature invalides (voir `fields`) |
| 422 | `turnstile_failed` | Échec de la vérification Turnstile |
| 422 | `invalid_content_type` / `file_too_large` | Téléversement de CV refusé |

## Suivi des statuts via webhooks

Pour suivre les candidatures après leur soumission, configurez des [webhooks sortants](/docs/webhooks). Les événements pertinents incluent `application.submitted`, `application.advanced` et `application.rejected`, ainsi que `job_posting.published/paused/closed`. Les payloads de candidature contiennent à la fois l'`id` numérique et le `prefix_id` de l'API (`app_…`), ainsi que le `public_token` de l'offre, pour corréler les événements webhook avec les enregistrements de l'API.

## SDK et template Next.js

- **SDK TypeScript** — `npm install @startupkit-app/jobs`. Un client typé, sans dépendance, avec `listJobs`, `getJob`, `uploadFile` et `apply`.
- **Template Next.js** — un site d'emploi déployable en un clic ([Deploy to Vercel](https://vercel.com)) que vous pouvez forker et personnaliser. Il câble d'office la clé secrète, les formulaires de candidature dynamiques, le téléversement présigné des CV et le SEO/JSON-LD.

Les deux consomment le contrat décrit ci-dessus : vous pouvez donc aussi développer avec n'importe quel framework, en HTTP simple.

## Limites de débit

Les soumissions de candidatures sont limitées à 10/heure par IP et les requêtes de téléversement à 30/heure par IP, en plus des limites de débit globales de l'API. Les clés navigateur sont en outre protégées par leur liste d'origines autorisées et, en option, par Turnstile.