> ## 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.

# Register Flow endpoint at Meta

> Registers OUR proxy as this Flow's `endpoint_uri` at Meta — the deliberate act the `PUT` on this same resource refuses to perform. **Without it `data_exchange` is unreachable**: Meta refuses to publish a Flow whose routing model needs an endpoint, with *"Publishing without specifying 'endpoint_uri' is forbidden"*.

⛔ It is a different verb and not a side effect of the `PUT` because the two acts have different subjects. Saving a destination is the tenant describing THEIR webhook; this is us telling META to route the Flow through us. Fusing them would mean a tenant who runs their own Flow endpoint loses every exchange the next time they fix a typo in a URL.

⚠️ The body is OPTIONAL — unlike `PUT`, this verb has no required field, so no body, an empty string and `{}` are all accepted. A truncated body is still refused (400 `FLOW_BODY_INVALID`), and `confirmOverwrite` is the only field accepted.

⛔ `confirmOverwrite` is not a formality. The current `endpoint_uri` is read LIVE from the Graph — not from `metaEndpointUri`, which an hourly sync writes and which is therefore silent about a URI registered in the last hour. If the Flow already points somewhere else, registering takes every exchange away from whoever answers today, so the default is 409 `FLOW_ENDPOINT_URI_WOULD_OVERWRITE` — whose body carries a FOURTH top-level field, `currentEndpointUri`, naming the endpoint that would be displaced. Ours is never echoed: it embeds the number's endpoint path token. A non-boolean is refused and never coerced (400 `FLOW_ENDPOINT_CONFIRM_INVALID`), because `"false"` read as truthy would take over a live endpoint on a request whose author believed they had declined.

Idempotent: a Flow already pointing at us answers 200 without a Graph write, and `metaEndpointUri` is refreshed either way. A number with no endpoint keypair is refused with 422 `FLOW_ENDPOINT_NUMBER_HAS_NO_KEY` — registering anyway tells Meta to encrypt for a keypair that does not exist, and every exchange of the published Flow would 421. 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/flows/{id}/endpoint
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/flows/{id}/endpoint:
    post:
      tags:
        - Flows
      summary: Register Flow endpoint at Meta
      description: >-
        Registers OUR proxy as this Flow's `endpoint_uri` at Meta — the
        deliberate act the `PUT` on this same resource refuses to perform.
        **Without it `data_exchange` is unreachable**: Meta refuses to publish a
        Flow whose routing model needs an endpoint, with *"Publishing without
        specifying 'endpoint_uri' is forbidden"*.


        ⛔ It is a different verb and not a side effect of the `PUT` because the
        two acts have different subjects. Saving a destination is the tenant
        describing THEIR webhook; this is us telling META to route the Flow
        through us. Fusing them would mean a tenant who runs their own Flow
        endpoint loses every exchange the next time they fix a typo in a URL.


        ⚠️ The body is OPTIONAL — unlike `PUT`, this verb has no required field,
        so no body, an empty string and `{}` are all accepted. A truncated body
        is still refused (400 `FLOW_BODY_INVALID`), and `confirmOverwrite` is
        the only field accepted.


        ⛔ `confirmOverwrite` is not a formality. The current `endpoint_uri` is
        read LIVE from the Graph — not from `metaEndpointUri`, which an hourly
        sync writes and which is therefore silent about a URI registered in the
        last hour. If the Flow already points somewhere else, registering takes
        every exchange away from whoever answers today, so the default is 409
        `FLOW_ENDPOINT_URI_WOULD_OVERWRITE` — whose body carries a FOURTH
        top-level field, `currentEndpointUri`, naming the endpoint that would be
        displaced. Ours is never echoed: it embeds the number's endpoint path
        token. A non-boolean is refused and never coerced (400
        `FLOW_ENDPOINT_CONFIRM_INVALID`), because `"false"` read as truthy would
        take over a live endpoint on a request whose author believed they had
        declined.


        Idempotent: a Flow already pointing at us answers 200 without a Graph
        write, and `metaEndpointUri` is refreshed either way. A number with no
        endpoint keypair is refused with 422 `FLOW_ENDPOINT_NUMBER_HAS_NO_KEY` —
        registering anyway tells Meta to encrypt for a keypair that does not
        exist, and every exchange of the published Flow would 421. 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_flows_id_endpoint
      parameters:
        - name: id
          in: path
          required: true
          description: >-
            The **local** id of the Flow — the `id` field `GET /v1/flows`
            returns, never `metaFlowId`.
          schema:
            type: string
          example: cmf1a2b3c4d5e6f7g8h9i0j1
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                confirmOverwrite:
                  type: boolean
                  description: >-
                    Consent to displace an `endpoint_uri` that is not ours. Must
                    be a real boolean — the string `"false"` is refused, never
                    coerced. Unnecessary when Meta holds no `endpoint_uri`, or
                    already holds ours.
                  example: true
            example:
              confirmOverwrite: true
      responses:
        '200':
          description: >-
            Meta now points this Flow at our proxy. `metaEndpointUri` is
            refreshed from what was just written, not from the hourly mirror. No
            `secret`: registering mints nothing
          content:
            application/json:
              example:
                flowId: cmf1a2b3c4d5e6f7g8h9i0j1
                url: https://hooks.acme.com/flows/data-exchange
                hasSecret: true
                endpointUri: >-
                  https://pilotstatus.com.br/api/flows/endpoint/AbC123.../1122334455
                metaEndpointUri: >-
                  https://pilotstatus.com.br/api/flows/endpoint/AbC123.../1122334455
                drift: false
                numberHasKey: true
                numberKeyUploadedAt: '2026-09-01T18:04:00.000Z'
                warnings: []
        '400':
          description: >-
            `FLOW_ENDPOINT_CONFIRM_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
              example:
                error: confirmOverwrite deve ser um booleano (true ou false).
                errorEN: confirmOverwrite must be a boolean (true or false).
                code: FLOW_ENDPOINT_CONFIRM_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 the permission
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Tenant-scoped keys cannot call number endpoints
                code: TENANT_SCOPE_NOT_ALLOWED
        '404':
          description: >-
            No Flow with that id inside the key's own WABA. Never 403: a Flow of
            another tenant is indistinguishable from one that does not exist, on
            purpose — a 403 would confirm someone else's id to whoever guessed
            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: Flow não encontrado para o número desta chave.
                errorEN: Flow not found for this key's number.
                code: FLOW_NOT_FOUND
        '409':
          description: >-
            Meta already points this Flow at an endpoint that is not ours.
            Nothing about the request is malformed — the STATE at Meta is what
            blocks it, and a retry of the same body keeps hitting it until the
            caller decides to take it over. The fourth top-level field
            `currentEndpointUri` names the endpoint that would be displaced;
            ours is not echoed, because it embeds the number's endpoint path
            token
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
                  currentEndpointUri:
                    type: string
                    description: >-
                      The `endpoint_uri` Meta holds today, read live from the
                      Graph. Present on this error only
              example:
                error: >-
                  A Meta já aponta este Flow para outro endpoint. Registar o
                  nosso passaria TODAS as trocas deste Flow para a Pilot Status.
                  Reenvie com confirmOverwrite para assumir.
                errorEN: >-
                  Meta already points this Flow at another endpoint. Registering
                  ours would take EVERY exchange of this Flow over to Pilot
                  Status. Re-send with confirmOverwrite to take it over.
                code: FLOW_ENDPOINT_URI_WOULD_OVERWRITE
                currentEndpointUri: https://flows.acme.com/data-exchange
        '422':
          description: >-
            `FLOW_ENDPOINT_NUMBER_HAS_NO_KEY` (the number has no endpoint
            keypair, so there is no URL to register),
            `FLOW_REQUIRES_META_NUMBER` (the key's number is not a Meta number,
            or has no WABA behind it) or `FLOW_NUMBER_WABA_MISMATCH`
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              example:
                error: >-
                  Este número ainda não tem chave de endpoint de Flow, por isso
                  não existe URL para registar na Meta. Gere a chave do número
                  primeiro.
                errorEN: >-
                  This number has no Flow endpoint key yet, so there is no URL
                  to register at Meta. Generate the number's key first.
                code: FLOW_ENDPOINT_NUMBER_HAS_NO_KEY
        '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: >-
            Internal error. Never carries the internal message — that belongs in
            the log
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              example:
                error: Erro interno do servidor.
                errorEN: Internal server error.
                code: INTERNAL_ERROR
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)

````