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

# Update a menu

> For a connected app's key alone, with `chat:menus` in its connection's grant: an application's own key, full access included, answers `403 insufficient_scope`, and adds its menus in the console. Changes any of `title`, `url` and `icon` of a menu this connection made; what is left out stays, and at least one is sent. A new `url` is checked for framing again. Use it to rotate the key in the link: keep the new key on your server beside the old one, send the new link, then drop the old key once the client's team has reloaded Tawked Chat. Answers the menu as it stands after the change.

Guide: [Connected apps](/integrations/connected-apps)


## OpenAPI

````yaml https://tawked.com/openapi.json patch /v1/chat/menus/{id}
openapi: 3.1.0
info:
  title: Tawked API
  version: 2026-09
  summary: >-
    Tawked Verify: OTP verification for Saudi mobile numbers over SMS, in two
    calls. Tawked Notifications: WhatsApp template messages from the
    application's own number. Tawked Email: transactional e-mail from the
    application's own verified domain. Tawked Chat: the conversation menus a
    connected app adds beside the conversations of its connection's application,
    and the auto-attendant it sets up there. The Partner API for platforms that
    run Tawked Verify for their merchants.
  description: >-
    The public API of Tawked, a Saudi messaging platform: Tawked Verify
    (one-time codes), Tawked Notifications (WhatsApp templates from the
    application's own number), Tawked Email (transactional e-mail from the
    application's own domain) and the Partner API. Tawked Verify is an OTP
    verification API for Saudi numbers. One call sends a one-time code by SMS to
    a Saudi mobile number, and a second call checks it. SAR 0.09 per SMS code
    sent, prepaid; a send the network refuses is refunded. Authenticate with a
    Bearer API key from the dashboard (tk_live_ for production, tk_test_ for the
    sandbox, tk_partner_ for partners, tk_partner_test_ for the partner
    sandbox). Partners (tk_partner_) provision a Verify application per merchant
    under /v1/partner/applications and name it as application on every Verify
    call. Both /v1/... and /api/v1/... answer identically. Rate limit: 120
    requests per minute per key.
  termsOfService: https://tawked.com/en/terms
  license:
    name: Proprietary (Tawked terms of service)
    url: https://tawked.com/en/terms
  contact:
    name: Tawked support
    email: support@tawked.com
    url: https://tawked.com/en/contact
servers:
  - url: https://tawked.com
    description: Production (the sandbox is a tk_test_ key on the same host)
  - url: https://tawked.com/api
    description: The same API under /api/v1/..., kept for older integrations
security:
  - bearerKey: []
  - headerKey: []
tags:
  - name: Verify
    x-group: Verify
    description: Send and check one-time codes.
  - name: WhatsApp
    x-group: Notifications
    description: >-
      Tawked Notifications: send approved WhatsApp templates through the number
      connected to an application. An application in review or approved connects
      its own number from the dashboard.
  - name: Email
    x-group: Email
    description: >-
      Tawked Email: send one transactional message per call from an address on a
      verified domain of the application, and read its status. Live keys only.
  - name: Chat
    x-group: Chat
    description: >-
      Tawked Chat: the conversation menus a connected app adds beside the
      conversations of its connection's application, and reads, changes and
      removes, with `chat:menus`; and the application's auto-attendant, which a
      connected app sets up, publishes and switches on or off, with
      `chat:attendant`. A connected app's key only; a business adds its own
      menus and builds its auto-attendant in the console.
  - name: Partner
    x-group: Partner API
    description: >-
      For platforms that run Tawked Verify for their merchants: provision a
      Verify application per merchant, keep its brand and verify settings
      current, switch it off and on, and read usage and the code log. Partner
      keys (tk_partner_) only; every Verify operation then takes `application`
      with the partner's own id for the application.
  - name: Webhooks
    x-group: Webhooks
    description: >-
      The events Tawked sends to your endpoint: an application's verification
      outcomes and message statuses, and a partner account's lifecycle, balance
      and opt-in verification events. Every delivery is signed the same way and
      retried the same way.
externalDocs:
  description: Developer docs, with code samples
  url: https://docs.tawked.com
paths:
  /v1/chat/menus/{id}:
    patch:
      tags:
        - Chat
      summary: Update a menu
      description: >-
        For a connected app's key alone, with `chat:menus` in its connection's
        grant: an application's own key, full access included, answers `403
        insufficient_scope`, and adds its menus in the console. Changes any of
        `title`, `url` and `icon` of a menu this connection made; what is left
        out stays, and at least one is sent. A new `url` is checked for framing
        again. Use it to rotate the key in the link: keep the new key on your
        server beside the old one, send the new link, then drop the old key once
        the client's team has reloaded Tawked Chat. Answers the menu as it
        stands after the change.
      operationId: chat_menu_update
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: >-
            The menu's `id`, as the create answered it. An id this connection
            did not make answers `404 not_found`, whoever made it.
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            A 1 to 128 printable-ASCII-character key you generate, scoped to
            your API key. Replaying it with the same request answers the
            original 2xx unchanged, with the response header
            `Idempotent-Replayed: true`, and runs nothing again; the same key
            with another request answers `409 idempotency_key_reused`, and a
            request still in flight `409 idempotency_in_progress`. A refused
            request is never stored, so a corrected retry under the same key
            runs. Keys expire after 24 hours.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  description: >-
                    The name your client's team sees on the tab: 2 to 30
                    characters, unique among the menus that show in the
                    application's conversations. Refused: `required`, `length`,
                    `taken`.
                url:
                  type: string
                  description: >-
                    The link of the page, saved as sent: `https://` only, at
                    most 1024 characters, on a public domain name (no IP
                    address, no port but 443, no user name or password, no token
                    in the host). It may hold the tokens `{customer_phone}`,
                    `{customer_name}`, `{customer_email}` and
                    `{conversation_id}`, filled when the menu opens, and the key
                    you made for this one client. Refused: `required`, `http`,
                    `host`, `length`, `reserved`.
                icon:
                  type:
                    - string
                    - object
                  description: >-
                    Left out, the current icon stays. `"site"` takes the link's
                    site's own icon (its favicon or apple-touch-icon), as the
                    console does; `"none"` shows no icon; `{ "url":
                    "https://..." }` is an https link to a PNG, JPEG or WebP of
                    at most 1 MB and at least 32 px on its short side, which
                    Tawked downloads once from a public address and turns into
                    the 64 px icon Tawked Chat shows. Refused: `type` (not a
                    PNG, JPEG or WebP of at most 1 MB, or an `icon` of another
                    shape), `small` (under 32 px), `unreachable` (the link could
                    not be read).
      responses:
        '200':
          description: The menu after the change
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  title:
                    type: string
                  url:
                    type: string
                  icon_url:
                    type: string
                  frame:
                    type: string
                  created_at:
                    type: string
                    format: date-time
                  updated_at:
                    type: string
                    format: date-time
              examples:
                the_menu_after_the_change:
                  summary: The menu after the change
                  value:
                    id: 5b1d2c3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e
                    title: الحجوزات
                    url: >-
                      https://app.yourplatform.example/tawked/bookings?key=NEW_PER_CLIENT_KEY&phone={customer_phone}
                    icon_url: >-
                      https://tawked.com/storage/chat-menus/5b1d2c3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e.png
                    frame: opens
                    created_at: '2026-10-15T09:00:00Z'
                    updated_at: '2026-11-01T08:30:00Z'
        '400':
          description: >-
            `invalid_request`: `fields` names every refused field and `errors`
            gives its codes. `title`: `required`, `length` (not 2 to 30
            characters), `taken` (another menu of the application has that
            name). `url`: `required`, `http` (not `https://`), `host` (an IP
            address, a port but 443, a user name or password, a private or local
            name, a token in the host), `length` (over 1024 characters),
            `reserved` (a link of Tawked's own). `icon`: `type` (not a PNG, JPEG
            or WebP of at most 1 MB, or an `icon` of another shape), `small`
            (under 32 px on its short side), `unreachable` (the icon's link
            could not be read). An edit with none of `title`, `url` and `icon`
            answers `title` `required`. Also a malformed `Idempotency-Key`.;
            `invalid_json`: The request body is not valid JSON.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - invalid_request
                          - invalid_json
        '401':
          description: >-
            `unauthorized`: Missing, unknown, revoked, or mode-mismatched key;
            or an unknown/suspended partner.; `key_expired`: The key's
            `expires_at` has passed. Checked right after the key is recognised,
            before the IP allowlist or scope.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - unauthorized
                          - key_expired
        '403':
          description: >-
            `live_key_required`: A test key. A connected app's key is live; use
            the key the exchange answered.; `account_not_active`: The
            application is not approved yet, the account is banned, or a partner
            disabled this application. All four causes share this one response.;
            `ip_not_allowed`: The caller's IP does not match the key's
            `ip_allowlist` (exact IPs and/or CIDR ranges, IPv4 and IPv6).;
            `insufficient_scope`: The key was not granted this call: a custom
            key without this product, or with a level below the call, such as a
            key that checks where the call sends. The response header
            `WWW-Authenticate` names the scope the call needs. Judged right
            after the key is recognised, before `account_not_active` and the
            request's shape. This operation needs `chat:menus`. An application's
            own key answers this, full access included: only a connected app's
            key holds that scope.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - live_key_required
                          - account_not_active
                          - ip_not_allowed
                          - insufficient_scope
          headers:
            WWW-Authenticate:
              description: >-
                Sent with `insufficient_scope` only: the scope this operation
                needs, which the key was not granted.
              schema:
                type: string
              example: Bearer error="insufficient_scope", scope="chat:menus"
        '404':
          description: >-
            Not this connection's / `not_found`: A menu id this connection did
            not make, whoever made it: the client, another app, or none.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - not_found
              examples:
                not_this_connection_s:
                  summary: Not this connection's
                  value:
                    error: not_found
        '409':
          description: >-
            `idempotency_in_progress`: A request with this `Idempotency-Key` is
            still running. Wait for its answer, or retry in a moment.;
            `idempotency_key_reused`: This `Idempotency-Key` was already used
            for a different request. Use one key per change.; `chat_not_ready`:
            `reason` says why: `chat`, the client's account has no Tawked Chat
            yet; `number`, the application's WhatsApp number is not in Tawked
            Chat yet. Nothing changed. Tell the client, and try again once it is
            ready.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - idempotency_in_progress
                          - idempotency_key_reused
                          - chat_not_ready
        '429':
          description: >-
            `too_many_requests`: Per-key rate limit exceeded (default 120
            requests/minute, fixed 60s window). Checked before authentication.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - too_many_requests
        '500':
          description: >-
            `internal_error`: An unhandled server error. Quote the X-Request-Id
            header if you report it.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - internal_error
        '503':
          description: >-
            `chat_unavailable`: Tawked Chat did not answer. Nothing changed;
            retry with the same `Idempotency-Key`.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - chat_unavailable
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            curl -X PATCH
            https://tawked.com/v1/chat/menus/5b1d2c3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e
            \
              -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxx" \
              -H "Content-Type: application/json" \
              -H "Idempotency-Key: client-8841-bookings-key-2" \
              -d '{
                "url": "https://app.yourplatform.example/tawked/bookings?key=NEW_PER_CLIENT_KEY&phone={customer_phone}"
            }'
        - lang: javascript
          label: Node.js
          source: >-
            const res = await
            fetch("https://tawked.com/v1/chat/menus/5b1d2c3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
            {
              method: "PATCH",
              headers: {
                Authorization: "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx",
                "Content-Type": "application/json",
                "Idempotency-Key": "client-8841-bookings-key-2",
              },
              body: JSON.stringify({
                  "url": "https://app.yourplatform.example/tawked/bookings?key=NEW_PER_CLIENT_KEY&phone={customer_phone}"
              }),
            });

            const data = await res.json();
        - lang: python
          label: Python
          source: |-
            import requests

            res = requests.patch(
                "https://tawked.com/v1/chat/menus/5b1d2c3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
                headers={"Authorization": "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx", "Idempotency-Key": "client-8841-bookings-key-2"},
                json={
                    "url": "https://app.yourplatform.example/tawked/bookings?key=NEW_PER_CLIENT_KEY&phone={customer_phone}"
                },
            )
            data = res.json()
components:
  schemas:
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: A stable machine-readable code; the docs list every one.
      additionalProperties: true
  securitySchemes:
    bearerKey:
      type: http
      scheme: bearer
      description: >-
        Authorization: Bearer <api key>. An application key (tk_live_, tk_test_)
        has full access, which covers every product and any product added later,
        or custom access, chosen when the key is created: one or more of the
        scopes `verify:check`, `verify:send` (includes `verify:check`),
        `notifications:read`, `notifications:send` (includes
        `notifications:read`), `email:read`, `email:send` (includes
        `email:read`), `notifications:templates` (includes
        `notifications:read`), `chat:menus`, `chat:attendant`. No key made on
        the API keys page holds `notifications:templates`, full access included:
        the three template writes answer such a key 403 `insufficient_scope`. A
        connected app's key holds it. Nor `chat:menus`: the five conversation
        menu operations answer such a key 403 `insufficient_scope`, and a
        connected app's key holds it once the client approved it. Nor
        `chat:attendant`: the five auto-attendant operations answer such a key
        403 `insufficient_scope` too, and a connected app's key holds it once
        the client approved it. A call outside the key's access answers 403
        `insufficient_scope` and names the scope it needs in the
        `WWW-Authenticate` response header. A partner key (tk_partner_) always
        has full access. Whatever its access, a key is a server-side secret.
    headerKey:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        The same API key, with the same access, as a header, for clients that
        cannot set Authorization.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.