> ## Documentation Index
> Fetch the complete documentation index at: https://docs.engineapi.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK PHP

> Client PHP da engineAPI (EngineApiClient): NFe, empresas emissoras e autenticação. Uso direto via Guzzle hoje; pacote no Packagist a caminho.

<Info>
  O client PHP (`EngineApiClient`) cobre NFe, empresas emissoras e autenticação. O pacote
  `engineapi/sdk` está a caminho do Packagist. Hoje, use a integração direta com Guzzle
  abaixo: mesmo contrato da API, zero dependência de publicação externa.
</Info>

## Integração direta com Guzzle

```php theme={null}
<?php

use GuzzleHttp\Client;

class EngineApiClient
{
    public Client $http;

    public function __construct(string $baseUrl, ?string $apiKey = null, ?string $token = null, float $timeout = 30.0)
    {
        $headers = $apiKey
            ? ['x-api-key' => $apiKey]
            : ['Authorization' => "Bearer {$token}"];

        $this->http = new Client([
            'base_uri' => rtrim($baseUrl, '/') . '/v1/',
            'headers' => array_merge($headers, [
                'Content-Type' => 'application/json',
                'Accept' => 'application/json',
            ]),
            'timeout' => $timeout,
        ]);
    }

    public function login(string $email, string $senha): array
    {
        $response = $this->http->post('auth/login', ['json' => ['email' => $email, 'password' => $senha]]);
        return json_decode($response->getBody(), true);
    }
}
```

| Parâmetro | Tipo   | Obrigatório | Descrição                                        |
| --------- | ------ | ----------- | ------------------------------------------------ |
| `baseUrl` | string | Sim         | URL base da API (sem `/v1`, o client adiciona)   |
| `apiKey`  | string | Sim\*       | API Key server-to-server (`ek_live_`/`ek_test_`) |
| `token`   | string | Sim\*       | JWT de sessão de dashboard                       |
| `timeout` | float  | Não         | Timeout em segundos (padrão 30)                  |

\*Use `apiKey` OU `token`.

***

## NFe: Emitir

`POST nfe` recebe um array associativo repassado **verbatim** ao corpo da requisição,
use os nomes de campo reais do contrato: `destinatario`, `items` (não `itens`),
`pagamentos[]` (não `pagamento` singular), `cnpjCpf`/`nome`/`ie` no destinatário.

```php theme={null}
<?php

function emitirNfe(EngineApiClient $client, array $dados): array
{
    $response = $client->http->post('nfe', ['json' => $dados]);
    return json_decode($response->getBody(), true);
}

$nfe = emitirNfe($client, [
    'naturezaOperacao' => 'VENDA DE MERCADORIA',
    'destinatario' => [
        'cnpjCpf' => '99888777000100',
        'nome' => 'Cliente Exemplo SA',
        'indicadorIE' => 1,
        'endereco' => [
            'logradouro' => 'Av Brasil', 'numero' => '500',
            'bairro' => 'Centro', 'codigoMunicipio' => '3550308',
            'municipio' => 'São Paulo', 'uf' => 'SP', 'cep' => '01001000',
        ],
    ],
    'items' => [[
        'codigo' => 'PROD001',
        'descricao' => 'Camiseta Azul M', 'ncm' => '61091000',
        'cfop' => '5102', 'unidade' => 'UN', 'quantidade' => 1,
        'valorUnitario' => 89.90,
        'icms' => ['origem' => 0, 'csosn' => '400'],
    ]],
    'pagamentos' => [['forma' => '01', 'valor' => 89.90]],
]);

echo "NFe autorizada: " . $nfe['data']['accessKey'] . PHP_EOL;
echo "Status: " . $nfe['data']['status'] . PHP_EOL; // "AUTHORIZED"
```

<Info>
  `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`.
</Info>

### Cancelar, CC-e e downloads

```php theme={null}
<?php

$chave = '35260211222333000181550010000000011000000019';

// Cancelar: campo justificativa, mínimo 15 caracteres
$client->http->post("nfe/{$chave}/cancelar", [
    'json' => ['justificativa' => 'Erro nos dados do destinatário informado na venda'],
]);

// Carta de Correção: campo correcao, mínimo 15 caracteres
$client->http->post("nfe/{$chave}/carta-correcao", [
    'json' => ['correcao' => 'Corrijo o endereço do destinatário para Av Brasil, 500'],
]);

// Downloads
$pdf = $client->http->get("nfe/pdf/{$chave}")->getBody();
file_put_contents('danfe.pdf', $pdf);

$xml = $client->http->get("nfe/xml/{$chave}")->getBody();
file_put_contents('nfe.xml', $xml);
```

***

## Companies: Empresas Emissoras

```php theme={null}
<?php

use GuzzleHttp\Psr7\Utils;

$response = $client->http->post('companies', ['json' => [
    'cnpj' => '11222333000181',
    'name' => 'Empresa Exemplo Ltda',
    'crt' => 1,
]]);
$empresa = json_decode($response->getBody(), true);

$client->http->post("companies/{$empresa['data']['id']}/certificate", [
    'multipart' => [
        ['name' => 'file', 'contents' => Utils::tryFopen('certificado.pfx', 'r'), 'filename' => 'certificado.pfx'],
        ['name' => 'password', 'contents' => 'senhaDoCertificado'],
    ],
]);
```

***

## Login

```php theme={null}
<?php

$login = $client->login('usuario@partner.com', 'senha');
echo $login['access_token'];
```

***

## Tratamento de erros

```php theme={null}
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;

try {
    $nfe = emitirNfe($client, $dados);
} catch (ClientException $e) {
    $status = $e->getResponse()->getStatusCode();
    $body = json_decode($e->getResponse()->getBody(), true);

    match ($status) {
        400 => logger()->error('Validação ou rejeição SEFAZ', $body['error']['erros'] ?? $body),
        403 => logger()->error('Sem permissão / ambiente da key incompatível', $body),
        422 => logger()->error('Emissão assistida: campo não resolvido', $body),
        429 => logger()->warning('Rate limit. Retry após Retry-After'),
        default => logger()->error("Erro {$status}", $body),
    };
} catch (ServerException $e) {
    // 500+: retry com backoff
    logger()->critical('Erro no servidor engineAPI', [
        'status' => $e->getResponse()->getStatusCode(),
    ]);
}
```

<Warning>
  Não existem `sefazCode`/`sefazMessage` no corpo do erro: o formato real é RFC 7807:
  `error.erros[]` (`{codigo, descricao}`) para rejeição SEFAZ. Ver
  [Erros e Rejeições](/guides/errors).
</Warning>

***

## Próximos passos

<CardGroup cols={2}>
  <Card title="SDK TypeScript" icon="js" href="/sdks/typescript">
    SDK oficial com tipagem completa
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/overview">
    Documentação completa de todos os endpoints
  </Card>
</CardGroup>
