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

# Send a test delivery to an installation

> A synthetic, signed delivery of a sample body (`tassyir-test: true`, `webhook-id: dlv_test_…`, envelope id `evt_0`); nothing is recorded and a 410 disables nothing. Needs an active installation whose version subscribes to the topic and was granted its scope. 20 tests per app and 40 per developer every 10 minutes.

Takes the dashboard session or a developer token (`tsy_dt_…`); either way the caller is the signed-in user, and only their own account, apps and tokens are reachable.



## OpenAPI

````yaml /openapi/openapi.developer.json post /developer/apps/{appId}/installations/{installationId}/webhooks/test
openapi: 3.1.0
info:
  title: Tassyir developer API
  version: '1'
  summary: >-
    The API a developer calls to build Tassyir apps: their account, apps, client
    secrets, versions, the webhook console, dev stores and the CLI sign-in.
  description: >-
    The routes under `/developer/` that a DEVELOPER calls: the developer
    console, the `tassyir` CLI and the dev MCP. Not an app's API: installed apps
    call the Admin API, which has its own document. Guides:
    https://developers.tassyir.io.


    **Authentication.** `Authorization: Bearer <dashboard JWT>` (the console) or
    `Authorization: Bearer tsy_dt_…` (a developer token from the CLI sign-in),
    on every route but the approval routes (the dashboard session only) and the
    two public RFC 8628 routes. A developer account belongs to a USER, not a
    business: no route takes a business id, and an id that is not yours answers
    404, exactly like one that does not exist.


    **Conventions.** JSON fields are camelCase (snake_case on the two OAuth
    routes, as RFC 8628 has them); ids are strings; times are ISO 8601 UTC. One
    resource answers `{ data }`, a paged list `{ data, nextCursor }` (pass
    `nextCursor` back as `cursor`), a short list `{ data }`.


    **Errors.** Not problem+json: the dashboard API's shape. A coded refusal is
    `{ statusCode, code, message, fix?, …subject, requestId }`
    (`DeveloperError`); act on `code`. Three refusals carry no `code` today: a
    request that fails validation (400, `message` is a list of sentences), a
    missing or ended dashboard session (401), and the platform-wide rate limit
    (429, a bare JSON string). The two OAuth routes answer `{ error,
    error_description }`. Each operation lists every code it can answer, by
    status.


    **Limits.** Each developer has caps (apps, secrets, versions, dev stores),
    and the console's replays and tests and the CLI sign-in have rate limits of
    their own, which apply whether or not the platform-wide rate limit is
    enforced; each operation says which apply.
servers:
  - url: https://api.tassyir.io
security:
  - dashboardSession: []
  - developerToken: []
tags:
  - name: Account
    description: 'Developer mode: your developer account.'
  - name: Apps
    description: 'Your apps: handle, client id, names.'
  - name: Secrets
    description: 'Client secrets: minted once, rotated with a 24-hour overlap.'
  - name: Versions
    description: Manifest uploads.
  - name: Installations
    description: Where your app is installed, and its webhooks' health.
  - name: Webhooks
    description: The delivery log, replay, test deliveries, re-enable.
  - name: Dev stores
    description: Test businesses with synthetic data that reach no one.
  - name: CLI sign-in
    description: >-
      The OAuth 2.0 Device Authorization Grant (RFC 8628) the `tassyir` CLI
      signs in with, and the approval page's routes.
  - name: CLI tokens
    description: 'Your developer tokens: list and revoke.'
paths:
  /developer/apps/{appId}/installations/{installationId}/webhooks/test:
    post:
      tags:
        - Webhooks
      summary: Send a test delivery to an installation
      description: >-
        A synthetic, signed delivery of a sample body (`tassyir-test: true`,
        `webhook-id: dlv_test_…`, envelope id `evt_0`); nothing is recorded and
        a 410 disables nothing. Needs an active installation whose version
        subscribes to the topic and was granted its scope. 20 tests per app and
        40 per developer every 10 minutes.


        Takes the dashboard session or a developer token (`tsy_dt_…`); either
        way the caller is the signed-in user, and only their own account, apps
        and tokens are reachable.
      operationId: sendTestWebhook
      parameters:
        - name: appId
          in: path
          required: true
          description: >-
            One of your apps' ids. Another developer's app, or a malformed id,
            is 404 `APP_NOT_FOUND`.
          schema:
            type: string
            pattern: ^\d{1,20}$
        - name: installationId
          in: path
          required: true
          description: >-
            An installation of this app. Another app's, or a malformed id, is
            404 `APP_INSTALLATION_NOT_FOUND`.
          schema:
            type: string
            pattern: ^\d{1,20}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendWebhookTestInput'
      responses:
        '200':
          description: Whatever the endpoint answered.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookTestResponse'
        '400':
          description: >-
            Bad request: `APP_HOSTING_NOT_AVAILABLE`, `BAD_REQUEST`,
            `WEBHOOK_TOPIC_NOT_SUBSCRIBED`; a field refused by validation (no
            `code`).
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
          content:
            application/json:
              schema:
                anyOf:
                  - allOf:
                      - $ref: '#/components/schemas/DeveloperError'
                      - properties:
                          code:
                            enum:
                              - APP_HOSTING_NOT_AVAILABLE
                              - BAD_REQUEST
                              - WEBHOOK_TOPIC_NOT_SUBSCRIBED
                  - $ref: '#/components/schemas/ValidationError'
        '401':
          $ref: '#/components/responses/DeveloperUnauthorized'
        '403':
          description: 'Not allowed: `DEVELOPER_ACCOUNT_SUSPENDED`.'
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/DeveloperError'
                  - properties:
                      code:
                        enum:
                          - DEVELOPER_ACCOUNT_SUSPENDED
        '404':
          description: >-
            Not found: `APP_INSTALLATION_NOT_FOUND`, `APP_NOT_FOUND`,
            `APP_VERSION_NOT_FOUND`, `DEVELOPER_ACCOUNT_NOT_FOUND`.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/DeveloperError'
                  - properties:
                      code:
                        enum:
                          - APP_INSTALLATION_NOT_FOUND
                          - APP_NOT_FOUND
                          - APP_VERSION_NOT_FOUND
                          - DEVELOPER_ACCOUNT_NOT_FOUND
        '409':
          description: >-
            Conflict: `APP_READ_ONLY`, `WEBHOOK_ENDPOINT_DISABLED`,
            `WEBHOOK_INSTALLATION_INACTIVE`, `WEBHOOK_SCOPE_NOT_GRANTED`.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/DeveloperError'
                  - properties:
                      code:
                        enum:
                          - APP_READ_ONLY
                          - WEBHOOK_ENDPOINT_DISABLED
                          - WEBHOOK_INSTALLATION_INACTIVE
                          - WEBHOOK_SCOPE_NOT_GRANTED
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          description: >-
            Rate limited: `DEVELOPER_RATE_LIMITED`,
            `DEVELOPER_TOKEN_RATE_LIMITED`; the platform-wide rate limit (a
            string).
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            Retry-After:
              $ref: '#/components/headers/Retry-After'
          content:
            application/json:
              schema:
                anyOf:
                  - allOf:
                      - $ref: '#/components/schemas/DeveloperError'
                      - properties:
                          code:
                            enum:
                              - DEVELOPER_RATE_LIMITED
                              - DEVELOPER_TOKEN_RATE_LIMITED
                  - $ref: '#/components/schemas/RateLimitError'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          description: >-
            Unavailable: `APPS_KEYS_NOT_CONFIGURED`,
            `DEVELOPER_RATE_LIMIT_UNAVAILABLE`, `WEBHOOKS_PAUSED`; a database
            that cannot be reached.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            Retry-After:
              $ref: '#/components/headers/Retry-After'
          content:
            application/json:
              schema:
                anyOf:
                  - allOf:
                      - $ref: '#/components/schemas/DeveloperError'
                      - properties:
                          code:
                            enum:
                              - APPS_KEYS_NOT_CONFIGURED
                              - DEVELOPER_RATE_LIMIT_UNAVAILABLE
                              - WEBHOOKS_PAUSED
                  - $ref: '#/components/schemas/ServerError'
      security:
        - dashboardSession: []
        - developerToken: []
components:
  schemas:
    SendWebhookTestInput:
      type: object
      properties:
        topic:
          type: string
          enum:
            - orders/created
            - abandoned_checkouts/created
            - orders/confirmed
            - orders/cancelled
            - orders/dispatched
            - orders/delivered
            - orders/returned
            - orders/updated
            - products/created
            - products/updated
            - products/deleted
            - inventory_levels/updated
            - customers/updated
            - app/installed
            - app/uninstalled
            - app/scopes_updated
            - customers/data_request
            - customers/redact
            - business/redact
            - bulk/finished
      additionalProperties: false
      required:
        - topic
    WebhookTestResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/WebhookTestResult'
      required:
        - data
      additionalProperties: false
    DeveloperError:
      type: object
      description: >-
        A coded refusal of the developer API (the dashboard API's error shape,
        not problem+json). Act on `code`, never on `message`. Other members name
        the subject of the refusal (`appId`, `limit`, `retryAfterSeconds`,
        `status`, …).
      properties:
        statusCode:
          type: integer
        code:
          type: string
          pattern: ^[A-Z][A-Z0-9_]{1,63}$
          description: 'Stable: what a client branches on.'
        message:
          type: string
          description: For a person (English).
        fix:
          type: string
          description: One sentence an AI agent can act on.
        errors:
          type: array
          items:
            $ref: '#/components/schemas/FieldError'
          description: >-
            With `APP_MANIFEST_INVALID`, `APP_FIELDS_INVALID`,
            `DEVELOPER_ACCOUNT_INVALID`: every problem at once, in the manifest
            validator's format.
        path:
          type: string
          description: 'With a body the parser refused: the request path.'
        requestId:
          type: string
          description: Also in the `X-Request-Id` header. Quote it to Tassyir.
      required:
        - code
        - message
    ValidationError:
      type: object
      description: >-
        A request that fails validation: a field missing, malformed or unknown,
        in the body or the query string. It carries NO `code`: branch on the
        status (400) and show `message`.
      properties:
        statusCode:
          type: integer
          const: 400
        message:
          type: array
          items:
            type: string
          description: >-
            One sentence per refused field (`limit must not be greater than
            100`).
        error:
          type: string
          const: Bad Request
        requestId:
          type: string
      required:
        - statusCode
        - message
        - error
    RateLimitError:
      type: string
      description: >-
        The platform-wide rate limit's refusal: a bare JSON string, not an
        object, sent only while that limit is enforced. A developer token's
        calls are counted by IP.
    ServerError:
      type: object
      description: >-
        An unexpected failure (500), or a database that cannot be reached (503:
        retry). Never an internal message: `requestId` names it.
      properties:
        statusCode:
          type: integer
        message:
          type: string
        path:
          type: string
        requestId:
          type: string
      required:
        - statusCode
        - message
    WebhookTestResult:
      type: object
      properties:
        webhookId:
          type: string
          pattern: ^dlv_test_[0-9a-f]{24}$
          description: The `webhook-id` the test was sent with.
        installationId:
          type: string
          pattern: ^\d{1,20}$
          description: 'A Tassyir id: a string of digits.'
        topic:
          type: string
          enum:
            - orders/created
            - abandoned_checkouts/created
            - orders/confirmed
            - orders/cancelled
            - orders/dispatched
            - orders/delivered
            - orders/returned
            - orders/updated
            - products/created
            - products/updated
            - products/deleted
            - inventory_levels/updated
            - customers/updated
            - app/installed
            - app/uninstalled
            - app/scopes_updated
            - customers/data_request
            - customers/redact
            - business/redact
            - bulk/finished
        status:
          type: string
          enum:
            - delivered
            - failed
          description: '`delivered` on a 2xx; a test is never retried.'
        statusCode:
          anyOf:
            - type: integer
              minimum: -9007199254740991
              maximum: 9007199254740991
            - type: 'null'
        error:
          anyOf:
            - type: string
            - type: 'null'
        errorClass:
          anyOf:
            - type: string
              enum:
                - http
                - timeout
                - network
                - refused
                - expired
                - skipped
                - internal
            - type: 'null'
          description: A coarse family of the attempt's error; null without one.
        durationMs:
          type: integer
          minimum: 0
          maximum: 9007199254740991
        sentAt:
          type: string
          format: date-time
          pattern: >-
            ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
          description: ISO 8601 date-time, UTC.
      required:
        - webhookId
        - installationId
        - topic
        - status
        - statusCode
        - error
        - errorClass
        - durationMs
        - sentAt
      additionalProperties: false
      description: What your endpoint answered to a test delivery.
    FieldError:
      type: object
      description: One problem, as the manifest validator reports it.
      properties:
        code:
          type: string
          pattern: ^[A-Z][A-Z0-9_]{1,63}$
        path:
          type: string
          description: 'Where: `handle`, `webhooks[0].uri`, …'
        message:
          type: string
        fix:
          type: string
      required:
        - code
        - path
        - message
    HttpError:
      type: object
      description: >-
        A refusal without a `code`: no dashboard session (no token, a revoked or
        expired session, a password changed since it was issued).
      properties:
        statusCode:
          type: integer
        message:
          anyOf:
            - type: string
            - type: array
              items:
                type: string
        error:
          type: string
        requestId:
          type: string
      required:
        - statusCode
        - message
  headers:
    X-Request-Id:
      description: The request's id, also in an error's `requestId`.
      schema:
        type: string
    Retry-After:
      description: >-
        Seconds to wait before retrying, when the refusal knows (also
        `retryAfterSeconds` in a coded body).
      schema:
        type: integer
        minimum: 0
  responses:
    DeveloperUnauthorized:
      description: >-
        Not signed in: `DEVELOPER_TOKEN_INVALID`; no dashboard session (no
        `code`).
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        WWW-Authenticate:
          description: >-
            On `DEVELOPER_TOKEN_INVALID`: `Bearer realm="tassyir-developer",
            error="invalid_token"`.
          schema:
            type: string
      content:
        application/json:
          schema:
            anyOf:
              - allOf:
                  - $ref: '#/components/schemas/DeveloperError'
                  - properties:
                      code:
                        enum:
                          - DEVELOPER_TOKEN_INVALID
              - $ref: '#/components/schemas/HttpError'
    PayloadTooLarge:
      description: 'Body too large: `PAYLOAD_TOO_LARGE`.'
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/DeveloperError'
              - properties:
                  code:
                    enum:
                      - PAYLOAD_TOO_LARGE
    UnsupportedMediaType:
      description: 'Unsupported body: `BAD_REQUEST`.'
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/DeveloperError'
              - properties:
                  code:
                    enum:
                      - BAD_REQUEST
    InternalError:
      description: 'Internal error: an unexpected failure.'
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ServerError'
  securitySchemes:
    dashboardSession:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        The dashboard's session token (`Authorization: Bearer <JWT>`), as the
        console sends it. Never paste it into a terminal: it opens every
        business the user belongs to.
    developerToken:
      type: http
      scheme: bearer
      bearerFormat: tsy_dt_<43 base62>
      description: >-
        A developer token from the CLI sign-in (`POST
        /developer/cli/device-code`, then `POST /developer/cli/token`): accepted
        on `developer/*` ONLY, scope `developer`. Valid while not revoked, used
        within 90 days (each use extends it) and issued less than 365 days ago;
        refused once the user changes their password. Signing out of the
        dashboard does not revoke it.

````

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