A API v1 da Winx é uma API REST para gerenciamento de empresas, funcionários e filtros, com foco em operações CRUD essenciais e conformidade com LGPD.
https://scenic-tokyo-3bje4ahyb4rd.on-vapor.comhttps://bubbling-havana-prclnzvluehc.on-vapor.comPara obter acesso ao ambiente de desenvolvimento/homologação:
Características do Sandbox:
Para acesso ao ambiente de produção:
Requisitos para Produção:
A API utiliza chaves de acesso geradas no Dashboard Administrativo da aplicação.
🌐 Dashboard Homologação: Dashboard Winx
🌐 Dashboard Produção: Dashboard Winx
Authorization: Bearer {sua-chave-de-acesso}Authorization: Bearer {sua-chave-de-acesso}
Content-Type: application/json
Accept: application/json
Resource-Id: {uuid-da-empresa}
Quando usar Resource-Id:
# ✅ Exemplo de uso correto
GET /v1/companies
Authorization: Bearer wjnx_sk_live_1234567890abcdefghijklmnop
Content-Type: application/json
Accept: application/json
🗑️ Revogação: Para revogar acesso, exclua a chave no Dashboard e gere uma nova.
🔄 Rotação: Recomenda-se rotacionar chaves periodicamente por segurança.
Por que 500/min?
| Cenário | Requisições/min | Status |
|---|---|---|
| Sincronização pequena (< 100 funcionários) | ~5-20 | ✅ Bem dentro do limite |
| Sincronização média (100-1000 funcionários) | ~50-200 | ✅ Dentro do limite |
| Importação grande (1000+ funcionários) | ~300-500 | ⚠️ Pode necessitar otimização |
| Operação massiva (10000+ funcionários) | 500+ | ❌ Contate suporte técnico |
Para casos especiais:
Cada resposta inclui headers informativos:
X-RateLimit-Limit: 500 # Limite máximo
X-RateLimit-Remaining: 999 # Requisições restantes
X-RateLimit-Reset: 1640995200 # Timestamp de reset
Status: 429 Too Many Requests
{
"id": "rate-limit-error-uuid",
"status": 429,
"title": "Too Many Requests",
"detail": "Limite de requisições excedido. Tente novamente em 60 segundos.",
"meta": {
"retry_after": 60,
"limit": 500,
"window": "1 minute"
}
}
X-RateLimit-Remainingretry_after{
"data": {
"id": "uuid-funcionario",
"name": "João Silva",
"email": "joao@empresa.com",
"created_at": "2024-01-01T10:00:00+00:00"
}
}
{
"data": [
{
"id": "uuid-funcionario-1",
"name": "João Silva"
},
{
"id": "uuid-funcionario-2",
"name": "Maria Santos"
}
],
"links": {
"first": "https://api.winx.ai/v1/employees?page=1",
"last": "https://api.winx.ai/v1/employees?page=10",
"prev": null,
"next": "https://api.winx.ai/v1/employees?page=2"
},
"meta": {
"current_page": 1,
"per_page": 10,
"total": 100,
"last_page": 10
}
}
{
"id": "error-unique-uuid",
"status": 422,
"title": "Validation Error",
"detail": "Os dados fornecidos contêm erros de validação",
"meta": {
"errors": {
"document": ["The document field is required."],
"email": ["The email has already been taken."]
}
}
}
| Código | Significado | Quando Ocorre |
|---|---|---|
200 | OK | GET, PUT bem-sucedidos |
201 | Created | POST bem-sucedido |
204 | No Content | DELETE bem-sucedido |
401 | Unauthorized | Token inválido/ausente |
403 | Forbidden | Sem permissão |
404 | Not Found | Recurso não encontrado |
422 | Validation Error | Dados inválidos |
429 | Too Many Requests | Rate limit excedido |
500 | Internal Error | Erro interno do servidor |
Resposta:
Content-Type: application/json; charset=UTF-8
X-RateLimit-Limit:500
X-RateLimit-Remaining: 499
Requisição:
Authorization: Bearer {sua-chave}
Content-Type: application/json
Accept: application/json
Resource-Id: {uuid-empresa} # Opcional
links e metaO campo fields dos funcionários aceita dois formatos:
{
"fields": [
{
"id": "uuid-filter-departamento",
"value": "Tecnologia"
},
{
"id": "uuid-filter-cargo",
"value": "Desenvolvedor"
}
]
}
{
"fields": [
{
"key": "departamento",
"value": "Tecnologia"
},
{
"key": "cargo",
"value": "Desenvolvedor"
}
]
}
| Formato | Quando Usar | Vantagem |
|---|---|---|
| ID + Value | Integrações modernas | UUID garante unicidade, mais seguro |
| Key + Value | Sistemas legados | Mais legível, fácil manutenção |
# Para obter UUIDs e keys dos filtros
GET /v1/filters
Resposta:
{
"data": [
{
"id": "uuid-filter-departamento", # Use no formato ID+Value
"key": "departamento", # Use no formato Key+Value
"is_required": true,
"default_value": "Não informado"
}
]
}
is_required: true) devem estar presentesCada empresa tem um identify_key que determina qual campo é obrigatório para funcionários:
document (CPF) obrigatórioemail obrigatório{
"identify_key": "document", // ou "email"
}
Impacto nas Validações:
Fields são pares chave-valor que vinculam funcionários aos filtros da empresa.
Estrutura:
{
"fields": [
{
"id": "uuid-do-filter",
"value": "Valor Específico"
}
]
}
Regras:
is_required = true devem estar presentesvalue estiver vazio, usa default_value do filtrofilter_id deve existir nos filtros ativos da empresafilter_id deve aparecer apenas uma vez por funcionárioFuncionários podem ser incluídos/removidos de campanhas através dos campos:
include_campaign (POST/PUT):
true: Inclui funcionário em todas as campanhas ativasfalse: Não inclui em campanhasfiring_date preenchidoremove_campaign (PUT apenas):
true: Remove funcionário de todas as campanhas ativasfalse: Mantém funcionário nas campanhas atuaisinclude_campaign1. PUT /v1/employees/{id} com firing_date
↓
2. Validações obrigatórias: is_voluntary_firing, data válida
↓
3. Registro atualizado (funcionário ainda visível)
↓
4. EmployeeObserver detecta firing_date
↓
5. Soft delete automático (deleted_at preenchido)
↓
6. Funcionário desaparece das consultas GET
1. DELETE /v1/employees/{id}
↓
2. EmployeeRepository::delete() executa:
- Remove fields fisicamente
- Remove vínculos de campanhas
- Aplica soft delete
↓
3. Funcionário desaparece imediatamente das consultas
| Aspecto | firing_date (PUT) | DELETE (Direto) |
|---|---|---|
| Momento do soft delete | Após Observer processar | Imediato na requisição |
| Histórico de desligamento | ✅ Preservado (data, motivo, tipo) | ❌ Apenas deleted_at |
| Visibilidade temporária | ✅ Permanece visível até Observer | ❌ Invisível imediatamente |
| Campanhas | Mantidas (exceto se remove_campaign: true) | ❌ Removidas automaticamente |
| Fields | ✅ Preservados no momento do PUT | ❌ Removidos imediatamente |
| Auditoria/Compliance | ✅ Processo rastreável | ⚠️ Menos informação |
| Uso recomendado | Desligamento formal/HR | Limpeza/correção de dados |
PUT /v1/employees/uuid
{
"firing_date": "31/12/2024",
"is_voluntary_firing": true,
"reason_firing": "Pedido de demissão",
"remove_campaign": true // Remove de campanhas se necessário
}
DELETE /v1/employees/uuid
# Resultado: Soft delete + limpeza imediata
Durante processo firing_date:
// Estado 1: Após PUT, antes do Observer
{
"firing_date": "31/12/2024",
"is_voluntary_firing": true,
"deleted_at": null // ← Ainda não processado
}
// Funcionário ainda aparece em GET /v1/employees
// Estado 2: Após Observer (poucos segundos depois)
{
"firing_date": "31/12/2024",
"is_voluntary_firing": true,
"deleted_at": "2024-01-01T10:00:00+00:00" // ← Processado
}
// Funcionário desaparece de GET /v1/employees
Ambos os processos:
php artisan employee:restore {uuid}
12345678901123.456.789-01, 123456789, 00000000000joao@empresa.com, test+tag@domain.co.ukemail-invalido, @domain.compropaganistas/laravel-phoneBR)PhoneExists+5511999999999, 551199999999911999999999, 551133333333 (fixo)EMP001, FUNC-123, João-TIdd/mm/yyyybirthdate: < hojehiring_date: <= hoje, > birthdate (se birthdate existir)firing_date: <= hoje, > hiring_date (se hiring_date existir)FullName() personalizadaJoão Silva, Maria Santos OliveiraJoão, 123, joão silva (minúscula)✅ DADOS PRESERVADOS (permanecem no banco):
name, social_name, email, phone, documentid, internal_id, company_idbirthdate, hiring_date, firing_date, created_at, updated_atis_voluntary_firing, reason_firing, is_on_leave, languagedeleted_at (preenchido com data/hora da exclusão)❌ RELACIONAMENTOS REMOVIDOS:
employee_fields são apagados fisicamente// O que acontece no EmployeeRepository::delete()
1. DELETE FROM employee_fields WHERE employee_id = ? // Remove fields
2. DELETE FROM campaign_employees WHERE employee_id = ? // Remove campanhas
3. UPDATE employees SET deleted_at = NOW() WHERE id = ? // Soft delete
Resultado no Banco:
-- ANTES do soft delete
employees: {id: uuid, name: "João", email: "joao@...", deleted_at: NULL}
employee_fields: [{employee_id: uuid, filter_id: uuid, value: "TI"}]
campaign_employees: [{employee_id: uuid, campaign_id: uuid}]
-- DEPOIS do soft delete
employees: {id: uuid, name: "João", email: "joao@...", deleted_at: "2024-01-01 10:00:00"}
employee_fields: [] // ← Tabela vazia para este employee
campaign_employees: [] // ← Sem vínculos para este employee
Exemplo prático:
// Funcionário ANTES da exclusão
{
"id": "9c73a4aa-8094-4241-ad05-658436f471fe",
"name": "João Silva",
"document": "12345678901",
"email": "joao@empresa.com",
"internal_id": "EMP001",
"fields": [
{"id": "filter-dept", "value": "TI"},
{"id": "filter-cargo", "value": "Dev"}
],
"deleted_at": null
}
// Registro no banco DEPOIS da exclusão (invisível via GET)
{
"id": "9c73a4aa-8094-4241-ad05-658436f471fe", // ✅ Preservado
"name": "João Silva", // ✅ Preservado
"document": "12345678901", // ✅ Preservado
"email": "joao@empresa.com", // ✅ Preservado
"internal_id": "EMP001", // ✅ Preservado
"deleted_at": "2024-01-01T10:00:00+00:00" // ✅ Timestamp da exclusão
}
// Fields foram fisicamente removidos:
employee_fields: [] // ❌ Tabela vazia para este funcionário
❌ Funcionários soft-deleted NÃO aparecem em:
GET /v1/employees (listagem)GET /v1/employees?search=termo (busca)GET /v1/employees/{id} (detalhes)🔧 Implementação Laravel:
// Laravel automaticamente aplica whereNull('deleted_at') em todas as consultas
Employee::where('company_id', $companyId)->get();
// Retorna apenas registros com deleted_at = NULL
// Para incluir soft-deleted seria necessário:
Employee::withTrashed()->where('company_id', $companyId)->get();
// Mas este comportamento NÃO está disponível via API REST
🎯 Exemplo Prático:
// ANTES do soft delete - GET /v1/employees retorna:
{
"data": [
{
"id": "uuid-joao",
"name": "João Silva",
"email": "joao@empresa.com"
},
{
"id": "uuid-maria",
"name": "Maria Santos",
"email": "maria@empresa.com"
}
],
"meta": {"total": 2}
}
// DEPOIS do soft delete do João - GET /v1/employees retorna:
{
"data": [
{
"id": "uuid-maria",
"name": "Maria Santos",
"email": "maria@empresa.com"
}
],
"meta": {"total": 1} // ← João desapareceu das consultas
}
⚠️ Importante:
A busca no parâmetro search utiliza LIKE parcial, não busca exata:
// Laravel scopeSearch implementa:
$query->where('name', 'LIKE', "%{$search}%")
->orWhere('document', 'LIKE', "%{$search}%")
->orWhere('email', 'LIKE', "%{$search}%")
->orWhere('phone', 'LIKE', "%{$search}%")
->orWhere('internal_id', 'LIKE', "%{$search}%");
Busca por Internal ID:
GET /v1/employees?search=600
# ✅ RETORNA todos que CONTÊM "600":
- internal_id: "6000001"
- internal_id: "60000011"
- internal_id: "EMP6001"
- internal_id: "A600B"
Busca por Nome:
GET /v1/employees?search=Silva
# ✅ RETORNA todos que CONTÊM "Silva":
- name: "João Silva"
- name: "Maria Silva Santos"
- name: "Pedro da Silva"
- social_name: "Ana Silva"
Busca por CPF:
GET /v1/employees?search=123
# ✅ RETORNA todos CPFs que CONTÊM "123":
- document: "12345678901"
- document: "98712345678"
- document: "11123456789"
search=600 ≠ internal_id = "600"| Campo | Busca | Exemplos de Match |
|---|---|---|
internal_id | Contém substring | "EMP001" encontra "EMP001", "EMP0011", "TEMP001" |
document | Contém dígitos | "123" encontra "12345678901", "98712345678" |
email | Contém texto | "joao" encontra "joao@empresa.com", "joaosilva@email.com" |
phone | Contém números | "99" encontra "+5511999888777", "+5511988997766" |
name/social_name | Contém nome | "Maria" encontra "Maria Santos", "Ana Maria" |
❌ NÃO existe: GET /v1/employees/{internal_id} (por internal_id)
✅ Use sempre: GET /v1/employees?search={valor} (busca inteligente)
# Para buscar funcionário com internal_id "EMP001":
GET /v1/employees?search=EMP001
# Resposta paginada mesmo que seja 1 resultado:
{
"data": [
{
"id": "uuid-encontrado",
"internal_id": "EMP001",
"name": "João Silva"
}
],
"meta": {
"total": 1,
"current_page": 1
}
}
✅ Funcionalidades Incluídas:
Gestão de Campanhas:
Autenticação Avançada:
Funcionalidades Administrativas:
Integrações Externas:
✅ Cenários Ideais:
❌ Cenários Não Suportados:
| Necessidade | Alternativa Recomendada |
|---|---|
| Autenticação avançada | Configuração futura via Dashboard |
| E-mails personalizados | Sistema automático + customização via Dashboard |
| Gestão de campanhas | CRUD via Dashboard administrativo |
| Relatórios de compliance | Usar endpoints GET respeitando LGPD |
| Webhooks | Implementação futura da API |
{
"id": "auth-error-uuid",
"status": 401,
"title": "Unauthorized",
"detail": "Token de acesso inválido ou expirado"
}
{
"id": "permission-error-uuid",
"status": 403,
"title": "Forbidden",
"detail": "Acesso negado ao recurso solicitado"
}
{
"id": "not-found-uuid",
"status": 404,
"title": "Not Found",
"detail": "Funcionário não encontrado"
}
{
"id": "validation-error-uuid",
"status": 422,
"title": "Validation Error",
"detail": "Os dados fornecidos contêm erros",
"meta": {
"errors": {
"document": ["The document field is required."],
"email": ["The email has already been taken."],
"phone": ["The phone format is invalid."]
}
}
}
{
"id": "rate-limit-uuid",
"status": 429,
"title": "Too Many Requests",
"detail": "Limite de requisições excedido. Tente novamente em 60 segundos."
}
{
"id": "server-error-uuid",
"status": 500,
"title": "Internal Server Error",
"detail": "Erro interno do servidor. Contate o suporte."
}
curl -X POST https://api.winx.ai/v1/employees \
-H "Authorization: Bearer seu-token" \
-H "Content-Type: application/json" \
-d '{
"name": "Maria Santos Silva",
"social_name": "Maria Santos",
"email": "maria@empresa.com",
"phone": "+5511987654321",
"document": "12345678901",
"internal_id": "FUNC001",
"birthdate": "15/03/1985",
"hiring_date": "01/06/2020",
"fields": [
{
"id": "uuid-filter-departamento",
"value": "Recursos Humanos"
},
{
"id": "uuid-filter-cargo",
"value": "Analista"
}
],
"include_campaign": true,
"is_on_leave": false
}'
# Busca por nome
curl "https://api.winx.ai/v1/employees?search=Maria&page=1" \
-H "Authorization: Bearer seu-token"
# Busca por CPF
curl "https://api.winx.ai/v1/employees?search=12345678901" \
-H "Authorization: Bearer seu-token"
# Busca por ID interno
curl "https://api.winx.ai/v1/employees?search=FUNC001" \
-H "Authorization: Bearer seu-token"
curl -X PUT https://api.winx.ai/v1/employees/uuid-funcionario \
-H "Authorization: Bearer seu-token" \
-H "Content-Type: application/json" \
-d '{
"firing_date": "15/12/2024",
"is_voluntary_firing": false,
"reason_firing": "Reestruturação organizacional",
"remove_campaign": true,
"fields": [
{
"id": "uuid-filter-departamento",
"value": "Ex-funcionário"
}
],
"is_on_leave": false
}'
# Token da matriz operando na filial
curl https://api.winx.ai/v1/employees \
-H "Authorization: Bearer token-matriz" \
-H "Resource-Id: uuid-filial"
Problema: Erro 422 ao criar funcionário
{
"meta": {
"errors": {
"document": ["The document field is required."]
}
}
}
Solução: Verificar o identify_key da empresa. Se for "document", o campo CPF é obrigatório.
Problema: Erro 422 em fields
{
"meta": {
"errors": {
"fields": ["Filtros obrigatórios ausentes: Departamento, Cargo"]
}
}
}
Solução: Incluir todos os filtros com is_required = true no array fields.
Problema: Funcionário não aparece após criação
Solução: Verificar se não foi soft-deleted automaticamente devido ao firing_date. Use withTrashed() para confirmar.
Problema: Erro 401 "Unauthorized"
Solução: Verificar se o token está válido e incluído corretamente no header Authorization.