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

# Generate or import Flow endpoint key

> Puts the `data_exchange` keypair on ONE number, by one of TWO verbs told apart by a single field: send `privateKey` and it IMPORTS the key you already own, leave it out and it GENERATES a fresh pair. Answers 200 with the same shape the `GET` returns, plus `replaced`.

- **GENERATE — no `privateKey`.** Mints the pair, stores the private half encrypted, and REGISTERS the public half with Meta.
- **IMPORT — `privateKey`, optionally with `passphrase`.** Stores a key you ALREADY own, and ⛔ REGISTERS NOTHING WITH META.

⛔ **GENERATING IS A DESTRUCTIVE OPERATION WEARING THE FACE OF A CREATION.** Meta holds exactly ONE public key per phone id: registering a second one does not add it, it REPLACES the first, and every `data_exchange` Flow on that number stops decrypting at once. From the customer's side the symptom is a form that no longer opens, with no error anywhere on our side — the endpoint simply answers 421 to a body encrypted for a key we no longer hold. The private half stored here is overwritten at the same moment and cannot be restored.

⛔ **IMPORTING REGISTERS NOTHING WITH META, and that is the entire point of it.** Your public key is already registered — often by a system that predates us — and Meta holds exactly one key per phone id, so registering here would replace the very setup the import exists to preserve. What makes it safe is a READ: the public half is derived from the private one you sent and compared with the key Meta reports for the number.

- they agree → stored, `uploadedAt` stamped, `metaStatus: "VALID"`.
- they disagree → 400 `FLOW_ENDPOINT_KEY_IMPORT_MISMATCH`, and NOTHING is written. Storing it would leave a number reporting `configured: true` that decrypts nothing.
- Meta could not be asked, or holds no key → **stored anyway**, with `uploadedAt: null` and `metaStatus` `UNKNOWN`/`NOT_SET`. That null IS the "unverified" signal and you have to read it: a 200 alone does not tell the two apart.

A passphrase-protected PEM is accepted; the key is opened once, here, and stored normalised. The PEM must be RSA of 2048 bits or more — Meta encrypts `data_exchange` with RSA/OAEP.

⛔ **`confirm` IS NOT ALWAYS REQUIRED. It is required exactly when the call REPLACES A LIVE KEY — and that rule is the same for BOTH verbs.** One state check decides it: a key of ours is already live at Meta (`uploadedAt` is not `null`). The FIRST setup never asks, whichever verb you use, and neither does retrying a pair Meta never accepted (`uploadedAt: null`) — that call re-uploads the SAME stored public half and converges instead of replacing anything, so there is nothing to destroy. When it IS asked for, the check is `=== true`, so `"true"`, `1` and `{}` are all refused.

⚠️ **Importing is gated too, and what it puts at risk is OUR side, not Meta's.** An import registers nothing at Meta, but it overwrites the private half stored here — the key WE decrypt with. If the number already held a working key and Meta cannot confirm the imported one (it being unreachable, say), the imported key is stored anyway and every `data_exchange` request on that number stops decrypting, with nothing changed at Meta to point at. So the two verbs share the flag and answer with DIFFERENT codes: `FLOW_ENDPOINT_KEY_REQUIRES_CONFIRMATION` when generating (what is at stake is the registration at Meta) and `FLOW_ENDPOINT_KEY_IMPORT_REQUIRES_CONFIRMATION` when importing (what is at stake is the private half kept here).

⚠️ `replaced` is in the payload because a 200 alone does not say which of the two things happened. `replaced: false` is "a key now exists where none was live"; `replaced: true` is "the key that was working is gone, and every Flow that was answering with the old pair is now answering with the new one".

**The body accepts three fields — `confirm`, `privateKey` and `passphrase` — and anything else is 400 `FLOW_UNKNOWN_FIELDS`**. `passphrase` on its own is REFUSED, 400 `FLOW_ENDPOINT_KEY_PASSPHRASE_ORPHAN`, and never ignored: a mistyped `privateKey` would otherwise fall through into GENERATE, which registers a fresh key at Meta and replaces whatever was there — the most destructive outcome this route has, reached by a typo and reported as success. A `privateKey` that is not a non-empty string, or a `passphrase` that is not a string, is 400 `FLOW_ENDPOINT_KEY_IMPORT_INVALID`, answered before any crypto runs. A PEM that cannot be used is a 400 whose code names the repair: `FLOW_ENDPOINT_KEY_PASSPHRASE_REQUIRED`, `FLOW_ENDPOINT_KEY_PASSPHRASE_WRONG`, or `FLOW_ENDPOINT_KEY_PEM_INVALID` (unreadable, not RSA, or under 2048 bits).

A truncated body is not read as an empty one — the payload is parsed from raw text, so `{"confirm": true` (one missing brace) is 400 `FLOW_BODY_INVALID` rather than a confusing "confirmation required". No body at all stays legitimate, and means GENERATE. Naming the number in the body — `whatsappNumberId`, `numberId` or `wabaId` — has a refusal of its own, 400 `FLOW_NUMBER_FROM_KEY`, because the number comes from the credential. Permission `flows:manage`.

**Requires a number-scoped key.** A tenant-scoped key must name the number with the `x-whatsapp-number-id` header, or it gets 403 `TENANT_SCOPE_NOT_ALLOWED`.



## OpenAPI

````yaml openapi.json POST /v1/numbers/{id}/flow-endpoint-key
openapi: 3.1.0
info:
  title: Pilot Status API
  version: 1.0.0
  license:
    name: Pilot Status Terms of Service
    url: https://pilotstatus.com.br/terms
  description: >-
    Public REST API for Pilot Status. Authenticate with the `x-api-key: ps_...`
    header (or `x-api-key-id`). Base URL: https://pilotstatus.com.br
servers:
  - url: https://pilotstatus.com.br
security:
  - apiKey: []
  - apiKeyId: []
paths:
  /v1/numbers/{id}/flow-endpoint-key:
    post:
      tags:
        - Numbers
      summary: Generate or import Flow endpoint key
      description: >-
        Puts the `data_exchange` keypair on ONE number, by one of TWO verbs told
        apart by a single field: send `privateKey` and it IMPORTS the key you
        already own, leave it out and it GENERATES a fresh pair. Answers 200
        with the same shape the `GET` returns, plus `replaced`.


        - **GENERATE — no `privateKey`.** Mints the pair, stores the private
        half encrypted, and REGISTERS the public half with Meta.

        - **IMPORT — `privateKey`, optionally with `passphrase`.** Stores a key
        you ALREADY own, and ⛔ REGISTERS NOTHING WITH META.


        ⛔ **GENERATING IS A DESTRUCTIVE OPERATION WEARING THE FACE OF A
        CREATION.** Meta holds exactly ONE public key per phone id: registering
        a second one does not add it, it REPLACES the first, and every
        `data_exchange` Flow on that number stops decrypting at once. From the
        customer's side the symptom is a form that no longer opens, with no
        error anywhere on our side — the endpoint simply answers 421 to a body
        encrypted for a key we no longer hold. The private half stored here is
        overwritten at the same moment and cannot be restored.


        ⛔ **IMPORTING REGISTERS NOTHING WITH META, and that is the entire point
        of it.** Your public key is already registered — often by a system that
        predates us — and Meta holds exactly one key per phone id, so
        registering here would replace the very setup the import exists to
        preserve. What makes it safe is a READ: the public half is derived from
        the private one you sent and compared with the key Meta reports for the
        number.


        - they agree → stored, `uploadedAt` stamped, `metaStatus: "VALID"`.

        - they disagree → 400 `FLOW_ENDPOINT_KEY_IMPORT_MISMATCH`, and NOTHING
        is written. Storing it would leave a number reporting `configured: true`
        that decrypts nothing.

        - Meta could not be asked, or holds no key → **stored anyway**, with
        `uploadedAt: null` and `metaStatus` `UNKNOWN`/`NOT_SET`. That null IS
        the "unverified" signal and you have to read it: a 200 alone does not
        tell the two apart.


        A passphrase-protected PEM is accepted; the key is opened once, here,
        and stored normalised. The PEM must be RSA of 2048 bits or more — Meta
        encrypts `data_exchange` with RSA/OAEP.


        ⛔ **`confirm` IS NOT ALWAYS REQUIRED. It is required exactly when the
        call REPLACES A LIVE KEY — and that rule is the same for BOTH verbs.**
        One state check decides it: a key of ours is already live at Meta
        (`uploadedAt` is not `null`). The FIRST setup never asks, whichever verb
        you use, and neither does retrying a pair Meta never accepted
        (`uploadedAt: null`) — that call re-uploads the SAME stored public half
        and converges instead of replacing anything, so there is nothing to
        destroy. When it IS asked for, the check is `=== true`, so `"true"`, `1`
        and `{}` are all refused.


        ⚠️ **Importing is gated too, and what it puts at risk is OUR side, not
        Meta's.** An import registers nothing at Meta, but it overwrites the
        private half stored here — the key WE decrypt with. If the number
        already held a working key and Meta cannot confirm the imported one (it
        being unreachable, say), the imported key is stored anyway and every
        `data_exchange` request on that number stops decrypting, with nothing
        changed at Meta to point at. So the two verbs share the flag and answer
        with DIFFERENT codes: `FLOW_ENDPOINT_KEY_REQUIRES_CONFIRMATION` when
        generating (what is at stake is the registration at Meta) and
        `FLOW_ENDPOINT_KEY_IMPORT_REQUIRES_CONFIRMATION` when importing (what is
        at stake is the private half kept here).


        ⚠️ `replaced` is in the payload because a 200 alone does not say which
        of the two things happened. `replaced: false` is "a key now exists where
        none was live"; `replaced: true` is "the key that was working is gone,
        and every Flow that was answering with the old pair is now answering
        with the new one".


        **The body accepts three fields — `confirm`, `privateKey` and
        `passphrase` — and anything else is 400 `FLOW_UNKNOWN_FIELDS`**.
        `passphrase` on its own is REFUSED, 400
        `FLOW_ENDPOINT_KEY_PASSPHRASE_ORPHAN`, and never ignored: a mistyped
        `privateKey` would otherwise fall through into GENERATE, which registers
        a fresh key at Meta and replaces whatever was there — the most
        destructive outcome this route has, reached by a typo and reported as
        success. A `privateKey` that is not a non-empty string, or a
        `passphrase` that is not a string, is 400
        `FLOW_ENDPOINT_KEY_IMPORT_INVALID`, answered before any crypto runs. A
        PEM that cannot be used is a 400 whose code names the repair:
        `FLOW_ENDPOINT_KEY_PASSPHRASE_REQUIRED`,
        `FLOW_ENDPOINT_KEY_PASSPHRASE_WRONG`, or `FLOW_ENDPOINT_KEY_PEM_INVALID`
        (unreadable, not RSA, or under 2048 bits).


        A truncated body is not read as an empty one — the payload is parsed
        from raw text, so `{"confirm": true` (one missing brace) is 400
        `FLOW_BODY_INVALID` rather than a confusing "confirmation required". No
        body at all stays legitimate, and means GENERATE. Naming the number in
        the body — `whatsappNumberId`, `numberId` or `wabaId` — has a refusal of
        its own, 400 `FLOW_NUMBER_FROM_KEY`, because the number comes from the
        credential. Permission `flows:manage`.


        **Requires a number-scoped key.** A tenant-scoped key must name the
        number with the `x-whatsapp-number-id` header, or it gets 403
        `TENANT_SCOPE_NOT_ALLOWED`.
      operationId: post_numbers_id_flow_endpoint_key
      parameters:
        - name: id
          in: path
          required: true
          description: >-
            The id of the number the key is bound to — the `id` (or instance id)
            `GET /v1/numbers` returns. It is CHECKED against the credential's
            own number, never used to look one up.
          schema:
            type: string
          example: num_01HZX...
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                confirm:
                  type: boolean
                  enum:
                    - true
                  description: >-
                    Must be the boolean `true`, exactly. ⛔ NOT always required:
                    it is required exactly when the call REPLACES A LIVE KEY —
                    i.e. when a key of ours is already live at Meta
                    (`uploadedAt` is not `null`) — and that rule holds for BOTH
                    verbs, generating and importing alike. The first setup does
                    not ask, and neither does retrying a pair Meta never
                    accepted. What it acknowledges differs by verb: when
                    generating, that the key registered at Meta is REPLACED and
                    every `data_exchange` Flow of this number stops decrypting;
                    when importing, that the private half stored here is
                    REPLACED, which breaks decryption on our side if Meta cannot
                    confirm the imported key.
                  example: true
                privateKey:
                  type: string
                  minLength: 1
                  description: >-
                    The PEM of a private key you ALREADY own, whose public half
                    Meta has registered for this number. ⛔ Its PRESENCE is what
                    turns this POST from GENERATE into IMPORT: sent, nothing is
                    registered with Meta and only the stored private half
                    changes; omitted, a fresh pair is minted and its public half
                    REGISTERED at Meta. Must be a non-empty string —
                    `privateKey: 42` is 400 `FLOW_ENDPOINT_KEY_IMPORT_INVALID`
                    and is never coerced. Send the whole `.pem` file contents,
                    `-----BEGIN …-----` and `-----END …-----` lines included.
                    RSA, 2048 bits or more; a passphrase-protected PEM is
                    accepted alongside `passphrase`. If its public half is not
                    the one Meta holds, the answer is 400
                    `FLOW_ENDPOINT_KEY_IMPORT_MISMATCH` and nothing is written.
                  example: |
                    -----BEGIN PRIVATE KEY-----
                    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ...
                    -----END PRIVATE KEY-----
                passphrase:
                  type: string
                  description: >-
                    The passphrase that opens `privateKey`, when the PEM is
                    encrypted. Optional, and only ever alongside `privateKey`: ⛔
                    sent on its own it is REFUSED with 400
                    `FLOW_ENDPOINT_KEY_PASSPHRASE_ORPHAN` rather than ignored,
                    because a mistyped `privateKey` would otherwise fall through
                    into GENERATE and replace the registration at Meta. Must be
                    a string. An encrypted PEM with no passphrase is 400
                    `FLOW_ENDPOINT_KEY_PASSPHRASE_REQUIRED`; a passphrase that
                    does not open it is 400 `FLOW_ENDPOINT_KEY_PASSPHRASE_WRONG`
                    — two codes, because they are two different repairs.
                  example: the-passphrase-that-opens-the-pem
            examples:
              generateFirstTime:
                summary: >-
                  GENERATE, first setup — no body needed, nothing live to
                  replace
                value: {}
              generateReplacingLiveKey:
                summary: GENERATE over a live key — `confirm` required
                value:
                  confirm: true
              importFirstTime:
                summary: IMPORT a key you already own — nothing is registered with Meta
                value:
                  privateKey: |
                    -----BEGIN PRIVATE KEY-----
                    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ...
                    -----END PRIVATE KEY-----
              importPassphraseProtected:
                summary: IMPORT a passphrase-protected PEM over a live key
                value:
                  privateKey: |
                    -----BEGIN PRIVATE KEY-----
                    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ...
                    -----END PRIVATE KEY-----
                  passphrase: the-passphrase-that-opens-the-pem
                  confirm: true
      responses:
        '200':
          description: >-
            A keypair now stands on the number. GENERATE also registered the
            public half with Meta; IMPORT registered NOTHING — there,
            `uploadedAt: null` with `metaStatus` `UNKNOWN`/`NOT_SET` means the
            key was STORED and Meta did not confirm it, which is the one thing a
            200 alone does not tell you. `replaced` says whether a key that was
            LIVE has just been displaced — a 200 alone does not
          content:
            application/json:
              examples:
                generated:
                  summary: >-
                    GENERATE — a fresh pair, registered with Meta, replacing a
                    live key
                  value:
                    configured: true
                    publicKey: |
                      -----BEGIN PUBLIC KEY-----
                      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
                      -----END PUBLIC KEY-----
                    uploadedAt: '2026-09-02T10:00:00.000Z'
                    endpointUrl: https://pilotstatus.com.br/api/flows/endpoint/AbC123...
                    metaStatus: VALID
                    replaced: true
                importedVerified:
                  summary: IMPORT — Meta confirmed the key is the one it holds
                  value:
                    configured: true
                    publicKey: |
                      -----BEGIN PUBLIC KEY-----
                      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
                      -----END PUBLIC KEY-----
                    uploadedAt: '2026-09-02T10:00:00.000Z'
                    endpointUrl: https://pilotstatus.com.br/api/flows/endpoint/AbC123...
                    metaStatus: VALID
                    replaced: false
                importedUnverified:
                  summary: >-
                    IMPORT — stored, but Meta did NOT confirm it (uploadedAt is
                    null)
                  value:
                    configured: true
                    publicKey: |
                      -----BEGIN PUBLIC KEY-----
                      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
                      -----END PUBLIC KEY-----
                    uploadedAt: null
                    endpointUrl: https://pilotstatus.com.br/api/flows/endpoint/AbC123...
                    metaStatus: UNKNOWN
                    replaced: false
        '400':
          description: >-
            `FLOW_ENDPOINT_KEY_REQUIRES_CONFIRMATION` (generating would replace
            a key live at Meta and `confirm: true` was not sent),
            `FLOW_ENDPOINT_KEY_IMPORT_REQUIRES_CONFIRMATION` (the same gate on
            the import verb — what would be replaced is the private half stored
            here), `FLOW_ENDPOINT_KEY_IMPORT_MISMATCH` (the key sent is not the
            one Meta holds; NOTHING was written),
            `FLOW_ENDPOINT_KEY_IMPORT_INVALID`,
            `FLOW_ENDPOINT_KEY_PASSPHRASE_ORPHAN`,
            `FLOW_ENDPOINT_KEY_PASSPHRASE_REQUIRED`,
            `FLOW_ENDPOINT_KEY_PASSPHRASE_WRONG`,
            `FLOW_ENDPOINT_KEY_PEM_INVALID`, `FLOW_BODY_INVALID`,
            `FLOW_UNKNOWN_FIELDS`, `FLOW_NUMBER_FROM_KEY`, or a Meta refusal
            passed through with Meta's own message and code
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              examples:
                requiresConfirmation:
                  summary: FLOW_ENDPOINT_KEY_REQUIRES_CONFIRMATION
                  value:
                    error: >-
                      Este número já tem uma chave registrada na Meta. Registrar
                      uma nova SUBSTITUI a atual e quebra todos os Flows
                      `data_exchange` deste número no instante em que a Meta
                      aceitar — envie `"confirm": true` (booleano, exatamente)
                      para prosseguir.
                    errorEN: >-
                      This number already has a key registered with Meta.
                      Registering a new one REPLACES it and breaks every
                      `data_exchange` Flow of this number the moment Meta
                      accepts it — send `"confirm": true` (a boolean, exactly)
                      to proceed.
                    code: FLOW_ENDPOINT_KEY_REQUIRES_CONFIRMATION
                importRequiresConfirmation:
                  summary: FLOW_ENDPOINT_KEY_IMPORT_REQUIRES_CONFIRMATION
                  value:
                    error: >-
                      Este número já tem uma chave em uso. Importar outra
                      SUBSTITUI a chave privada guardada aqui — e se a Meta não
                      confirmar a importada (indisponível, por exemplo), o
                      número fica com uma chave que pode não decifrar nada. Nada
                      é registrado na Meta por esta operação. Envie `"confirm":
                      true` (booleano, exatamente) para prosseguir.
                    errorEN: >-
                      This number already has a key in use. Importing another
                      REPLACES the private key stored here — and if Meta cannot
                      confirm the imported one (it being unreachable, say), the
                      number is left holding a key that may decrypt nothing.
                      Nothing is registered with Meta by this operation. Send
                      `"confirm": true` (a boolean, exactly) to proceed.
                    code: FLOW_ENDPOINT_KEY_IMPORT_REQUIRES_CONFIRMATION
                importMismatch:
                  summary: FLOW_ENDPOINT_KEY_IMPORT_MISMATCH
                  value:
                    error: >-
                      A chave privada enviada não corresponde à chave pública
                      que a Meta tem registrada para este número — guardá-la
                      deixaria o número sem conseguir decifrar nada, e a falha
                      só apareceria como um formulário que não avança. Importe a
                      chave correspondente à registrada, ou faça um POST sem
                      `privateKey` para gerar um par novo (isso SUBSTITUI o
                      registro na Meta).
                    errorEN: >-
                      The private key you sent does not match the public key
                      Meta has registered for this number — storing it would
                      leave the number unable to decrypt anything, and the
                      failure would only show up as a form that never advances.
                      Import the key matching the registered one, or POST
                      without `privateKey` to generate a fresh pair (which
                      REPLACES the registration at Meta).
                    code: FLOW_ENDPOINT_KEY_IMPORT_MISMATCH
                importInvalidField:
                  summary: FLOW_ENDPOINT_KEY_IMPORT_INVALID
                  value:
                    error: >-
                      `privateKey` deve ser a chave privada em PEM (string não
                      vazia) e `passphrase`, quando enviada, uma string.
                    errorEN: >-
                      `privateKey` must be the private key in PEM (a non-empty
                      string), and `passphrase`, when sent, a string.
                    code: FLOW_ENDPOINT_KEY_IMPORT_INVALID
                passphraseOrphan:
                  summary: FLOW_ENDPOINT_KEY_PASSPHRASE_ORPHAN
                  value:
                    error: >-
                      `passphrase` só faz sentido junto de `privateKey`. Sem
                      `privateKey` esta rota GERA um par novo e o registra na
                      Meta, substituindo o atual — não é o que uma passphrase
                      indica querer. Envie a chave, ou remova a passphrase.
                    errorEN: >-
                      `passphrase` only means something alongside `privateKey`.
                      Without `privateKey` this route GENERATES a new pair and
                      registers it with Meta, replacing the current one — which
                      is not what sending a passphrase suggests you meant. Send
                      the key, or drop the passphrase.
                    code: FLOW_ENDPOINT_KEY_PASSPHRASE_ORPHAN
                passphraseRequired:
                  summary: FLOW_ENDPOINT_KEY_PASSPHRASE_REQUIRED
                  value:
                    error: >-
                      A chave privada enviada está protegida por passphrase e
                      nenhuma foi informada. Envie `passphrase` junto de
                      `privateKey`.
                    errorEN: >-
                      The private key you sent is passphrase-protected and none
                      was given. Send `passphrase` alongside `privateKey`.
                    code: FLOW_ENDPOINT_KEY_PASSPHRASE_REQUIRED
                passphraseWrong:
                  summary: FLOW_ENDPOINT_KEY_PASSPHRASE_WRONG
                  value:
                    error: >-
                      A passphrase não abre a chave privada enviada. Confira a
                      passphrase — a chave em si não foi rejeitada, ela apenas
                      não foi aberta.
                    errorEN: >-
                      The passphrase does not open the private key you sent.
                      Check the passphrase — the key itself was not rejected, it
                      simply was not opened.
                    code: FLOW_ENDPOINT_KEY_PASSPHRASE_WRONG
                pemUnreadable:
                  summary: FLOW_ENDPOINT_KEY_PEM_INVALID (unreadable PEM)
                  value:
                    error: >-
                      Não foi possível ler `privateKey` como uma chave privada
                      PEM. Envie o conteúdo do arquivo `.pem` inteiro, incluindo
                      as linhas `-----BEGIN …-----` e `-----END …-----`.
                    errorEN: >-
                      `privateKey` could not be read as a PEM private key. Send
                      the whole `.pem` file contents, including the `-----BEGIN
                      …-----` and `-----END …-----` lines.
                    code: FLOW_ENDPOINT_KEY_PEM_INVALID
                pemNotRsa:
                  summary: FLOW_ENDPOINT_KEY_PEM_INVALID (not an RSA key)
                  value:
                    error: >-
                      A chave enviada não é RSA. A Meta cifra o `data_exchange`
                      com RSA/OAEP, e uma chave EC ou Ed25519 não decifra nada —
                      envie a chave RSA-2048 do número.
                    errorEN: >-
                      The key you sent is not RSA. Meta encrypts `data_exchange`
                      with RSA/OAEP, and an EC or Ed25519 key decrypts nothing —
                      send the number's RSA-2048 key.
                    code: FLOW_ENDPOINT_KEY_PEM_INVALID
                pemTooWeak:
                  summary: FLOW_ENDPOINT_KEY_PEM_INVALID (RSA under 2048 bits)
                  value:
                    error: >-
                      A chave RSA enviada é menor que 2048 bits. A Meta
                      especifica RSA-2048 e recusaria a chave — envie uma de
                      2048 bits ou mais.
                    errorEN: >-
                      The RSA key you sent is smaller than 2048 bits. Meta
                      specifies RSA-2048 and would refuse it — send one of 2048
                      bits or more.
                    code: FLOW_ENDPOINT_KEY_PEM_INVALID
        '401':
          description: Missing or invalid `x-api-key` / `x-api-key-id` header
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Unauthorized
        '403':
          description: >-
            Tenant-scoped key used on a number-scoped endpoint, or the key's
            role lacks `flows:manage`
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: >-
                  This endpoint acts on a single WhatsApp number: send the
                  x-whatsapp-number-id header naming the number to act on (its
                  id or instance id from GET /v1/numbers) | Este endpoint atua
                  sobre um único número de WhatsApp: envie o header
                  x-whatsapp-number-id indicando o número desejado (o id dele ou
                  o id da instância, obtidos em GET /v1/numbers)
                code: TENANT_SCOPE_NOT_ALLOWED
        '404':
          description: >-
            The path `{id}` is not the number this credential is bound to. Never
            403: another tenant's number and another of your own numbers answer
            the same body, byte for byte, so a guessed id is never confirmed
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              example:
                error: >-
                  Número não encontrado para esta chave. Esta rota atua sobre o
                  número ao qual a chave está vinculada — use a chave do número
                  em questão, ou o header x-whatsapp-number-id se a chave for de
                  conta.
                errorEN: >-
                  Number not found for this key. This route acts on the number
                  the key is bound to — use that number's key, or the
                  x-whatsapp-number-id header if the key is an account-wide one.
                code: FLOW_NUMBER_NOT_FOUND
        '422':
          description: >-
            The key's number is not a Meta (Cloud API) number, or has no WABA
            behind it
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              example:
                error: >-
                  Flows existem apenas em números Meta (API Oficial). O número
                  desta chave não é Meta ou não tem uma WABA associada — use uma
                  chave de um número Meta.
                errorEN: >-
                  Flows only exist on Meta (Cloud API) numbers. This key's
                  number is not a Meta number, or has no WABA behind it — use a
                  key bound to a Meta number.
                code: FLOW_REQUIRES_META_NUMBER
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Too many requests
        '500':
          description: >-
            `FLOW_ENDPOINT_KEY_UNAVAILABLE` (at-rest key encryption is not
            configured, so the service refuses to store the private half in the
            clear — on BOTH verbs, generate and import alike, even though the
            message speaks of generating; a deployment fault the caller can do
            nothing about) or `INTERNAL_ERROR`
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              example:
                error: >-
                  Não foi possível gerar a chave do endpoint: o serviço não está
                  configurado para guardar a chave privada com segurança. Fale
                  com o suporte.
                errorEN: >-
                  Could not generate the endpoint key: the service is not
                  configured to store the private key safely. Contact support.
                code: FLOW_ENDPOINT_KEY_UNAVAILABLE
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: Your ps_ API key
    apiKeyId:
      type: apiKey
      in: header
      name: x-api-key-id
      description: API key id (alternative to x-api-key)

````