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

# List your numbers

> GET /v1/instances — every number on your account and whether it can send.

The quickest way to find an `instance_id` in code, and a cheap health check for a dashboard.

## Notes

* Returns every number on the account that has not been deleted, **oldest first**.
* `connected` is the field to read: `true` means that number can send right now.
* This returns the **stored** status, not a fresh check of the live connection. It is fast and costs nothing. To force a live check of one number, use [`GET /v1/instances/{id}/status`](/api-reference/instances/status).
* `phone_number` is filled in once the number has been observed connected. It can be `null` on a number that has never connected.
* `last_connected` is the last time the connection was observed open — it moves whenever something checks, so treat it as "seen recently", not as a session start time.
* `slots` summarises how many numbers you are using and what your account allows. `limit` is `null` when there is no fixed cap. If the summary cannot be worked out, `slots` is `null` and the list itself is still correct.

## Example

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.bulkneo.com/v1/instances \
    -H "apikey: YOUR_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const res = await fetch("https://api.bulkneo.com/v1/instances", {
    headers: { apikey: process.env.BULKNEO_API_KEY },
  });

  const { data } = await res.json();
  for (const i of data.instances) {
    console.log(i.label, i.instance_id, i.connected ? "ready" : "DISCONNECTED");
  }
  ```

  ```python Python theme={null}
  import os
  import requests

  res = requests.get(
      "https://api.bulkneo.com/v1/instances",
      headers={"apikey": os.environ["BULKNEO_API_KEY"]},
      timeout=30,
  )
  res.raise_for_status()

  for i in res.json()["data"]["instances"]:
      print(i["label"], i["instance_id"], "ready" if i["connected"] else "DISCONNECTED")
  ```
</CodeGroup>

<Tip>
  This is also the cheapest way to test that an API key works — it sends nothing and uses no message allowance.
</Tip>


## OpenAPI

````yaml api-reference/openapi.json GET /v1/instances
openapi: 3.1.0
info:
  title: BulkNeo WhatsApp API
  version: 1.0.0
  description: >-
    Send WhatsApp messages from your own connected numbers. Every request is
    authenticated with an `apikey` header and names the `instance_id` of the
    number it should be sent from.
servers:
  - url: https://api.bulkneo.com
    description: BulkNeo API
security:
  - apikey: []
tags:
  - name: Messages
    description: Send a message from one of your connected numbers.
  - name: Numbers
    description: Check which numbers are reachable on WhatsApp.
  - name: Instances
    description: Create, connect, inspect and remove your WhatsApp numbers.
  - name: Service
    description: Unauthenticated service health checks.
paths:
  /v1/instances:
    get:
      tags:
        - Instances
      summary: List your numbers
      description: >-
        Lists every number on your account that has not been deleted, oldest
        first, with its stored connection status. This read does not re-check
        the live connection — use `GET /v1/instances/{id}/status` for that.
      operationId: listInstances
      responses:
        '200':
          description: Your numbers.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    const: true
                  data:
                    type: object
                    properties:
                      slots:
                        type:
                          - object
                          - 'null'
                        description: >-
                          How many number slots you are using and what your
                          account allows. `null` if the summary could not be
                          resolved — the list itself is still correct.
                        properties:
                          kind:
                            type: string
                            description: What kind of account this is.
                          used:
                            type: integer
                            description: Live (non-deleted) numbers on the account.
                          limit:
                            type:
                              - integer
                              - 'null'
                            description: >-
                              Maximum allowed, or `null` when there is no fixed
                              cap.
                          billed:
                            type: boolean
                      instances:
                        type: array
                        items:
                          type: object
                          properties:
                            instance_id:
                              type: string
                              format: uuid
                            label:
                              type:
                                - string
                                - 'null'
                            status:
                              $ref: '#/components/schemas/ConnectionStatus'
                            connected:
                              type: boolean
                              description: '`true` when this number can send right now.'
                            phone_number:
                              type:
                                - string
                                - 'null'
                              description: >-
                                The connected WhatsApp number, once we have
                                observed it.
                            last_connected:
                              type:
                                - string
                                - 'null'
                              format: date-time
                              description: The last time the connection was observed open.
                            created_at:
                              type: string
                              format: date-time
                  request_id:
                    type: string
                    format: uuid
              example:
                success: true
                data:
                  slots:
                    kind: direct
                    used: 2
                    limit: null
                    billed: false
                  instances:
                    - instance_id: 3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34
                      label: Sales
                      status: open
                      connected: true
                      phone_number: '919876543210'
                      last_connected: '2026-08-08T09:14:22.000Z'
                      created_at: '2026-07-21T06:02:11.000Z'
                    - instance_id: a1b7c3d9-2e4f-4a60-b8c1-5d7e9f0a2b34
                      label: Support
                      status: close
                      connected: false
                      phone_number: '919812345678'
                      last_connected: '2026-08-06T18:40:03.000Z'
                      created_at: '2026-07-24T11:31:47.000Z'
                request_id: b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  schemas:
    ConnectionStatus:
      type: string
      description: >-
        `open` connected and able to send, `connecting` pairing in progress,
        `close` not connected.
      example: open
    Error:
      type: object
      properties:
        success:
          type: boolean
          const: false
        error:
          type: object
          properties:
            code:
              type: string
              description: >-
                Stable machine-readable code. Branch on this, not on the
                message.
            message:
              type: string
              description: Human-readable explanation. The wording may change.
            details:
              type: object
              description: Extra safe context, present on some errors.
        request_id:
          type: string
          format: uuid
          description: Quote this when asking support about a request.
  responses:
    Unauthorized:
      description: >-
        `missing_api_key` no `apikey` header was sent · `invalid_api_key` the
        key is unknown or revoked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error:
              code: invalid_api_key
              message: Invalid or revoked API key.
            request_id: b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
    Forbidden:
      description: >-
        `account_suspended` · `access_expired` · `trial_quota_exceeded` ·
        `instance_limit_reached` · `activation_unavailable`.


        On `instance_limit_reached`, `details.limit_source` tells you which cap
        you hit: `plan` (the plan you are on — upgrade it) or `account` (a cap
        set on your account — ask your provider to raise it). Branch on that,
        never on the wording of the message.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error:
              code: trial_quota_exceeded
              message: >-
                Trial message quota reached (500 messages). Upgrade to a paid
                plan to keep sending.
              details:
                trial_message_limit: 500
            request_id: b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
    RateLimited:
      description: >-
        `rate_limit_exceeded` — too many requests this minute. Wait for the
        number of seconds in the `Retry-After` header. `details.scope` and the
        `X-RateLimit-Scope` header say WHICH allowance you exceeded.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
        X-RateLimit-Scope:
          $ref: '#/components/headers/RateLimitScope'
        X-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error:
              code: rate_limit_exceeded
              message: Rate limit exceeded, retry in 12s.
              details:
                retry_after_seconds: 12
                limit_per_minute: 20
                scope: messages
            request_id: b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
    InternalError:
      description: >-
        `internal_error` — something went wrong on our side. Quote the
        `request_id` to support.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error:
              code: internal_error
              message: An unexpected error occurred.
            request_id: b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
  headers:
    RateLimitScope:
      description: >-
        WHICH allowance the other X-RateLimit-* headers describe. `messages`
        covers every /v1/messages/* endpoint and /v1/numbers/check together;
        `instances` is the separate, much larger budget for /v1/instances/*. If
        you cache a limit, cache it against its scope.
      schema:
        type: string
        enum:
          - messages
          - instances
      example: messages
    RateLimitLimit:
      description: Requests allowed per minute on this scope.
      schema:
        type: integer
      example: 20
    RateLimitRemaining:
      description: Requests left in the current minute on this scope.
      schema:
        type: integer
      example: 17
    RateLimitReset:
      description: >-
        Seconds until the allowance next goes up by one. Present on successful
        responses too, so you can pace yourself without first earning a 429. The
        window is a rolling minute, so this is when the oldest request ages out
        — not a fixed reset instant. If `X-RateLimit-Remaining` is above 0 you
        can send now.
      schema:
        type: integer
      example: 43
  securitySchemes:
    apikey:
      type: apiKey
      in: header
      name: apikey
      description: >-
        Your API key, sent in an `apikey` request header. Never in the URL,
        never as a Bearer token.

````