# Especificação do feed de vagas para ATS

Esta é a especificação pública do feed de vagas da Joonow para sistemas de recrutamento (ATS) e sites de carreira. Com ela você monta o feed, confere no [validador](/pt-br/integre-seu-ats/validador) e pede a integração sem falar com ninguém. O programa é gratuito: veja [o que o seu ATS ganha](/pt-br/integre-seu-ats).

## Resumo

- **Um arquivo XML** numa URL pública, de preferência com `https://`, com **todas** as vagas abertas dos seus clientes.
- **Cada vaga** precisa de id estável, título, descrição, URL de candidatura e empresa.
- **Recomendado:** o e-mail de quem responde pela vaga (`email`). Não aparece no site e faz a vaga subir na ordenação ([por que incluir](#por-que-incluir-o-e-mail-do-responsavel)).
- **Recomendado:** o site e a logo da empresa (`companyurl`, `companylogo`); a descrição (`companydescription`) é opcional. A logo aparece nas vagas, na busca e no diretório, e os três ajudam a montar a página da empresa ([por que incluir](#por-que-incluir-site-logo-e-descricao-da-empresa)).
- **Lemos o feed a cada 6 horas** (a agenda é combinada na aprovação), com GET condicional (`ETag` e `Last-Modified`).
- **Vaga que sai do arquivo é encerrada** na segunda leitura seguida sem ela.
- **A candidatura acontece no seu site:** o botão leva à URL da vaga com `utm_source=joonow`. Ou, com as [candidaturas por webhook](#candidaturas-por-webhook) ligadas, o candidato se candidata na Joonow e a candidatura chega assinada ao seu sistema.
- **Antes de pedir, valide** no [validador](/pt-br/integre-seu-ats/validador): ele mostra os erros de cada vaga e não grava nada.

## Formatos aceitos

| Formato | Quando usar | Elemento de cada vaga |
|---|---|---|
| XML Joonow (recomendado) | Você vai criar o feed agora ou já gera o XML do Indeed. | `<job>` |
| XML do Indeed | O seu ATS já publica o XML padrão do Indeed (`requisitionid`, `sourcename`, `remotetype`). | `<job>` |
| XML do LinkedIn | O seu ATS já gera o XML de vagas do LinkedIn (`partnerJobId`, `applyUrl`). | `<job>` |

Os três usam a raiz `<source>` com uma lista de `<job>`. Os nomes dos elementos não diferenciam maiúsculas e o texto pode vir dentro de `CDATA`.

## XML Joonow

É o XML que muitos ATS já geram para o Indeed, com alguns campos a mais (validade, modelo híbrido e nível). Baixe o [XML de exemplo](/partners/joonow-feed-example.xml) e o [esquema XSD](/partners/joonow-feed.xsd).

```xml
<?xml version="1.0" encoding="UTF-8"?>
<source>
  <publisher>Nome do seu ATS</publisher>
  <publisherurl>https://www.seu-ats.com</publisherurl>
  <lastBuildDate>2026-10-30T09:00:00Z</lastBuildDate>
  <job>
    <referencenumber>ACME-4821</referencenumber>
    <title><![CDATA[Analista de Suporte Júnior]]></title>
    <company><![CDATA[Acme Logística]]></company>
    <email>recrutamento@acme.example</email>
    <companyurl>https://www.acme.example/</companyurl>
    <companylogo>https://www.acme.example/img/logo-acme-256.png</companylogo>
    <companydescription><![CDATA[Operador logístico com armazéns em Pernambuco e na Bahia.]]></companydescription>
    <url><![CDATA[https://acme.seu-ats.com/vagas/4821]]></url>
    <date>2026-10-28T13:00:00Z</date>
    <expirationdate>2026-12-15T23:59:59Z</expirationdate>
    <city><![CDATA[Recife]]></city>
    <state>PE</state>
    <country>BR</country>
    <description><![CDATA[<p>Atendimento a clientes...</p><h3>Requisitos</h3><ul><li>...</li></ul>]]></description>
    <salary><![CDATA[R$ 2.800,00 a R$ 3.200,00 por mês]]></salary>
    <jobtype>fulltime</jobtype>
    <category>Profissional</category>
    <remotetype>Hybrid remote</remotetype>
  </job>
</source>
```

### Campos da vaga

| Elemento | Obrigatório | Tipo | Exemplo | Como a Joonow usa |
|---|---|---|---|---|
| `referencenumber` | Sim | Texto, até 200 caracteres | `ACME-4821` | Identifica a vaga entre uma leitura e outra. Precisa ser único e **estável**: se mudar, a vaga vira outra. Sem ele, usamos a URL de candidatura. |
| `title` | Sim | Texto, até 200 caracteres | `Analista de Suporte Júnior` | Título da vaga, na busca e na página. Título todo em CAIXA ALTA vira caixa normal (siglas ficam). |
| `description` | Sim | HTML simples, até 50 KB | `<p>…</p><ul><li>…</li></ul>` | Descrição da vaga. Ficam só `p`, `br`, `ul`, `ol`, `li`, `strong`, `b`, `em`, `i`, `u`, `h2`, `h3`, `h4`, `a`, `blockquote` e `hr`; o resto (script, estilo, imagem) sai. |
| `url` | Sim | URL `https://` | `https://acme.seu-ats.com/vagas/4821` | Destino do botão "Candidatar-se", com `utm_source=joonow`. Precisa abrir a própria vaga. |
| `company` | Sim | Texto | `Acme Logística` | Nome da empresa: cria a página da empresa com todas as vagas dela. Vazio vira "Confidencial". |
| `city` | Recomendado | Texto | `Recife` | Cidade: filtros, busca por local e páginas de cidade. |
| `state` | Recomendado | Sigla ou nome | `PE` | Estado. No Brasil, a sigla (UF) ou o nome por extenso. |
| `country` | Recomendado | Código ISO de 2 letras ou nome | `BR` | País. Vazio usa o país padrão do feed, definido na aprovação. |
| `date` | Recomendado | Data (ISO 8601 ou RFC 1123) | `2026-10-28T13:00:00Z` | Data de publicação: ordenação, "publicada há X dias" e idade da vaga. Sem ela, usamos a data da leitura; data no futuro vira hoje. |
| `email` | Recomendado | E-mail, até 254 caracteres | `recrutamento@acme.example` | E-mail profissional de quem responde pela vaga. **Não aparece no site.** A vaga ganha pontos na ordenação e, se receber uma denúncia, a equipe da Joonow tem com quem falar ([por que incluir](#por-que-incluir-o-e-mail-do-responsavel)). Inválido é ignorado, sem recusar a vaga. |
| `companyurl` | Recomendado | URL absoluta | `https://www.acme.example/` | Site da própria empresa (não o do ATS, de rede social nem de página de vagas). Vira o link da página da empresa e ajuda a achar a logo e as redes ([por que incluir](#por-que-incluir-site-logo-e-descricao-da-empresa)). |
| `companylogo` | Recomendado | URL absoluta de imagem | `https://www.acme.example/img/logo.png` | Logo da empresa em PNG, JPEG ou WebP, com 128 px ou mais (quadrada é melhor). A Joonow baixa uma cópia e serve do próprio site. |
| `companydescription` | Opcional | Texto ou HTML simples, até 4.000 caracteres | `Operador logístico com armazéns no Nordeste.` | O que a empresa faz, para a página dela. Repita o mesmo texto em todas as vagas da empresa. |
| `expirationdate` | Opcional | Data (ISO 8601) | `2026-12-15T23:59:59Z` | Validade: depois dela, a vaga é encerrada. Vaga que já chega vencida é recusada. |
| `salary` | Opcional | Texto com valor ou faixa | `R$ 2.800,00 a R$ 3.200,00 por mês` | Salário, só quando a empresa informa. Reconhecemos moeda (R$, US$, €), faixa e período (hora, dia, semana, mês, ano). |
| `jobtype` | Opcional | Código | `fulltime` | Tipo de contrato (valores abaixo). |
| `category` | Opcional | Código | `Profissional` | Nível da vaga (valores abaixo). Outros valores são ignorados. |
| `isRemote` | Opcional | `Yes` ou `No` | `Yes` | `Yes` = 100% remota; `No` = presencial. |
| `remotetype` | Opcional | Texto | `Hybrid remote` | `Fully remote` = 100% remota; `Hybrid remote` = híbrida. Vale quando `isRemote` não vem. |

Os elementos `publisher`, `publisherurl` e `lastBuildDate` da raiz são opcionais: ajudam a identificar o arquivo, mas não mudam as vagas.

### Por que incluir o e-mail do responsável

O `email` (no XML do LinkedIn, `posterEmail`) é o e-mail profissional de quem responde pela vaga: a pessoa recrutadora ou a caixa da equipe de RH da empresa. Não é obrigatório, mas vale a pena:

- **A vaga sobe na busca.** O e-mail soma pontos na nota de qualidade do anúncio, como o salário e a descrição completa, e essa nota desempata a ordem entre as vagas que combinam com a busca: com o e-mail, a vaga passa à frente das equivalentes sem ele. Aparecer antes costuma render mais visitas e mais candidaturas.
- **Tem com quem falar antes de a vaga sair do ar.** Quando a vaga recebe uma denúncia de algo que dá para corrigir (dado errado, link trocado), a equipe da Joonow escreve para esse e-mail antes de decidir se a tira do ar.
- **O e-mail fica só com a equipe da Joonow.** Ele nunca aparece na vaga, nas buscas, na API nem para candidatos, não é vendido nem usado para propaganda e é apagado 30 dias depois de a vaga sair do ar ([política de privacidade](/pt-br/politica-de-privacidade)).

Mande um endereço completo por vaga (`recrutamento@empresa.com.br`). Caixa "não responda" não serve. Um e-mail inválido é ignorado sem recusar a vaga (ela entra sem o ponto), e o [validador](/pt-br/integre-seu-ats/validador) mostra quantas vagas trazem o e-mail.

### Por que incluir site, logo e descrição da empresa

O `companyurl`, o `companylogo` e o `companydescription` montam a página de cada empresa cliente na Joonow, com o mesmo valor em todas as vagas dela:

- **A logo aparece em todo lugar:** no cartão da vaga, na busca, na vaga aberta e no diretório de empresas, e vai no JSON-LD da vaga para o Google.
- **Ajudam a montar a página da empresa:** o site vira o link da página depois de conferido, as redes sociais vêm dele, e a descrição entra, junto com o próprio site da empresa, no texto curto que escrevemos sobre ela (nunca publicamos a descrição como veio).
- **Nada disso muda a vaga:** site, logo e descrição são da empresa. Mudar a logo não regrava as vagas, e um valor inválido é ignorado sem recusar a vaga.

A logo é baixada uma vez e servida pela Joonow (nunca carregamos imagem do seu servidor na página). Se a empresa reivindicar a página, o que ela escrever vale no lugar do que veio do feed. O [validador](/pt-br/integre-seu-ats/validador) mostra quantas vagas trazem a logo e o site.

### Valores aceitos

**`jobtype` (tipo de contrato):** `fulltime` (efetivo, tempo integral), `parttime` (meio período), `contract` (prestador de serviços, PJ, freelancer), `temporary` (temporário), `internship` (estágio) e `volunteer` (voluntário). Também aceitamos os rótulos em português: Efetivo – CLT, Estatutário, Estágio, Prestador de Serviços - PJ, Freelancer, Autônomo, Cooperado, Temporário, Tempo Determinado e Intermitente.

**`category` (nível):** os códigos `Apprentice` (aprendiz), `Intern` (estágio), `Entry` (início de carreira), `Operational` (operacional), `Professional` (profissional), `Management` (coordenação e gerência) e `Executive` (diretoria), ou os rótulos em português Aprendiz, Estágio, Trainee, Operacional, Profissional, Supervisão/Coordenação e Gerencial e Executivos.

**Modelo de trabalho:** `isRemote` com `Yes` ou `No`, ou `remotetype` com `Fully remote`, `Hybrid remote` ou `On-site`. "COVID-19" (remoto só temporariamente) fica sem modelo.

### O que não usar

O elemento `priority` existe no nosso leitor por compatibilidade, mas não faz parte do programa: vaga patrocinada não é combinada pelo feed. `phone` e campos internos do ATS são ignorados. Além do e-mail do responsável pela vaga, não mande dados pessoais no feed (nunca dados de candidatos).

## XML do Indeed

Se o seu ATS já publica o XML padrão do Indeed, escolha "XML do Indeed" no validador. As diferenças para o XML Joonow:

- o id pode vir em `referencenumber` ou `requisitionid`;
- a empresa pode vir em `company` ou `sourcename`;
- `jobtype` em inglês (`fulltime`, `parttime`, `contract`, `temporary`, `internship`, `volunteer`, `per diem`);
- `remotetype` com `Fully remote` ou `Hybrid remote`;
- `email` é lido como o e-mail do responsável pela vaga, com o mesmo uso do XML Joonow ([por que incluir](#por-que-incluir-o-e-mail-do-responsavel));
- `category` e `experience` são ignorados.

## XML do LinkedIn

Se o seu ATS já gera o XML de vagas do LinkedIn, escolha "XML do LinkedIn". Lemos:

| Elemento | Uso |
|---|---|
| `partnerJobId` | Id estável da vaga |
| `title`, `description`, `applyUrl` | Título, descrição e URL de candidatura |
| `company`, `companyId` | Empresa; o `companyId` do grupo junto com o nome separa cada marca |
| `city`, `state`, `country` | Local |
| `listDate`, `expirationDate` | Publicação e validade |
| `salaries/salary/lowEnd/amount`, `highEnd/amount`, `currencyCode`, `period` | Salário estruturado (`HOURLY`, `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY`) |
| `jobType` | `FULL_TIME`, `PART_TIME`, `CONTRACT`, `TEMPORARY`, `INTERNSHIP`, `VOLUNTEER` |
| `experienceLevel` | `INTERNSHIP`, `ENTRY_LEVEL`, `ASSOCIATE`, `MID_SENIOR_LEVEL`, `DIRECTOR`, `EXECUTIVE` |
| `workplaceTypes` | `On-site`, `Hybrid` ou `Remote` |
| `posterEmail` | E-mail do responsável pela vaga, com o mesmo uso do `email` do XML Joonow ([por que incluir](#por-que-incluir-o-e-mail-do-responsavel)) |

## Como lemos o feed

- **Frequência:** a cada 6 horas, no padrão. A agenda é combinada na aprovação, e o painel do parceiro tem o botão "Pedir leitura agora".
- **GET condicional:** mandamos `If-None-Match` e `If-Modified-Since` com o `ETag` e o `Last-Modified` da leitura anterior. Resposta `304` encerra a leitura sem baixar nada; arquivo idêntico ao anterior também não grava nada.
- **Compressão:** use a compressão do HTTP (`Content-Encoding: gzip`, `deflate` ou `br`). Arquivo `.gz` para baixar não é aceito.
- **Tamanho:** até 2 GB descompactado e 15 minutos de download.
- **Endereço:** URL pública, `http` ou `https` (prefira `https`), até 5 redirecionamentos. Endereços de rede interna são bloqueados.
- **Robô:** `JoonowFeedBot/1.0 (+https://www.joonow.com/robots.txt)`. Se houver firewall, libere esse User-Agent.
- **XML:** UTF-8, **sem** `<!DOCTYPE>` nem entidades externas. Um arquivo com DTD é recusado por segurança.

## Ciclo de vida da vaga

1. **Nova:** entra na leitura seguinte e aparece no site em poucos minutos.
2. **Alterada:** atualizamos quando o conteúdo da vaga muda (título, descrição, local, salário, datas…).
3. **Encerrada:** quando some do arquivo em **duas leituras seguidas**, quando a validade (`expirationdate`) passa ou quando o link de candidatura fica fora do ar em duas conferências com 24 horas de intervalo.
4. **Idade máxima:** uma vaga que nunca é renovada (a `date` não avança) é encerrada depois de 60 dias; nenhuma vaga fica mais de 180 dias no ar. Para manter a vaga, republique com a data nova.
5. **Depois de encerrada:** a página mostra "vaga encerrada" com vagas parecidas e, depois de 30 dias, responde 410 para os buscadores.

Mande sempre o **inventário completo** a cada leitura: um arquivo parcial encerra as vagas que ficaram de fora.

## Válvula de segurança

Para um feed quebrado não derrubar as vagas dos seus clientes, a leitura vai para **quarentena** quando:

- mais de **5%** dos registros são recusados; ou
- o número de vagas válidas cai mais de **30%** em relação à leitura anterior (com 50 vagas ou mais).

Em quarentena nada é publicado nem encerrado, e o painel do parceiro mostra o motivo. Corrija o arquivo e peça uma leitura nova.

## Empresa confidencial

Vaga sem `company` aparece como **Confidencial**: não tem página de empresa nem entra no diretório. Se o seu ATS usa o próprio nome no lugar da empresa nas vagas confidenciais, conte no pedido de integração: tratamos esse nome como "Confidencial", e a sua marca nunca aparece como empresa contratante.

## Salário

- Mande só quando a empresa informa: **nunca estimamos salário**.
- Valor único ou faixa, com moeda e período: `R$ 3.000,00`, `R$ 2.800,00 a R$ 3.200,00 por mês`, `US$ 25/hora`.
- Separe o milhar com ponto ou não separe (`3.800` ou `3800`): a vírgula é sempre lida como separador decimal, então `3,800` vira 3,80.
- Sem período, consideramos mensal. Vagas com salário entram no filtro de salário e no dado estruturado do Google.

## Local e idioma

- **Cidade e estado** de onde a pessoa vai trabalhar. Vaga 100% remota pode vir sem cidade, com `isRemote` = `Yes`.
- **País** em código ISO de 2 letras (`BR`, `PT`, `MX`…). Sem país, vale o país padrão do feed.
- **Idioma:** um feed por idioma (português, espanhol ou inglês). O idioma é definido na aprovação; para vagas em mais de um idioma, mande feeds separados.

## Candidatura

- Use a **URL direta da vaga** no seu ATS, com `https://`.
- Nada de página inicial, lista de vagas, encurtador de link, WhatsApp, Telegram ou e-mail: a vaga fica com aviso ou é recusada.
- Acrescentamos `utm_source=joonow&utm_medium=referral` (se a URL já tiver `utm_source`, mantemos a sua). Assim você mede no seu analytics as candidaturas vindas da Joonow.
- Quer receber a candidatura no seu sistema, sem o candidato sair da Joonow? Ligue as [candidaturas por webhook](#candidaturas-por-webhook).

## Qualidade e revisão

Cada vaga passa por regras de risco antes de ir ao ar: pedido de pagamento ao candidato, candidatura por mensageiro, e-mail pessoal na descrição, link encurtado, descrição muito curta, salário fora da realidade do cargo, entre outras. Acima do limite, a vaga fica **retida para revisão** da equipe e não aparece até ser aprovada. O validador mostra esses sinais antes da integração.

## Boas práticas para a vaga aparecer bem

- **Título claro**, só com o cargo: "Analista de RH", e não "VAGA 123 – ANALISTA RH URGENTE!!!".
- **Descrição completa** (atribuições, requisitos, benefícios), em HTML simples com títulos e listas. Mais de 200 caracteres.
- **Salário** sempre que a empresa permitir.
- **E-mail do responsável** em cada vaga: ela sobe na ordenação e, se receber uma denúncia, a equipe tem com quem falar.
- **Site e logo da empresa** em cada vaga (e a descrição, se tiver): a logo aparece na busca e eles ajudam a montar a página da empresa.
- **Cidade e estado corretos**; vaga remota marcada como remota.
- **Datas reais** de publicação e, quando houver, de validade.
- **Ids estáveis:** editar a vaga não muda o `referencenumber`.
- **Uma vaga, um registro:** a mesma vaga em várias cidades vira um registro por cidade, cada um com o seu id.

### No Google

Cada vaga da Joonow tem página própria com o dado estruturado `JobPosting` montado com o seu feed: `baseSalary` só com salário informado, `validThrough` só quando há validade, `jobLocationType` remoto só para vaga 100% remota e o nome da empresa como `hiringOrganization`. Dados completos e corretos no feed ajudam a vaga a aparecer na busca de vagas do Google.

## Candidaturas por webhook

Além de mandar vagas, o seu ATS pode **receber candidaturas** da Joonow. Com o webhook ligado, quem se candidata a uma vaga dos seus feeds faz isso na própria Joonow: vê a empresa, o seu sistema como destino e a lista do que vai, confirma, e a Joonow entrega a candidatura ao seu sistema, que faz o cadastro em nome da empresa cliente. As vagas de quem não ligou continuam como sempre: o candidato segue para a URL de candidatura. Valem os [termos de integração para ATS](/pt-br/termos-de-integracao-ats).

### Como ligar

No painel do parceiro, em **Candidaturas**:

1. Aceite os termos de integração (fica registrado a versão, a data e quem aceitou).
2. Informe o endereço HTTPS que recebe os envios: porta 443, um nome de domínio (não um IP) e sem usuário e senha na URL. Não seguimos redirecionamentos.
3. Gere a chave secreta. Ela aparece uma vez: guarde num cofre de segredos.
4. Mande a candidatura de teste (dados fictícios, `"test": true`) e responda 2xx.
5. Ligue o recebimento. Trocar o endereço desliga até um teste novo.

### O envio

Cada envio é um `POST` no seu endereço, com `Content-Type: application/json; charset=utf-8`, corpo em UTF-8 e estes cabeçalhos:

| Cabeçalho | Exemplo | Para que serve |
|---|---|---|
| `Joonow-Event-Id` | `0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21` | Id único do envio (o mesmo `id` do corpo): a sua chave de idempotência. |
| `Joonow-Event-Type` | `application.submitted` | Tipo do evento (o mesmo `type` do corpo). |
| `Joonow-Timestamp` | `1793628191` | Hora da assinatura, em segundos Unix. |
| `Joonow-Signature` | `v1=5d4a…` | HMAC-SHA256 da chave sobre `{timestamp}.{corpo}`, em hexadecimal. |
| `User-Agent` | `JoonowWebhooks/1.0` | Identifica a Joonow (não serve como prova: a prova é a assinatura). |

O seu servidor tem 10 segundos para responder.

### Corpo da candidatura (`application.submitted`, versão 1)

```json
{
  "id": "0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21",
  "type": "application.submitted",
  "version": "1",
  "created_at": "2026-11-02T14:03:11Z",
  "test": false,
  "data": {
    "application": { "id": "0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21", "submitted_at": "2026-11-02T14:03:11Z", "source": "joonow" },
    "job": {
      "external_id": "ACME-4821",
      "joonow_id": "k3j9x2m4pq",
      "title": "Analista de Suporte Júnior",
      "company": "Acme Logística",
      "url": "https://www.joonow.com/pt-br/vaga/analista-de-suporte-junior-k3j9x2m4pq"
    },
    "candidate": {
      "name": "Maria Souza",
      "email": "maria.souza@example.com",
      "phone": "+55 81 99999-0000",
      "location": { "city": "Recife", "region": "PE", "country": "BR" },
      "experiences": [
        { "title": "Atendente", "company": "Loja Exemplo", "start": "2023-04", "current": true, "summary": "Atendimento e pós-venda." }
      ],
      "education": [
        { "course": "Administração", "institution": "Faculdade Exemplo", "level": "undergraduate", "start": "2019-02", "end": "2022-12", "current": false }
      ],
      "skills": ["Atendimento ao cliente", "Excel"],
      "languages": [{ "name": "Inglês", "level": "intermediate" }],
      "salary_expectation": { "amount": 3000, "currency": "BRL", "period": "month" }
    },
    "answers": [{ "question": "Tem disponibilidade para trabalhar aos sábados?", "answer": "Sim" }],
    "resume": {
      "file_name": "maria-souza.pdf",
      "content_type": "application/pdf",
      "size_bytes": 182344,
      "url": "https://www.joonow.com/api/partners/v1/resumes/4bZ9qT…",
      "expires_at": "2026-11-05T14:03:12Z"
    }
  }
}
```

| Campo | Vai sempre? | O que é |
|---|---|---|
| `id` | sim | Id do envio (UUID), igual ao cabeçalho `Joonow-Event-Id`. |
| `type` | sim | `application.submitted`. |
| `version` | sim | `1`. Campo novo pode aparecer sem mudar a versão; mudança que quebra vira `2`, com aviso de 90 dias. Ignore o que não conhece. |
| `created_at` | sim | Quando o candidato confirmou (ISO 8601, UTC). |
| `test` | sim | `true` na candidatura de teste do painel: responda 2xx, mas não cadastre ninguém. |
| `data.application.id` | sim | Id da candidatura (o mesmo do envio). É com ele que você manda o andamento de volta. |
| `data.application.source` | sim | `joonow`. |
| `data.job.external_id` | sim | O id da vaga no seu feed (`referencenumber`, `partnerJobId`). |
| `data.job.joonow_id`, `title`, `company`, `url` | sim | A vaga como está na Joonow. |
| `data.candidate.name`, `email` | sim | Nome e e-mail da conta do candidato. |
| `data.candidate.phone` | se preenchido | Como o candidato digitou. |
| `data.candidate.location` | se preenchido | `city`, `region` (UF ou estado) e `country` (ISO 3166-1, 2 letras). |
| `data.candidate.links` | reservado | Lista de `{ "type", "url" }`. Ainda não vai: entra quando o perfil tiver links. |
| `data.candidate.experiences` | se preenchido | `title`, `company`, `start` e `end` (`AAAA-MM`), `current`, `summary`. |
| `data.candidate.education` | se preenchido | `course`, `institution`, `level` (`high_school`, `technical`, `undergraduate`, `postgraduate`, `masters`, `doctorate`, `course`), `start`, `end`, `current`. |
| `data.candidate.skills` | se preenchido | Lista de textos. |
| `data.candidate.languages` | se preenchido | `name` e `level` (`basic`, `intermediate`, `advanced`, `fluent`, `native`). |
| `data.candidate.salary_expectation` | se preenchido | `amount`, `currency` (ISO 4217) e `period` (`hour`, `day`, `week`, `month`, `year`). |
| `data.answers` | sim (pode ser vazia) | `question` e `answer`: as respostas às perguntas da vaga. |
| `data.resume` | se houver currículo | `file_name`, `content_type`, `size_bytes` e o arquivo: `url` com `expires_at` (link) ou `content_base64` (no envio). |

Só vai o que o candidato preencheu e viu na tela de confirmação: campo vazio fica de fora (nada de `null` nem lista vazia, exceto `answers`). Dado sensível (como deficiência) não vai nesta versão, nem as perguntas sensíveis da vaga: a tela de confirmação não as mostra.

### Currículo

- **Link temporário (padrão):** `data.resume.url` é um `GET` assinado que vale 3 dias a partir de cada tentativa de envio. Baixe logo ao receber e guarde no seu sistema. O link não pede login: trate-o como segredo e não o registre em logs. Vencido, ou depois que o candidato exclui a conta, responde 404.
- **No envio:** escolha no painel; o arquivo vai em `data.resume.content_base64` (PDF ou DOCX, até 5 MB).

### Conferir a assinatura

1. Leia o corpo **cru**, os bytes como chegaram, antes de converter em JSON.
2. Confira que `Joonow-Timestamp` está a no máximo 5 minutos da sua hora, para mais ou para menos.
3. Calcule `HMAC-SHA256(chave, "{Joonow-Timestamp}.{corpo}")` em hexadecimal minúsculo e ponha `v1=` na frente.
4. Compare, em tempo constante, com cada item de `Joonow-Signature` (separados por vírgula). Basta um conferir: na troca da chave vão dois, o da chave nova e o da anterior, por 24 horas.
5. Sem assinatura válida ou fora da janela: responda 401 e não grave nada.

```js
import crypto from 'node:crypto';

function assinaturaValida(corpoCru, cabecalhos, chave) {
  const t = cabecalhos['joonow-timestamp'] ?? '';
  if (!/^\d+$/.test(t) || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const esperada = Buffer.from('v1=' + crypto.createHmac('sha256', chave).update(t + '.').update(corpoCru).digest('hex'));
  return (cabecalhos['joonow-signature'] ?? '').split(',').map((s) => Buffer.from(s.trim()))
    .some((s) => s.length === esperada.length && crypto.timingSafeEqual(s, esperada));
}
```

### Resposta, idempotência e novas tentativas

- Responda **2xx só depois de gravar** a candidatura. O corpo da resposta é ignorado.
- O mesmo `id` pode chegar mais de uma vez (nova tentativa depois de uma falha de rede ou o candidato reenviando uma candidatura que não chegou). Se você já tem esse `id`, responda 2xx sem cadastrar de novo.
- Qualquer outra resposta, ou nenhuma em 10 segundos, é falha. Redirecionamento (3xx) também é falha.
- Tentamos de novo com intervalos crescentes (1 min, 5 min, 30 min, 2 h, 6 h e depois a cada 12 h) até 72 horas depois da confirmação. Então paramos e avisamos por e-mail o candidato e o contato do seu ATS.
- **410 Gone** quer dizer "esta vaga não recebe mais candidaturas": paramos na hora e avisamos.
- O painel mostra cada envio, as tentativas, a última resposta e as falhas.

### Andamento de volta

Para o candidato acompanhar na Joonow, mande o andamento de cada candidatura para `https://www.joonow.com/api/partners/v1/events`, com `POST`, `Content-Type: application/json` e os mesmos `Joonow-Timestamp` e `Joonow-Signature`, calculados com a sua chave do mesmo jeito.

```json
{ "type": "application.in_review", "application_id": "0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21", "occurred_at": "2026-11-03T10:15:00Z" }
```

- `type`: `application.received` (gravou), `application.in_review` (em análise) ou `application.closed` (o processo terminou para o candidato). O andamento só anda para frente: um evento atrasado não volta a situação.
- Respostas: `200` com `{"ok":true,"applied":true}` (ou `false`, quando nada mudou), `400` para evento desconhecido, `401` para assinatura, hora ou id errados, `413` acima de 16 KB e `429` em excesso.
- A candidatura de teste também aceita o andamento: use para testar a volta.
- O candidato vê o andamento só na área dele e recebe um e-mail quando a candidatura entra em análise ou é encerrada.

### Pedidos de correção e exclusão

Quando o candidato pede à Joonow para corrigir ou apagar os dados dele, avisamos cada sistema que recebeu uma candidatura dele (LGPD, art. 18, § 6º), pelo mesmo webhook, com a mesma assinatura e as mesmas novas tentativas:

```json
{
  "id": "0193f611-2c40-7e2b-8a51-0d9e7f3a1c55",
  "type": "candidate.erasure_requested",
  "version": "1",
  "created_at": "2026-11-20T09:00:00Z",
  "test": false,
  "data": {
    "reason": "account_deleted",
    "candidate": { "email": "maria.souza@example.com" },
    "applications": [{ "id": "0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21", "job_external_id": "ACME-4821", "submitted_at": "2026-11-02T14:03:11Z" }],
    "respond_by": "2026-12-05T09:00:00Z"
  }
}
```

- `candidate.erasure_requested`: apague (ou peça à empresa cliente que apague) o que veio da Joonow. `reason` é `account_deleted` (o candidato excluiu a conta) ou `candidate_request` (pediu pela central de privacidade).
- `candidate.correction_requested`: `data.candidate` traz os dados de agora, no mesmo formato da candidatura, para trocar os que você recebeu.
- Repasse à empresa cliente e confirme em até 15 dias (`respond_by`) com um evento de volta: `{ "type": "candidate.erasure_completed", "request_id": "<id do pedido>" }` (ou `candidate.correction_completed`).

### Segurança

- Só HTTPS, com TLS 1.2 ou mais novo. Recuse envios sem assinatura válida ou fora da janela de 5 minutos.
- Guarde a chave num cofre de segredos e troque no painel se suspeitar de vazamento.
- Não registre o corpo das candidaturas nem os links de currículo em logs abertos.
- Os envios saem de endereços de IP que podem mudar: confie na assinatura, não no IP.

## Como pedir a integração

1. Monte o feed seguindo esta especificação.
2. Cole a URL no [validador](/pt-br/integre-seu-ats/validador) e corrija o que ele apontar.
3. Com o feed validado, preencha o pedido ali mesmo. A equipe confere em até 2 dias úteis.
4. Aprovado, você recebe por e-mail o acesso ao painel do parceiro, com leituras, vagas no ar, recusas e cliques por mês.

Dúvidas técnicas? Fale com a gente pelo [Fale conosco](/pt-br/contato#publishing).

