# Como enviar uma TED via API

> Para enviar uma TED a partir de uma conta da sua empresa, use o _endpoint_
> `POST /api/v1/ted`.

:::info
Sua aplicação precisa do scope **`TED_POST`** e a empresa precisa da
funcionalidade **`TED`**. Veja
[Primeiros passos com a API de TED](./ted-api-getting-started.md).
:::

:::tip Referência completa
Para o schema, parâmetros e exemplos interativos, veja a
[API Reference](/api#tag/ted/POST/api/v1/ted).
:::

## Corpo da requisição

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| `correlationID` | string | sim | Seu identificador da TED. No máximo **20 caracteres** e sem espaços. Veja [Reenvio seguro](#reenvio-seguro-com-o-mesmo-correlationid). |
| `value` | number | sim | Valor em centavos, inteiro e maior que zero |
| `receiver` | object | sim | Quem recebe a TED (veja abaixo) |
| `moveDate` | string | não | Data de liquidação, `YYYY-MM-DD`. Padrão: hoje |
| `clientFinality` | number | não | Finalidade da transferência. Padrão: `10` (crédito em conta). Veja [Finalidade](#finalidade-clientfinality) |
| `description` | string | não | Descrição livre |
| `schedule` | boolean | não | Reservado. Hoje, com o STR fechado, a TED é recusada com `422` (`OUTSIDE_STR_SESSION`) mesmo com `schedule: true` |

## Conta de origem

A conta que a TED debita vem do **AppID**, e não do corpo da requisição, na mesma
ordem do Pix out:

1. a conta vinculada à aplicação do AppID;
2. se a aplicação não tiver conta, a conta padrão da empresa.

Não há campo para escolher a conta: um `accountId` no corpo é ignorado. Para
enviar de outra conta, use um AppID de uma aplicação vinculada a ela.

A conta precisa ser da empresa do AppID. Se for de outra empresa, a TED é
recusada com `403` (`ACCOUNT_NOT_OWNED`), e sem nenhuma conta, nem na aplicação
nem na empresa, com `403` (`SOURCE_ACCOUNT_NOT_FOUND`). Nos dois casos nenhuma
TED é criada.

### `receiver`

| Campo | Tipo | Descrição |
| --- | --- | --- |
| `name` | string | Nome do titular |
| `document` | string | CPF ou CNPJ, só dígitos |
| `ispb` | string | ISPB da instituição, 8 dígitos |
| `agency` | number | Agência, sem o dígito |
| `account` | number | Número da conta |
| `accountType` | string | `CACC` conta corrente, `SVGS` poupança, `SLRY` conta salário |

:::caution O `correlationID` é curto
O `correlationID` vai para o STR como número de controle da operação, por isso
aceita no máximo 20 caracteres. Um UUID (36 caracteres) é recusado com `400`.
:::

## Finalidade (`clientFinality`)

A finalidade é o código `FinlddCli` do BACEN que a TED leva ao STR. É por ele
que o banco recebedor classifica o crédito (salário, fornecedor, imposto...).
Sem `clientFinality`, a TED vai como `10`.

| Código | Finalidade |
| --- | --- |
| `1` | Pagamento de impostos, tributos e taxas |
| `2` | Pagamento a concessionárias de serviço público |
| `3` | Pagamento de dividendos |
| `4` | Pagamento de salários |
| `5` | Pagamento de fornecedores |
| `6` | Pagamento de honorários |
| `7` | Pagamento de aluguéis e taxas de condomínio |
| `8` | Pagamento de duplicatas e títulos |
| `9` | Pagamento de mensalidade escolar |
| `10` | Crédito em conta |
| `100` | Depósito judicial |
| `101` | Pensão alimentícia |

Qualquer outro valor é recusado com `400` (`INVALID_REQUEST_BODY`).

## Exemplo de requisição

```json
{
  "correlationID": "payout-20260203-1",
  "value": 150050,
  "moveDate": "2026-02-03",
  "receiver": {
    "name": "Joao da Silva",
    "document": "12345678901",
    "ispb": "87654321",
    "agency": 4321,
    "account": 98765,
    "accountType": "CACC"
  },
  "clientFinality": 10,
  "description": "Pagamento de serviços"
}
```

## Exemplo de resposta

Se a TED foi aceita, o _status code_ é `200` e o corpo traz o objeto `ted`:

```json
{
  "ted": {
    "correlationID": "payout-20260203-1",
    "nuop": "1234567820260203000001",
    "status": "PROCESSING",
    "type": "TED_OUT",
    "value": 150050,
    "moveDate": "2026-02-03",
      "debitParty": {
      "account": { "branch": "1234", "account": "567890", "accountType": "CACC" },
      "psp": { "id": "12345678" },
      "holder": {
        "name": "Empresa LTDA",
        "taxID": { "taxID": "12345678000199", "type": "BR:CNPJ" }
      }
    },
    "creditParty": {
      "account": { "branch": "4321", "account": "98765", "accountType": "CACC" },
      "psp": { "id": "87654321" },
      "holder": {
        "name": "Joao da Silva",
        "taxID": { "taxID": "12345678901", "type": "BR:CPF" }
      }
    },
    "errorCode": null,
    "reason": null,
    "createdAt": "2026-02-03T14:30:00.000Z",
    "updatedAt": "2026-02-03T14:30:00.000Z"
  }
}
```

:::warning `200` não quer dizer que o dinheiro chegou
A resposta confirma que a TED foi aceita. A liquidação é confirmada pelo webhook
`TED_OUT_CONFIRMED`, e a falha pelo `TED_OUT_REJECTED`. Veja
[Webhooks de TED](./ted-webhooks.md).
:::

O `nuop` (Número Único de Operação) é o identificador da TED no SPB, gerado pela
Woovi. Guarde-o para conciliar com o extrato e para falar com o suporte.

## Reenvio seguro com o mesmo `correlationID`

Reenviar com um `correlationID` que já existe **não** cria outra TED: a resposta
é `200` com a TED que já existe, sem um segundo débito e sem uma segunda
mensagem ao STR. Assim, se a requisição der _timeout_ ou a conexão cair, reenvie
com o mesmo `correlationID` sem risco de pagar duas vezes.

:::caution Um `correlationID` por TED
A TED existente é devolvida **como está**, mesmo que o reenvio traga outro
valor ou outro recebedor, e o novo corpo é ignorado. Olhe o `ted.status` da
resposta: um reenvio também pode devolver uma TED `FAILED`. Para uma TED nova,
use um `correlationID` novo.
:::

## Códigos de resposta

Todo erro traz o `errorCode` e o `error` (veja
[Erros](./ted-api-getting-started.md#erros)).

| Status | `errorCode` | Quando |
| --- | --- | --- |
| `200` | — | TED aceita, ou reenvio de um `correlationID` existente |
| `400` | `INVALID_REQUEST_BODY` | Corpo inválido: campo faltando, tipo errado, `correlationID` com mais de 20 caracteres ou com espaços, `clientFinality` fora da tabela. O detalhe de cada campo vem em `errors` |
| `400` | `INVALID_CORRELATION_ID` | `correlationID` com mais de 20 caracteres |
| `401` | — | AppID ausente ou inválido |
| `403` | `TED_FEATURE_REQUIRED` | Empresa sem a funcionalidade `TED` |
| `403` | `ACCOUNT_NOT_OWNED` | A conta de origem (da aplicação ou a padrão da empresa) é de outra empresa |
| `403` | `SOURCE_ACCOUNT_NOT_FOUND` | Nem a aplicação nem a empresa têm uma conta de origem. Veja [Conta de origem](#conta-de-origem) |
| `403` | — | Aplicação sem o scope `TED_POST` |
| `404` | `SENDER_NOT_FOUND`, `ACCOUNT_NOT_FOUND` | A conta vinculada à aplicação, ou a padrão da empresa, não existe |
| `422` | `TED_PER_TRANSACTION_LIMIT_EXCEEDED` | Valor acima do limite de TED por transação |
| `422` | `TED_TOTAL_LIMIT_EXCEEDED` | Valor acima do limite de TED disponível no dia |
| `422` | `ACCOUNT_LIMIT_NOT_FOUND` | A conta não tem limite de TED configurado |
| `422` | `ACCOUNT_BLOCKED_TED_OUT` | Conta bloqueada para envio de TED |
| `422` | `OUTSIDE_STR_SESSION` | STR fechado. Envie de novo quando o STR abrir |
| `422` | `LEDGER_FAILED` | Não foi possível debitar o saldo, inclusive por saldo insuficiente. A TED fica `FAILED`, com o motivo em `errorCode` |
| `500` | `FEE_FETCH_FAILED` | Não foi possível calcular a tarifa. A TED fica `FAILED` |
| `503` | `TED_LIMIT_SERVICE_UNAVAILABLE` | Serviço de limites indisponível. Nenhuma TED foi criada: reenvie com o mesmo `correlationID` |
| `503` | `SPB_FAILED` | A TED não foi entregue ao BACEN. Ela fica `FAILED`: para tentar de novo, use um `correlationID` novo |

Alguns erros acontecem depois de a TED ser criada (`LEDGER_FAILED`,
`FEE_FETCH_FAILED`, `SPB_FAILED`). Nesses casos a TED fica `FAILED`, chega um
`TED_OUT_REJECTED`, e reenviar com o mesmo `correlationID` devolve essa TED
`FAILED` com `200`. Veja [Depois de um erro no `POST`](./ted-lifecycle.md#depois-de-um-erro-no-post).

### Exemplos de erro

Corpo inválido, com o detalhe de cada campo em `errors`:

```json
{
  "error": "Corpo da requisição inválido",
  "errorCode": "INVALID_REQUEST_BODY",
  "errors": [
    {
      "origin": "string",
      "code": "too_big",
      "maximum": 20,
      "inclusive": true,
      "path": ["correlationID"],
      "message": "Too big: expected string to have <=20 characters"
    }
  ]
}
```

```json
{
  "error": "A empresa não possui o recurso TED habilitado",
  "errorCode": "TED_FEATURE_REQUIRED"
}
```

```json
{
  "error": "Valor acima do limite de TED disponível para o período",
  "errorCode": "TED_TOTAL_LIMIT_EXCEEDED"
}
```

```json
{
  "error": "O STR está fora da sessão de hoje, o SPB recusaria a mensagem com EGEN0300",
  "errorCode": "OUTSIDE_STR_SESSION"
}
```

Os limites de TED da conta (por transação e por dia) podem ser consultados em
[`GET /api/v1/limits/{accountId}`](../account-limits/api-limits-get.mdx).

## Exemplos em código

**Shell + cURL**

```sh
curl --request POST \
  --url https://api.woovi.com/api/v1/ted \
  --header 'Authorization: {APP_ID}' \
  --header 'Content-Type: application/json' \
  --data '{
    "correlationID": "payout-20260203-1",
    "value": 150050,
      "receiver": {
      "name": "Joao da Silva",
      "document": "12345678901",
      "ispb": "87654321",
      "agency": 4321,
      "account": 98765,
      "accountType": "CACC"
    }
  }'
```

**JavaScript + Fetch**

```js
const response = await fetch('https://api.woovi.com/api/v1/ted', {
  method: 'POST',
  headers: {
    Authorization: process.env.WOOVI_APP_ID,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    correlationID: 'payout-20260203-1',
    value: 150050,
    receiver: {
      name: 'Joao da Silva',
      document: '12345678901',
      ispb: '87654321',
      agency: 4321,
      account: 98765,
      accountType: 'CACC',
    },
  }),
});

const data = await response.json();

if (!response.ok) {
  // `errorCode` para decidir, `error` para mostrar ao usuário
  throw new Error(`${data.errorCode}: ${data.error}`);
}

console.log(data.ted.status);
// PROCESSING — o resultado chega pelo webhook
```
