openapi: 3.0.3
info:
  title: 4.works API externa
  description: |
    API REST v1 para integrações de servidor a servidor. Autenticação por **Bearer Token**: Api Key (Administração >  Configurações).

    Base URL (típico): `https://app.4.works/api-externa/v1`

    Todas as listagens usam `page` e `per_page` (máx. 100). Resposta paginada inclui `meta.total`, `meta.page`, `meta.per_page`, `meta.total_pages`.

    Limite de taxa: por padrão **30 req/s por empresa**. Respostas incluem `X-RateLimit-Remaining`; `429` quando excedido.
  version: "1.0.0"
servers:
  - url: /api-externa/v1
    description: Relativo ao host do app (ex. https://app.4.works)
security:
  - bearerAuth: []
paths:
  /ping:
    get:
      summary: Health check (sem Bearer)
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  service: { type: string }
                  version: { type: string }
  /me:
    get:
      summary: Dados mínimos da empresa autenticada
      responses:
        "200":
          description: OK
        "401":
          description: Token inválido ou ausente
  /public/events:
    get:
      summary: Listar eventos públicos de todas as empresas (SEM Bearer)
      description: |
        Endpoint **aberto** — não requer Bearer token. Lista todos os eventos
        **futuros** que estão aprovados, não-ocultos e não-deletados de
        qualquer empresa ativa na plataforma, com paginação. Ordenado por
        `event_start_at ASC` (mais próximos primeiro).

        Por razões de segurança/privacidade desta rota pública, o payload
        **não** expõe `id` do evento nem qualquer dado da empresa (id,
        fake_id, nome). Use o campo `public_url` para enviar o usuário até a
        página standalone do evento — lá ele vê tudo (empresa, ingressos,
        formulário de inscrição etc.).

        **Rate limit por IP**: ~10 req/s (mais baixo que o autenticado).
        Respostas incluem `X-RateLimit-Remaining`; `429` quando excedido.
      security: []
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        title: { type: string, example: "Workshop de Marketing Digital" }
                        description: { type: string, nullable: true, description: "HTML removido, máx. 1500 chars." }
                        cover_url: { type: string, nullable: true, example: "https://vp2uploads.s3.sa-east-1.amazonaws.com/4w/events/cover.jpg" }
                        start_at: { type: string, nullable: true, example: "2026-06-15 19:00:00" }
                        start_at_iso: { type: string, nullable: true, example: "2026-06-15T19:00:00-03:00" }
                        end_at: { type: string, nullable: true, example: "2026-06-15 22:00:00" }
                        end_at_iso: { type: string, nullable: true, example: "2026-06-15T22:00:00-03:00" }
                        type: { type: string, nullable: true, enum: [on-line, presencial] }
                        location: { type: string, nullable: true, example: "Online" }
                        public_url: { type: string, example: "https://app.4.works/sites/eventos/1569-workshop-de-marketing-digital" }
                  meta:
                    type: object
                    properties:
                      total: { type: integer }
                      page: { type: integer }
                      per_page: { type: integer }
                      total_pages: { type: integer }
        "429":
          description: Rate limit excedido (10 req/s por IP). Veja header `Retry-After`.
  /public/ai-results:
    get:
      summary: Listar os últimos resultados gerados pela Otto IA (SEM Bearer)
      description: |
        Endpoint **aberto** — não requer Bearer token. Lista os últimos (até 500)
        resultados gerados pelas IAs do sistema (sites, blog, posts de redes
        sociais, banners, templates de e-mail e imagens avulsas), de qualquer
        empresa, com paginação. Ordenado do mais recente para o mais antigo.

        Por razões de segurança/privacidade desta rota pública, o payload
        **não** expõe `company_id` nem `user_id`. Os textos são limpos de HTML
        e truncados em 1500 caracteres.

        **Rate limit por IP**: ~10 req/s (mais baixo que o autenticado).
        Respostas incluem `X-RateLimit-Remaining`; `429` quando excedido.
      security: []
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        result_type: { type: string, nullable: true, enum: [site_url, blog, social_post, banner, template_email, random_image] }
                        result_url: { type: string, nullable: true, example: "https://app.4.works/sites/meu-site" }
                        result_body: { type: string, nullable: true, description: "HTML removido, máx. 1500 chars." }
                        created_at: { type: string, nullable: true, example: "2026-05-29 11:40:00" }
                        created_at_iso: { type: string, nullable: true, example: "2026-05-29T11:40:00-03:00" }
                  meta:
                    type: object
                    properties:
                      total: { type: integer }
                      page: { type: integer }
                      per_page: { type: integer }
                      total_pages: { type: integer }
        "429":
          description: Rate limit excedido (10 req/s por IP). Veja header `Retry-After`.
  /nutrir/leads-lists:
    get:
      summary: Listar listas Nutrir
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
      responses:
        "200": { description: OK }
    post:
      summary: Criar lista
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
      responses:
        "201": { description: Criado }
  /nutrir/leads-lists/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Obter lista por id
      responses:
        "200": { description: OK }
        "404": { description: Não encontrado }
    patch:
      summary: Atualizar nome da lista
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
      responses:
        "200": { description: OK }
    delete:
      summary: Remover lista (e leads associados; lista padrão do site não pode ser removida)
      responses:
        "204": { description: Sem corpo }
        "422": { description: Regra de negócio }
  /nutrir/leads:
    get:
      summary: Listar leads de uma lista
      parameters:
        - name: list_id
          in: query
          required: true
          schema: { type: integer }
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
        - name: q
          in: query
          schema: { type: string }
        - name: sort_by
          in: query
          schema: { type: string, enum: [first_name, email, phone, points, created_at] }
        - name: sort_dir
          in: query
          schema: { type: string, enum: [asc, desc] }
      responses:
        "200": { description: OK }
    post:
      summary: Criar lead
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                list_id: { type: integer }
                first_name: { type: string }
                lastname: { type: string }
                email: { type: string }
                phone: { type: string }
                extra_json: { type: string, description: JSON string ou objeto serializado }
                log_text: { type: string }
      responses:
        "201": { description: Criado }
  /nutrir/leads/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Obter lead
      responses:
        "200": { description: OK }
    patch:
      summary: Atualizar lead
      responses:
        "200": { description: OK }
    delete:
      summary: Soft-delete do lead
      responses:
        "204": { description: Sem corpo }
  /crm/tags:
    get:
      summary: Listar tags
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
        - name: q
          in: query
          schema: { type: string }
      responses:
        "200": { description: OK }
    post:
      summary: Criar tag
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [tag_name]
              properties:
                tag_name: { type: string }
      responses:
        "201": { description: Criado }
  /crm/tags/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Obter tag
      responses:
        "200": { description: OK }
    patch:
      summary: Atualizar tag
      responses:
        "200": { description: OK }
    delete:
      summary: Remover tag (e relações user_tags)
      responses:
        "204": { description: Sem corpo }
  /crm/deal-menus:
    get:
      summary: Listar menus do tipo funil (deals) — somente leitura
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
      responses:
        "200": { description: OK }
  /crm/menus/{menu_id}/deal-steps:
    parameters:
      - name: menu_id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Listar etapas do funil
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
      responses:
        "200": { description: OK }
    post:
      summary: Criar etapa
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                step_order: { type: integer }
      responses:
        "201": { description: Criado }
  /crm/deal-steps/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Obter etapa
      responses:
        "200": { description: OK }
    patch:
      summary: Atualizar etapa
      responses:
        "200": { description: OK }
    delete:
      summary: Remover etapa (bloqueado se houver negociações)
      responses:
        "204": { description: Sem corpo }
  /crm/menus/{menu_id}/deals:
    parameters:
      - name: menu_id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Listar negociações do funil
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
        - name: step_id
          in: query
          schema: { type: integer }
        - name: status
          in: query
          schema: { type: string }
        - name: q
          in: query
          schema: { type: string }
      responses:
        "200": { description: OK }
    post:
      summary: Criar negociação
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name, step_id]
              properties:
                name: { type: string }
                step_id: { type: integer }
                responsible_user_id: { type: integer, description: "Se omitido, usa primeiro admin da empresa" }
                temperature: { type: string }
                total_value: { type: number }
                expected_close_date: { type: string, format: date }
                source: { type: string }
                campaign: { type: string }
                company_relation_id: { type: integer }
                description: { type: string }
      responses:
        "201": { description: Criado }
  /crm/deals/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Obter negociação
      responses:
        "200": { description: OK }
    patch:
      summary: Atualizar campos permitidos da negociação
      responses:
        "200": { description: OK }
    delete:
      summary: Soft-delete (deleted_at)
      responses:
        "204": { description: Sem corpo }
  /crm/menus/{menu_id}/startups:
    parameters:
      - name: menu_id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Listar empresas/oportunidades (startups) do funil
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
        - name: q
          in: query
          schema: { type: string }
      responses:
        "200": { description: OK }
    post:
      summary: Criar startup
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [title]
              properties:
                title: { type: string }
                descri: { type: string }
                logo: { type: string }
                approved: { type: integer }
                members: { type: string }
                user_id: { type: integer, description: "Dono; default primeiro admin" }
      responses:
        "201": { description: Criado }
  /crm/startups/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Obter startup
      responses:
        "200": { description: OK }
    patch:
      summary: Atualizar startup
      responses:
        "200": { description: OK }
    delete:
      summary: Soft-delete (removed_at)
      responses:
        "204": { description: Sem corpo }
  /crm/deals-contacts:
    get:
      summary: Listar contatos CRM de uma startup
      parameters:
        - name: company_relation_id
          in: query
          required: true
          schema: { type: integer }
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
      responses:
        "200": { description: OK }
    post:
      summary: Criar contato
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [company_relation_id, name]
              properties:
                company_relation_id: { type: integer }
                name: { type: string }
                email: { type: string }
                whatsapp: { type: string }
                cargo: { type: string }
                observacoes: { type: string }
      responses:
        "201": { description: Criado }
  /crm/deals-contacts/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Obter contato
      responses:
        "200": { description: OK }
    patch:
      summary: Atualizar contato
      responses:
        "200": { description: OK }
    delete:
      summary: Soft-delete
      responses:
        "204": { description: Sem corpo }
  /atrair/blog-articles:
    get:
      summary: Listar artigos do blog (Atrair)
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/perPage"
        - name: status
          in: query
          schema: { type: string, enum: [draft, published, removed] }
      responses:
        "200": { description: OK }
    post:
      summary: Criar artigo
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [title]
              properties:
                title: { type: string }
                body: { type: string }
                cover: { type: string }
                hashtags: { type: string }
                status: { type: string }
                author_id: { type: integer }
                ai_generated: { type: integer }
      responses:
        "201": { description: Criado }
  /atrair/blog-articles/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: integer }
    get:
      summary: Obter artigo
      responses:
        "200": { description: OK }
    patch:
      summary: Atualizar artigo
      responses:
        "200": { description: OK }
    delete:
      summary: Marcar como removed (soft)
      responses:
        "204": { description: Sem corpo }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: companies.api_key | companies.api_token
  parameters:
    page:
      name: page
      in: query
      schema: { type: integer, minimum: 1, default: 1 }
    perPage:
      name: per_page
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 30 }
