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.
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
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âmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
baseUrl | string | Sim | URL base da API (sem /v1, o cliente HTTP interno adiciona automaticamente) |
apiKey | string | Sim* | API Key para integração server-to-server (ek_live_/ek_test_) |
token | string | Sim* | JWT para sessões de dashboard |
timeout | number | Não | Timeout 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:
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.
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
const lista = await client.nfe.listar({
page: 1,
limit: 20,
issuerId: 'ISSUER_UUID',
status: 'AUTHORIZED',
});
Cancelar NFe
await client.nfe.cancelar('35260211222333000181550010000000011000000019', {
justificativa: 'Erro nos dados do destinatário informado na venda',
});
Carta de Correção
await client.nfe.cartaCorrecao('35260211222333000181550010000000011000000019', {
correcao: 'Corrijo o endereço do destinatário para Av Brasil, 500',
});
Download PDF e XML
// 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
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.
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
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):
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)
// 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
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' });
}
}
});