> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/construa/como-fazer/disparar-uma-funcao-pela-api.md).

# Disparar uma função pela API

O padrão completo para iniciar uma function a partir do seu sistema:

* um cliente HTTP configurado uma vez;
* um serviço que dispara com um GUID guardado junto do id de negócio;
* retry no chamador.

## Pré-requisitos

A URL base da API, a chave e o namespace do environment (tela do environment no portal). O workload publicado com `SKAIL_WORKLOAD=nome:versao` e a function que você quer iniciar.

## 1. Configure o cliente uma vez

Na inicialização do sistema que dispara (uma API, um worker, qualquer processo):

{% tabs %}
{% tab title="C#" %}

```csharp
builder.Services.AddHttpClient("skail", (sp, http) =>
{
    var cfg = sp.GetRequiredService<IConfiguration>().GetSection("skailApi");
    http.BaseAddress = new Uri(cfg["baseUrl"]!);
    http.DefaultRequestHeaders.Add("Authorization", $"Bearer {cfg["key"]!}");
    http.DefaultRequestHeaders.Add("skail-key", cfg["key"]!);
    http.DefaultRequestHeaders.Add("Skail-namespace", cfg["namespace"]!);
});
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
export interface SkailApiConfig {
  baseUrl: string;
  key: string;
  namespace: string;
}

export interface SkailHttp {
  post(path: string, body: unknown, signal?: AbortSignal): Promise<Response>;
}

function obrigatoria(nome: string): string {
  const valor = process.env[nome];
  if (!valor) throw new Error(`variável ${nome} não definida`);
  return valor;
}

export const skailApi: SkailApiConfig = {
  baseUrl: obrigatoria("SKAIL_BASE_URL"),
  key: obrigatoria("SKAIL_KEY"),
  namespace: obrigatoria("SKAIL_NAMESPACE"),
};

export function criarClienteSkail(cfg: SkailApiConfig): SkailHttp {
  return {
    post: (path, body, signal) =>
      fetch(new URL(path, cfg.baseUrl), {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${cfg.key}`,
          "skail-key": cfg.key,
          "Skail-namespace": cfg.namespace,
        },
        body: JSON.stringify(body),
        signal,
      }),
  };
}
```

{% endtab %}
{% endtabs %}

```json
{
  "skailApi": {
    "baseUrl": "https://SEU_SKAIL_BASE_URL",
    "key": "SUA_SKAIL_KEY",
    "namespace": "SEU_NAMESPACE"
  }
}
```

No exemplo em C#, os três valores vêm da seção `skailApi` da configuração, no formato do JSON acima; no exemplo em TypeScript, das variáveis de ambiente `SKAIL_BASE_URL`, `SKAIL_KEY` e `SKAIL_NAMESPACE`. A chave vem do cofre de segredos, não do arquivo versionado. Crie uma configuração dessas para cada environment.

## 2. Escreva o serviço que dispara

{% tabs %}
{% tab title="C#" %}

```csharp
using System.Net.Http.Json;
using System.Text.Json;

public class DisparadorSkail
{
    private readonly HttpClient _http;
    private readonly ILogger<DisparadorSkail> _log;

    public DisparadorSkail(IHttpClientFactory factory, ILogger<DisparadorSkail> log)
    {
        _http = factory.CreateClient("skail");
        _log = log;
    }

    // execucaoId: GUID gerado uma vez e guardado com a fatura
    public async Task IniciarEmissaoDeFatura(Guid faturaId, Guid execucaoId, CancellationToken ct)
    {
        var caminho = $"/api/v1/trigger/faturamento/v1.0.0/EmitirFatura/{execucaoId}";
        var argumentos = new object[] { faturaId };            // array, na ordem da assinatura

        for (var tentativa = 1; ; tentativa++)
        {
            HttpResponseMessage resposta;
            try
            {
                resposta = await _http.PostAsJsonAsync(caminho, argumentos, JsonSerializerOptions.Default, ct);
            }
            catch (Exception ex) when ((ex is HttpRequestException or TaskCanceledException)
                                       && !ct.IsCancellationRequested && tentativa < 5)
            {
                // falha de rede ou timeout: a chamada ficou sem resposta, retenta com o mesmo id
                await Task.Delay(TimeSpan.FromMilliseconds(200 * Math.Pow(2, tentativa)), ct);
                continue;
            }

            if (resposta.IsSuccessStatusCode) return;

            var deveRetentar = (int)resposta.StatusCode == 429 || (int)resposta.StatusCode >= 500;
            if (!deveRetentar || tentativa == 5)
            {
                _log.LogError("Trigger de {Fatura} falhou com {Status}", faturaId, resposta.StatusCode);
                resposta.EnsureSuccessStatusCode();
            }

            await Task.Delay(TimeSpan.FromMilliseconds(200 * Math.Pow(2, tentativa)), ct);
        }
    }
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { setTimeout as sleep } from "node:timers/promises";

export interface SkailHttp {
  post(path: string, body: unknown, signal?: AbortSignal): Promise<Response>;
}

export class DisparadorSkail {
  constructor(
    private readonly http: SkailHttp,
    private readonly log: Pick<Console, "error"> = console,
  ) {}

  // execucaoId: GUID gerado uma vez e guardado com a fatura
  async IniciarEmissaoDeFatura(faturaId: string, execucaoId: string, signal?: AbortSignal): Promise<void> {
    const caminho = `/api/v1/trigger/faturamento/v1.0.0/EmitirFatura/${execucaoId}`;
    const argumentos: unknown[] = [faturaId];                 // array, na ordem da assinatura

    for (let tentativa = 1; ; tentativa++) {
      let resposta: Response;
      try {
        resposta = await this.http.post(caminho, argumentos, signal);
      } catch (erro) {
        // falha de rede ou timeout: a chamada ficou sem resposta, retenta com o mesmo id
        if (signal?.aborted || tentativa === 5) throw erro;
        await sleep(200 * Math.pow(2, tentativa), undefined, { signal });
        continue;
      }

      if (resposta.ok) return;

      const deveRetentar = resposta.status === 429 || resposta.status >= 500;
      if (!deveRetentar || tentativa === 5) {
        this.log.error(`Trigger de ${faturaId} falhou com ${resposta.status}`);
        throw new Error(`trigger falhou: ${resposta.status}`);
      }

      await sleep(200 * Math.pow(2, tentativa), undefined, { signal });
    }
  }
}
```

{% endtab %}
{% endtabs %}

Em C#, registre com `builder.Services.AddScoped<DisparadorSkail>()` e injete no controller que recebe o pedido; em TypeScript, crie o disparador com `new DisparadorSkail(criarClienteSkail(skailApi))` e passe a instância ao método do controller. Se o seu projeto já usa uma biblioteca de retry (em C#, o Polly), a política dela substitui o laço (loop).

Três decisões estão nesse código:

* O `id` da execução é o `execucaoId`, um GUID (id que não é GUID é recusado com `400 invalid_body`). Quem dispara gera o GUID uma vez, reutiliza o mesmo valor nas novas tentativas desse disparo e guarda a relação entre ele e o id da fatura (por exemplo, numa coluna da fatura), para achar a execução no Monitor depois. Se preferir não guardar essa relação, derive o GUID com UUID v5 a partir do namespace do skail e do id de negócio (por exemplo, `SEU_NAMESPACE:4107`), com um GUID fixo da sua aplicação como base. A mesma fatura gera sempre o mesmo GUID no mesmo environment, e um GUID diferente em cada environment.
* O corpo é um array em JSON com os argumentos na ordem da assinatura (o `faturaId` vai como string).
* O retry só acontece para 429, 5xx e falha de rede ou timeout (chamada sem resposta); 400, 401 e 403 são erros de configuração ou de chamada, e retentar não resolve. Workload, versão ou função errados no caminho não dão erro: o trigger é aceito e a execução fica em Na fila (ver a linha "2xx, no Monitor mas não executa" em Erros comuns).

## 3. Chame a partir do controller

{% tabs %}
{% tab title="C#" %}

```csharp
[HttpPost("faturas/{faturaId:guid}/emitir")]
public async Task<IActionResult> Emitir(Guid faturaId, [FromServices] RepositorioFaturas faturas, [FromServices] DisparadorSkail skail, CancellationToken ct)
{
    var execucaoId = await faturas.ObterExecucaoId(faturaId, ct);
    await skail.IniciarEmissaoDeFatura(faturaId, execucaoId, ct);
    return Accepted(new { faturaId });
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
export interface DisparadorSkail {
  IniciarEmissaoDeFatura(faturaId: string, execucaoId: string, signal?: AbortSignal): Promise<void>;
}

export interface RepositorioFaturas {
  ObterExecucaoId(faturaId: string): Promise<string>;
}

export class FaturasController {
  async Emitir(
    faturaId: string,
    faturas: RepositorioFaturas,
    skail: DisparadorSkail,
    signal?: AbortSignal,
  ): Promise<{ status: 202; body: { faturaId: string } }> {
    const execucaoId = await faturas.ObterExecucaoId(faturaId);
    await skail.IniciarEmissaoDeFatura(faturaId, execucaoId, signal);
    return { status: 202, body: { faturaId } };
  }
}
```

{% endtab %}
{% endtabs %}

`ObterExecucaoId` é código do seu sistema: devolve o GUID guardado com a fatura e, na primeira vez, gera e grava esse GUID. Em C#, registre o `RepositorioFaturas` em `builder.Services`, como o `DisparadorSkail`; em TypeScript, passe uma implementação de `RepositorioFaturas` ao método `Emitir`, junto com o disparador.

{% hint style="success" %}
O que você deve ver: a execução aparece no Monitor com o TaskId igual ao `execucaoId`, e a sua aplicação com o runtime começa a processá-la.
{% endhint %}

## Por que retry aqui

O trigger e o fire ficam fora da durabilidade do skail; quem chama retenta os dois. Se a chamada de trigger falhar, a execução não começou e ninguém vai retentar por você. Depois que o trigger é aceito, tudo o que acontece dentro da function é retentado e retomado pelo skail. Por isso o chamador retenta com o mesmo `id`.

## Variações

* Function com vários argumentos: o array leva todos, na ordem da assinatura (`[pedidoId, valor, endereco]`).
* Function sem argumentos: um array vazio, `[]`.
* De outra linguagem ou de um shell: o mesmo `POST`, ver [Chamar o skail de qualquer linguagem por REST](/construa/como-fazer/chamar-o-skail-de-qualquer-linguagem-por-rest.md).

## Erros comuns

| Sintoma                                                                         | Causa                                                                                                                                                    | Correção                                                                                                                                                      |
| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401 ou 403                                                                      | Chave ou namespace errados, ou de outro environment                                                                                                      | Confira os dois na tela do environment                                                                                                                        |
| 2xx, mas nada no Monitor                                                        | URL base, chave e namespace de outro environment (a chamada foi aceita lá)                                                                               | Confira se URL base, chave e namespace são do mesmo environment do Monitor                                                                                    |
| 2xx, no Monitor mas não executa                                                 | A execução fica em Na fila: a aplicação não está rodando com esse `SKAIL_WORKLOAD`, ou `nome`, `versao` ou `funcao` do caminho não batem com o publicado | Suba a aplicação, e a execução espera por ela; se ela já está no ar, compare o caminho com `SKAIL_WORKLOAD` e o nome do método                                |
| 2xx, execução reentregue sem parar, sem entrar na function nem virar falha (C#) | Enum mandado como texto num argumento (o nome do membro em vez do número)                                                                                | Enum como número no JSON, ou campo `string` no tipo. Ver [Passar dados entre função e commands](/construa/como-fazer/passar-dados-entre-funcao-e-commands.md) |
| 400 com `missing_headers`                                                       | Falta um dos três headers: `Authorization`, `skail-key` ou `Skail-namespace`                                                                             | Confira o cliente configurado no passo 1                                                                                                                      |
| 400 com `invalid_body`                                                          | O `id` do caminho não é um GUID, o corpo não é um array, ou um argumento não desserializa no tipo do parâmetro                                           | Use um GUID no `id`; confira a ordem e os tipos                                                                                                               |

## Próximos passos

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><i class="fa-play">:play:</i></td><td><strong>POST /api/v1/trigger</strong></td><td>É a referência da rota.</td><td><a href="/referencia/api-http/post-trigger.md">POST trigger</a></td></tr><tr><td><i class="fa-paper-plane">:paper-plane:</i></td><td><strong>Como disparar um evento pela API HTTP</strong></td><td>Usa o mesmo cliente para o fire.</td><td><a href="/construa/como-fazer/disparar-um-evento-pela-api.md">Disparar um evento pela API</a></td></tr><tr><td><i class="fa-key">:key:</i></td><td><strong>Autenticação e headers</strong></td><td>Para os códigos de erro.</td><td><a href="/referencia/api-http/autenticacao.md">Autenticação</a></td></tr></tbody></table>
