快速开始
快速上手
注册主体、关联证书、注册 Webhook、开具 NF-e 并查询,TF Fiscal 开放 API 的五步接入路径。
本文按顺序演示每个开票集成都会做的五个调用:
- 注册开票主体,取得它的
empresaId。 - 关联主体的 A1 数字证书。
- 注册 Webhook,接收开票结果。
- 开具一张 NF-e。
- 查询这张 NF-e,取得状态与下载链接。
全部请求都发往生产网关基址 https://api.v2.tffiscal.com;完整 URL 为基址加各调用给出的路径。同样的五步也适用于 CT-e 与 DC-e:第 1 至 3 步共用,只有开票与查询端点不同。
前置条件
- 应用凭证:为你的应用签发的
app_secret。它仅回显一次,请妥善保存;若泄露请申请轮换。 - 接口订阅:平台为你的应用开通下文用到的全部端点。调用未订阅的端点返回 HTTP 403 与错误码
10009005。 - 签名:每个请求都携带
token、timestamp、sign三个请求头。下面的辅助函数被本页全部示例复用;完整规范见认证与签名。第一次真实调用之前,先用联调回显验证你的实现。
HOST="https://api.v2.tffiscal.com"APP_SECRET="<APP_SECRET>"# sign = lowercase hex MD5( token + path + body-without-CR-LF + timestamp )# usage: sign "<path>" "<body>" "<timestamp>" (pass "" as body for GET / DELETE / multipart)sign() {printf '%s%s%s%s' "$APP_SECRET" "$1" "$(printf '%s' "$2" | tr -d '\r\n')" "$3" \| md5sum | awk '{print $1}'}
第 1 步:注册主体
POST /openapi/v2/empresas 提交卖家企业资料。响应返回 empresaId,后续每个调用都以它作路径变量,请持久保存。
API_PATH="/openapi/v2/empresas"BODY='{"cnpj":"14422279000106","inscricaoEstadual":"999999","razaoSocial":"Empresa teste LTDA","nomeFantasia":"Empresa teste","optanteSimplesNacional":true,"mei":false,"email":"empresa-teste@example.com","telefoneComercial":"6122222222","endereco":{"pais":"Brasil","uf":"MG","cidade":"Belo Horizonte","logradouro":"Rua Teste","numero":"999","bairro":"Bairro Teste","cep":"85100000"},"emissaoNFeProduto":{"ambienteProducao":{"sequencialNFe":1,"serieNFe":"10"}}}'TS=$(date +%s)curl -sS -X POST "$HOST$API_PATH" \-H "Content-Type: application/json" \-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "$BODY" "$TS")" \-d "$BODY"
{ "empresaId": "1934811222334455" }
注册会把主体放入平台的审核队列。运营审批通过且证书关联成功后,主体才能开票;在此之前开票返回 codigo 10004004。同一 CNPJ 重复注册返回 HTTP 400 与 codigo 10003002。逐字段参考:注册主体。
第 2 步:关联数字证书
POST /openapi/v1/empresas/{empresaId}/certificadoDigital 接受 A1 证书(.pfx / .p12),既可用 multipart/form-data 上传,也可用 JSON 携带文件内容的 Base64。这里使用 JSON 形态,因为它的请求体与其他 JSON 请求一样参与签名。请生成不带换行的标准 Base64,避免参与签名的请求体与实际发送的请求体不一致。
EMPRESA_ID="1934811222334455"API_PATH="/openapi/v1/empresas/$EMPRESA_ID/certificadoDigital"BODY=$(printf '{"senha":"certpass123","arquivoBase64":"%s"}' "$(base64 -w0 certificate.pfx)")TS=$(date +%s)curl -sS -X POST "$HOST$API_PATH" \-H "Content-Type: application/json" \-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "$BODY" "$TS")" \--data-binary "$BODY"
成功为 HTTP 200 无响应体,上传成功即替换该主体的旧证书。密码错误返回 HTTP 400 与 codigo CER0005;非法 Base64 或解码后超过 1 MB 返回 10003035;证书 CNPJ 与主体不一致返回 10003010,证书已过期返回 10003011。multipart 形态(以空字符串作为 body 签名)见关联证书。
第 3 步:注册 Webhook
POST /openapi/v1/webhooks 登记接收开票结果的 URL。你选定的 token 会在每次回调的 token 请求头中原样带回,供接收端核对来源;此外平台还会用 X-Tffiscal-Signature 请求头对每次投递签名。
API_PATH="/openapi/v1/webhooks"BODY='{"uri":"https://example.com/tffiscal/callback","contentType":"application/json","token":"dGt6eXp5ZGRra2tzc3Nra2hoaGFha2tha2FhamFoaGFoNzc3Nz"}'TS=$(date +%s)curl -sS -X POST "$HOST$API_PATH" \-H "Content-Type: application/json" \-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "$BODY" "$TS")" \-d "$BODY"
{ "webHookId": "550001" }
每个应用只有一份回调配置,再次调用会覆盖。注册即订阅授权通过与被驳回两个结果事件。接收端必须返回 2xx,否则平台按退避重试。见注册 Webhook与 Webhooks。
第 4 步:开具 NF-e
主体审批通过后,POST /openapi/v2/empresas/{empresaId}/nf-e 即可受理开票请求。id 由你生成,是查询、作废与幂等的键。ambienteEmissao 必须与主体当前环境一致;新注册主体从 Homologacao 开始。
API_PATH="/openapi/v2/empresas/$EMPRESA_ID/nf-e"BODY='{"id":"NFe-000014553","ambienteEmissao":"Homologacao","pedido":{"presencaConsumidor":"OperacaoPelaInternet","pagamento":{"formas":[{"tipo":"CartaoDeCredito","valor":28.47}]}},"cliente":{"tipoPessoa":"F","nome":"Demo Client","email":"demo.client@mail.com","cpfCnpj":"88533234775","endereco":{"uf":"PR","cidade":"4106902","logradouro":"Rua Presidente Wilson","numero":"911","bairro":"Uberaba","cep":"81570440"}},"itens":[{"cfop":"6403","codigo":"000068","descricao":"Kingston DataTraveler SE9 DTSE9H 16GB USB Drive","ncm":"85235190","ean":"619659000424","quantidade":1,"unidadeMedida":"UN","valorUnitario":28.47,"impostos":{"icms":{"situacaoTributaria":"101"},"pis":{"situacaoTributaria":"49"},"cofins":{"situacaoTributaria":"49"}}}]}'TS=$(date +%s)curl -sS -X POST "$HOST$API_PATH" \-H "Content-Type: application/json" \-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "$BODY" "$TS")" \-d "$BODY"
受理即返回 HTTP 200 无响应体,进入异步开票流程。结果通过第 3 步注册的 Webhook 或第 5 步的查询获知。同一 id 重复提交复用原任务;若上一次开票被驳回(Negada),同一 id 修正字段后重提会按新报文重新开票。完整请求字典:开具 NF-e。
第 5 步:查询 NF-e
GET /openapi/v2/empresas/{empresaId}/nf-e/{nfeId} 返回当前状态、票面数据,授权后还返回 DANFE 与 XML 下载链接。nfeId 即第 4 步发送的 id。GET 请求以空字符串作为 body 签名;路径变量属于参与签名的路径。
NFE_ID="NFe-000014553"API_PATH="/openapi/v2/empresas/$EMPRESA_ID/nf-e/$NFE_ID"TS=$(date +%s)curl -sS -X GET "$HOST$API_PATH" \-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "" "$TS")"
status 字段会从 AguardandoAutorizacao 变为 Autorizada(或 Negada,并由 motivoStatus 说明原因)。授权后 linkDanfe 与 linkDownloadXml 直接 GET 即可下载,见文件下载。响应参考:查询 NF-e。
验证失败路径
下表每一行都是对上述流程的一处改动,用于确认你的客户端按预期失败:
| 改动 | 预期结果 |
|---|---|
token 取值错误 | HTTP 401,信封 code 10009002(token 无效) |
计算 sign 之后再改动 BODY | HTTP 401,信封 code 10009003(签名不匹配) |
复用超过 300 秒前的 timestamp | HTTP 401,信封 code 10009001(时间戳) |
| 调用应用未订阅的端点 | HTTP 403,信封 code 10009005(未订阅) |
| 同一 CNPJ 注册两次 | HTTP 400,[{"codigo":"10003002", ...}] |
主体仍在测试环境时用 ambienteEmissao 为 Producao 开票 | HTTP 400,[{"codigo":"10004030", ...}] |
查询不存在的 nfeId | HTTP 404,[{"codigo":"NFe0001", ...}] |
鉴权失败使用平台信封;开票类端点的业务失败使用错误数组。两种形状见通用约定。
