engineAPIengineAPI
// referência

SDK TypeScript

SDK oficial TypeScript para engineAPI. @engineapi/sdk no npm. Tipagem completa, todos os módulos fiscais.

SDK TypeScript

SDK oficial da engineAPI com tipagem completa para Node.js e TypeScript.

bash
npm install @engineapi/sdk

Publicado no npm como @engineapi/sdk@1.2.0 (2026-08-02). Compatível com Node.js 18+ e TypeScript 5+.


Configuração

typescript
import { EngineApiClient } from '@engineapi/sdk';

const client = new EngineApiClient({
  baseUrl: 'https://api.engineapi.com.br',
  apiKey: 'ek_live_SEU_API_KEY',
  timeout: 30000, // opcional, padrão 30s
});

Parâmetros de configuração

ParâmetroTipoObrigatórioDescrição
baseUrlstringSimURL base da API (sem /v1, o cliente HTTP interno adiciona automaticamente)
apiKeystringSim*API Key para integração server-to-server (ek_live_/ek_test_)
tokenstringSim*JWT para sessões de dashboard
timeoutnumberNãoTimeout em ms (padrão: 30000)

*Use apiKey para integrações ou token para sessões autenticadas via login.

O client anexa todas as credenciais configuradas em cada chamada: apiKey vira o header X-API-Key, token vira Authorization: Bearer, sem distinção por módulo do lado do client. O que decide qual funciona é o guard de cada endpoint no backend (ver Companies abaixo).


Módulos Disponíveis

O contrato público hoje cobre 2 módulos tipados:

typescript
client.nfe        // NFe: emissão, cancelamento, CC-e, download, status SEFAZ
client.companies  // Empresas: cadastro e certificados

O pacote publicado (@engineapi/sdk@1.2.0) também expõe client.dfe; esse módulo está fora do contrato público hoje. Não use em produção; será reativado quando a funcionalidade entrar na superfície pública.


NFe: Nota Fiscal Eletrônica

Emitir NFe

O tipo CreateNfeParams publicado nesta versão do pacote já bate com o contrato real da API: issuerId na raiz, items (não itens), pagamentos[] (não pagamento singular), destinatario.nome/destinatario.ie (não razaoSocial/inscricaoEstadual). Não precisa de as any para emitir.

typescript
import { EngineApiClient } from '@engineapi/sdk';

const client = new EngineApiClient({
  baseUrl: 'https://api.engineapi.com.br',
  apiKey: 'ek_live_SEU_API_KEY',
});

const nfe = await client.nfe.emitir({
  destinatario: {
    cnpjCpf: '99888777000100',
    nome: 'Cliente Exemplo SA',
    endereco: {
      logradouro: 'Av Brasil', numero: '500',
      bairro: 'Centro', municipio: 'São Paulo',
      uf: 'SP', cep: '01001000',
      codigoMunicipio: '3550308',
    },
  },
  items: [{
    codigo: 'PROD001',
    descricao: 'Produto Teste',
    ncm: '61091000',
    cfop: '5102',
    unidade: 'UN',
    quantidade: 1,
    valorUnitario: 100.00,
    icms: { origem: 0, csosn: '400' },
  }],
  pagamentos: [{ forma: '01', valor: 100.00 }],
});

console.log('Chave:', nfe.data.accessKey);
console.log('Status:', nfe.data.status);  // "AUTHORIZED"

Defeito conhecido no pacote publicado: o tipo NfeResponse declara protocolo/numero/serie/xml: a resposta real usa protocol/number/series, e o campo xml não existe no shape real. Faltam no tipo os campos reais model, amount, destCNPJ, destName, updatedAt, downloads.{xml,pdf} e, na NFCe, qrCode. accessKey/status (usados no exemplo acima) batem com o tipo publicado; para os demais campos, acesse via (nfe.data as any).protocol ou cast local. Confira o changelog do pacote publicado antes de assumir que uma correção já chegou. Shape real completo em Primeira Emissão.

issuerId é opcional no payload de NFe/NFCe: com um único emissor pode omitir; com dois ou mais, informe o issuerId (UUID) do CNPJ que deve emitir; sem ele a API responde 400.

Listar NFes

typescript
const lista = await client.nfe.listar({
  page: 1,
  limit: 20,
  issuerId: 'ISSUER_UUID',
  status: 'AUTHORIZED',
});

Cancelar NFe

typescript
await client.nfe.cancelar('35260211222333000181550010000000011000000019', {
  justificativa: 'Erro nos dados do destinatário informado na venda',
});

Carta de Correção

typescript
await client.nfe.cartaCorrecao('35260211222333000181550010000000011000000019', {
  correcao: 'Corrijo o endereço do destinatário para Av Brasil, 500',
});

Download PDF e XML

typescript
// PDF (DANFE)
const pdf = await client.nfe.downloadPdf('35260211...');
fs.writeFileSync('danfe.pdf', pdf);

// XML
const xml = await client.nfe.downloadXml('35260211...');
fs.writeFileSync('nfe.xml', xml);

// XML/PDF da CC-e
const cceXml = await client.nfe.downloadCceXml('35260211...');
const ccePdf = await client.nfe.downloadCcePdf('35260211...');

Status do serviço SEFAZ

typescript
const status = await client.nfe.status();

Companies: Empresas Emissoras

client.companies.* chama /companies, o path bate com a API real. O backend aceita apiKey ou token neste módulo, o mesmo aceito em NFe: configurar só apiKey no client já é suficiente para cadastrar empresa e enviar certificado. O upload de certificado (uploadCertificado()) já envia multipart/form-data com o campo file, batendo com o contrato real do endpoint de upload.

typescript
const company = await client.companies.criar({
  cnpj: '11222333000181',
  name: 'Empresa Exemplo Ltda',
  crt: 1,
});

await client.companies.uploadCertificado(
  company.data.id,
  fs.readFileSync('certificado.pfx'),
  'senhaDoCertificado',
);

Tratamento de Erros

typescript
import { EngineApiClient, EngineApiError } from '@engineapi/sdk';

try {
  await client.nfe.emitir(params);
} catch (error) {
  if (error instanceof EngineApiError) {
    console.error('Status:', error.statusCode);
    console.error('Mensagem:', error.message);
    console.error('Response:', error.response); // envelope RFC 7807 completo

    if (error.statusCode === 400) {
      // Validação OU rejeição SEFAZ: ver error.response?.error?.erros
    }
    if (error.statusCode === 401) {
      // API key/token inválido ou expirado
    }
    if (error.statusCode === 429) {
      // Rate limit: retry após Retry-After
    }
    if (error.statusCode === 404) {
      // Recurso não encontrado
    }
    if (error.statusCode >= 500) {
      // Retry com backoff
    }
  }
}

Defeito conhecido em versões antigas do pacote publicado: error.message pode chegar como a string literal "[object Object]" em vez do texto do erro. O envelope real de erro da API é RFC 7807 ({ error: { detail, title, ... } }, ver Erros e Rejeições). Contorno: leia a mensagem de error.response.error.detail (ou .title), não de error.message. Confira o changelog do pacote publicado antes de assumir que a correção já chegou na sua versão.


Login via JWT (Dashboard)

Para sessões de dashboard (não recomendado para server-to-server):

typescript
const client = new EngineApiClient({
  baseUrl: 'https://api.engineapi.com.br',
});

const login = await client.login({
  email: 'usuario@partner.com',
  password: 'senha',
});

console.log('Token:', login.access_token);
console.log('Usuário:', login.user.email, login.user.role);

// Agora o client usa o JWT automaticamente (setToken() já foi chamado
// internamente por login())
const empresas = await client.companies.listar();

Defeito conhecido em versões antigas do pacote publicado: client.login() lia response.access_token direto no envelope de resposta da API ({data: {...}, meta: {...}}, todo endpoint de sucesso é envelopado) em vez de response.data.access_token. O campo era sempre undefined, setToken() nunca era chamado, e sessões de dashboard via client.login() ficavam inutilizáveis. O tipo LoginResponse também descrevia um campo partner que não existe na resposta real (é user, com partnerId dentro). Confira o changelog do pacote publicado antes de assumir que a correção já chegou na sua versão.


Integração com Frameworks

Next.js (App Router)

typescript
// app/api/emitir-nfe/route.ts
import { EngineApiClient } from '@engineapi/sdk';
import { NextResponse } from 'next/server';

const engine = new EngineApiClient({
  baseUrl: process.env.ENGINE_API_URL!,
  apiKey: process.env.ENGINE_API_KEY!,
});

export async function POST(request: Request) {
  const body = await request.json(); // já no formato real: destinatario/items/pagamentos
  const nfe = await engine.nfe.emitir(body);
  return NextResponse.json(nfe);
}

Express

typescript
import express from 'express';
import { EngineApiClient, EngineApiError } from '@engineapi/sdk';

const app = express();
const engine = new EngineApiClient({
  baseUrl: process.env.ENGINE_API_URL!,
  apiKey: process.env.ENGINE_API_KEY!,
});

app.post('/nfe', async (req, res) => {
  try {
    const nfe = await engine.nfe.emitir(req.body);
    res.json(nfe);
  } catch (error) {
    if (error instanceof EngineApiError) {
      res.status(error.statusCode).json({ error: error.message });
    } else {
      res.status(500).json({ error: 'Internal server error' });
    }
  }
});

Próximos passos