## Por qué es importante

El [portal de empleo](/docs/setting-up-your-career-portal) alojado por Kit y el [widget integrable](/docs/embedding-your-career-portal) cubren la mayoría de las necesidades. Pero si quieres control total sobre el diseño — un sitio de empleo a medida, una landing page propia para cada puesto o una bolsa de trabajo que encaje con tu producto — la **Public Jobs API** te permite leer tus ofertas publicadas y enviar candidaturas directamente a tu pipeline de Kit, mientras Kit sigue encargándose del cribado, las etapas, las entrevistas y la comunicación con los candidatos.

También hay una **plantilla de Next.js** oficial desplegable con un clic y un **SDK de TypeScript**, para que puedas lanzar un sitio de empleo personalizado en minutos.

## Claves API

Crea un par de claves en **Hiring → Career Portal → Public API Keys**. Cada par incluye:

- **Clave publicable (`pk_…`)** — segura para incluir en un navegador. Puede leer ofertas publicadas y enviar candidaturas, nada más. Nunca expone datos de los candidatos. Puedes restringirla a orígenes específicos y protegerla con tu propio widget de Cloudflare Turnstile.
- **Clave secreta (`sk_…`)** — solo para uso en el servidor (por ejemplo, una Server Action de Next.js). Se salta las comprobaciones de origen y Turnstile del navegador. **Nunca la expongas en código del lado del cliente.** Ninguna de las dos claves puede leer datos personales de los candidatos.

La clave secreta se muestra una sola vez, al crearla o rotarla. Puedes rotarla en cualquier momento desde la página de configuración de la clave; la clave secreta anterior deja de funcionar de inmediato.

Autentica cada petición con una cabecera bearer:

```
Authorization: Bearer sk_your_secret_key
```

## Endpoints

URL base: `https://app.startupkit.app` (o tu dominio personalizado de empleo).

### Listar ofertas publicadas

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

Devuelve únicamente los puestos **publicados** de tu cuenta.

```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 }
}
```

El `id` es el token público de la oferta — úsalo en los endpoints de detalle y de candidatura.

### Obtener una oferta con su formulario de candidatura

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

Devuelve la oferta junto con un `application_form` que describe exactamente qué campos y preguntas mostrar, el aviso de consentimiento que hay que enseñar, los tipos y el tamaño de currículum aceptados, y si Turnstile es obligatorio.

```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 }
  }
}
```

### Subir un currículum (URL prefirmada)

Los currículums se suben directamente al almacenamiento, así que nunca pasan por tu servidor (lo que evita los límites de tamaño de cuerpo en entornos 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": "…" } }
}
```

Envía los bytes del archivo con `PUT` a `direct_upload.url` usando los `headers` devueltos, y luego pasa el `signed_id` como `resume_signed_id` al enviar la candidatura.

### Enviar una candidatura

```
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>"
}
```

Devuelve `201` con una confirmación mínima, sin datos personales:

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

El `turnstile_token` solo se necesita en envíos desde el navegador (`pk_`) cuando la clave tiene Turnstile configurado; las llamadas desde el servidor (`sk_`) se lo saltan.

## Errores

Los errores se devuelven en un sobre consistente:

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

| Estado | Código | Significado |
|---|---|---|
| 401 | `invalid_key` | Clave API ausente o inválida |
| 403 | `origin_not_allowed` | El origen del navegador no está en la lista de permitidos de la clave |
| 404 | `not_found` | Oferta no encontrada o no publicada |
| 409 | `already_applied` | Este email ya se postuló a esta oferta |
| 422 | `validation_failed` | Campos de candidatura inválidos (consulta `fields`) |
| 422 | `turnstile_failed` | Falló la verificación de Turnstile |
| 422 | `invalid_content_type` / `file_too_large` | Subida de currículum rechazada |

## Actualizaciones de estado vía webhooks

Para hacer seguimiento de las candidaturas después del envío, configura [webhooks salientes](/docs/webhooks). Los eventos relevantes incluyen `application.submitted`, `application.advanced` y `application.rejected`, además de `job_posting.published/paused/closed`. Los payloads de candidatura incluyen tanto el `id` numérico como el `prefix_id` de la API (`app_…`), y el `public_token` de la oferta, para que puedas correlacionar los eventos de webhook con los registros de la API.

## SDK y plantilla de Next.js

- **SDK de TypeScript** — `npm install @startupkit-app/jobs`. Un cliente tipado y sin dependencias con `listJobs`, `getJob`, `uploadFile` y `apply`.
- **Plantilla de Next.js** — una bolsa de trabajo desplegable con un clic ([Deploy to Vercel](https://vercel.com)) que puedes forkear y personalizar. Trae configurados de serie la clave secreta, los formularios de candidatura dinámicos, la subida prefirmada de currículums y el SEO/JSON-LD.

Ambos consumen el contrato de arriba, así que también puedes construir contra cualquier framework usando HTTP a secas.

## Límites de uso

Los envíos de candidaturas están limitados a 10/hora por IP y las peticiones de subida a 30/hora por IP, además de los límites de uso globales de la API. Las claves de navegador están protegidas adicionalmente por su lista de orígenes permitidos y, de forma opcional, por Turnstile.