openapi: 3.0.3
info:
  title: V-Numbers API
  version: "2.0"
  description: |
    REST API for ordering virtual phone numbers and receiving SMS verification codes.

    ## How it works
    1. Call `/api/v2/order` to reserve a real phone number for a given country and service.
    2. Poll `/api/v2/getStatus` every 5–10 seconds until `status` is `"sms received"`.
    3. Read `sms_code` from the status response and use it for verification.
    4. If no SMS arrives within 20 minutes the order expires automatically — no charge.
    5. Cancel early with `/api/v2/cancelOrder` — also no charge if no SMS was received.

    ## Authentication
    Pass your API key as the `key` query parameter on every request.
    Obtain your key at https://v-numbers.com/docs.

    ## Credits
    Credits are denominated in **cents (USD)**. You are charged only when an SMS is
    successfully delivered. Credits are held (reserved) when an order is placed and
    released back if the order expires or is canceled without receiving an SMS.

  contact:
    url: https://v-numbers.com
  license:
    name: Proprietary

servers:
  - url: https://v-numbers.com
    description: Production

security:
  - ApiKeyAuth: []

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: query
      name: key
      description: Your V-Numbers API key. Find it at https://v-numbers.com/docs.

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          example: Invalid api key
        message:
          type: string
          example: Invalid api key

    Order:
      type: object
      properties:
        orderId:
          type: string
          description: Unique identifier for this order. Use it to poll for status and to cancel.
          example: hold_8Kp2
        number:
          type: string
          description: The virtual phone number assigned to this order, including country code.
          example: "+1 (873) 283-5759"
        country_id:
          type: string
          description: ISO 3166-1 alpha-2 country code (lowercase).
          example: us
        service_id:
          type: string
          description: Service identifier (lowercase).
          example: telegram
        price:
          type: integer
          description: Price in cents (USD). Only charged on successful SMS delivery.
          example: 24
        cancelable_after:
          type: string
          format: date-time
          description: ISO 8601 timestamp after which you may cancel the order.
          example: "2026-06-03T10:21:00.000Z"

    StatusResponse:
      type: object
      properties:
        status:
          type: string
          enum:
            - waiting
            - sms received
            - retry received
            - expired
            - canceled
          description: |
            Current state of the order.
            - `waiting` — number is active, no SMS yet. Keep polling.
            - `sms received` — SMS arrived. Read `sms_code`.
            - `retry received` — a second SMS was delivered after a retry request.
            - `expired` — 20-minute window passed with no SMS. Not charged.
            - `canceled` — order was explicitly canceled. Not charged.
          example: sms received
        sms_code:
          type: string
          nullable: true
          description: The extracted numeric verification code. Null while waiting.
          example: "482916"
        sms_text:
          type: string
          nullable: true
          description: Full raw SMS text as received. Null while waiting.
          example: "Your Telegram code: 482916. Do not share it."
        is_retriable:
          type: boolean
          description: Whether a retry can be requested on this order.
          example: true

    BalanceResponse:
      type: object
      properties:
        balance:
          type: integer
          description: Total credits on the account, in cents.
          example: 1250
        on_hold:
          type: integer
          description: Credits currently reserved for active (HOLD) orders, in cents.
          example: 48
        available:
          type: integer
          description: Credits available for new orders (balance minus on_hold), in cents.
          example: 1202
        balance_usd:
          type: string
          description: Total balance formatted as a USD string.
          example: "12.50"
        on_hold_usd:
          type: string
          description: On-hold amount formatted as a USD string.
          example: "0.48"
        available_usd:
          type: string
          description: Available balance formatted as a USD string.
          example: "12.02"

    PriceResponse:
      type: object
      properties:
        price:
          type: integer
          description: Price in cents for one successful SMS delivery.
          example: 24
        available:
          type: integer
          description: Number of virtual numbers currently available in the pool.
          example: 15420
        successRate:
          type: integer
          description: Historical SMS delivery success rate as a 0–100 integer.
          example: 98

    PricesByCountry:
      type: array
      description: Price and availability for one service across all countries, cheapest first.
      items:
        type: object
        properties:
          country:
            type: string
            description: ISO country code (lowercase).
            example: ph
          price:
            type: integer
            description: Price in cents for one successful SMS delivery.
            example: 18
          available:
            type: integer
            description: Number of virtual numbers currently available in the pool.
            example: 8412

    Country:
      type: object
      properties:
        country_id:
          type: string
          description: ISO 3166-1 alpha-2 code (lowercase). Use this as the `country` parameter.
          example: us
        name:
          type: string
          example: United States

    Service:
      type: object
      properties:
        service_id:
          type: string
          description: Service identifier (lowercase). Use this as the `service` parameter.
          example: telegram
        name:
          type: string
          example: Telegram

    OrderHistory:
      type: object
      properties:
        orderId:
          type: string
          example: hold_8Kp2
        number:
          type: string
          example: "+1 (873) 283-5759"
        status:
          type: string
          enum: [HOLD, DONE, CANCELED]
          example: DONE
        service:
          type: string
          example: telegram
        country:
          type: string
          example: us
        amount:
          type: integer
          description: Amount charged in cents.
          example: 24
        sms_code:
          type: string
          nullable: true
          example: "482916"
        created_at:
          type: string
          format: date-time
          example: "2026-06-03T10:01:00.000Z"

  responses:
    Unauthorized:
      description: Invalid or missing API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Invalid api key

    RateLimited:
      description: Too many requests. Back off and retry.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Rate limit exceeded

    InsufficientCredits:
      description: Account does not have enough available credits.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Not enough credits

    NotFound:
      description: Order not found, or no numbers available for this combination.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: No numbers available for this combination

paths:

  /api/v2/order:
    get:
      operationId: orderNumber
      summary: Order a virtual phone number
      description: |
        Reserves a real virtual phone number for a given country and service.
        Returns an `orderId` used to poll for the SMS code.
        The number remains active for up to 20 minutes.
        You are **only charged when an SMS is successfully delivered**.
      tags: [Orders]
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Your API key.
        - name: country
          in: query
          required: true
          schema:
            type: string
          description: ISO country code (lowercase). e.g. `us`, `gb`, `in`, `ru`.
          example: us
        - name: service
          in: query
          required: true
          schema:
            type: string
          description: Service identifier (lowercase). e.g. `telegram`, `whatsapp`, `instagram`.
          example: telegram
      responses:
        "200":
          description: Number reserved successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "402":
          $ref: '#/components/responses/InsufficientCredits'
        "404":
          $ref: '#/components/responses/NotFound'
        "429":
          $ref: '#/components/responses/RateLimited'

  /api/v2/getStatus:
    get:
      operationId: getStatus
      summary: Get SMS status for an order
      description: |
        Polls the status of an active order. Call this every 5–10 seconds after placing an order.
        Once `status` is `"sms received"`, read the verification code from `sms_code`.
        Stop polling when status is `sms received`, `retry received`, `expired`, or `canceled`.
      tags: [Orders]
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Your API key.
        - name: orderId
          in: query
          required: true
          schema:
            type: string
          description: The `orderId` returned by `/api/v2/order`.
          example: hold_8Kp2
      responses:
        "200":
          description: Status returned successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'
        "429":
          $ref: '#/components/responses/RateLimited'

  /api/v2/cancelOrder:
    get:
      operationId: cancelOrder
      summary: Cancel an active order
      description: |
        Cancels an order that is currently in HOLD status.
        No charge is applied if no SMS was received before cancellation.
      tags: [Orders]
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Your API key.
        - name: orderId
          in: query
          required: true
          schema:
            type: string
          description: The `orderId` of the active order to cancel.
          example: hold_8Kp2
      responses:
        "200":
          description: Order canceled successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Order canceled successfully
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'
        "429":
          $ref: '#/components/responses/RateLimited'

  /api/v2/retryOrder:
    get:
      operationId: retryOrder
      summary: Request a second SMS on a completed order
      description: |
        Requests another SMS delivery on an order that has already received one.
        Only available when `is_retriable` is `true` in the status response.
        After calling retry, poll `/api/v2/getStatus` again — the new code arrives as `"retry received"`.
      tags: [Orders]
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Your API key.
        - name: orderId
          in: query
          required: true
          schema:
            type: string
          description: The `orderId` of a completed order with `is_retriable` true.
          example: hold_8Kp2
      responses:
        "200":
          description: Retry requested.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "Retry requested — new SMS on the way"
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'
        "429":
          $ref: '#/components/responses/RateLimited'

  /api/v2/getBalance:
    get:
      operationId: getBalance
      summary: Get account balance
      description: |
        Returns your current credit balance, the amount held for active orders,
        and the net available balance. All integer values are in cents (USD).
      tags: [Account]
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Your API key.
      responses:
        "200":
          description: Balance returned successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BalanceResponse'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "429":
          $ref: '#/components/responses/RateLimited'

  /api/getOrders:
    get:
      operationId: getOrders
      summary: List order history
      description: Returns your order history, optionally filtered by status.
      tags: [Account]
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Your API key.
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [HOLD, DONE, CANCELED]
          description: Filter by order status. Omit to return all orders.
      responses:
        "200":
          description: Order list returned successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/OrderHistory'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "429":
          $ref: '#/components/responses/RateLimited'

  /api/v2/getPrice:
    get:
      operationId: getPrice
      summary: Get price for a country/service combination
      description: |
        Returns the current price, availability, and success rate for a specific
        country and service before placing an order. Requires authentication.
      tags: [Pricing]
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Your API key.
        - name: country
          in: query
          required: true
          schema:
            type: string
          description: ISO country code (lowercase).
          example: us
        - name: service
          in: query
          required: true
          schema:
            type: string
          description: Service identifier (lowercase).
          example: telegram
      responses:
        "200":
          description: Price data returned successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PriceResponse'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'

  /api/v2/getPrices:
    get:
      operationId: getPrices
      summary: Get prices for a service across all countries
      description: |
        Returns the price and availability for a single service across every
        country in one request, so you don't have to call `getPrice` per country.
        Countries with no numbers in stock are omitted, and results are sorted
        cheapest-first. Requires authentication.
      tags: [Pricing]
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Your API key.
        - name: service
          in: query
          required: true
          schema:
            type: string
          description: Service identifier (lowercase).
          example: amazon
      responses:
        "200":
          description: Per-country prices returned successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PricesByCountry'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "502":
          description: Upstream pricing provider failed.

  /api/public/getPrice:
    get:
      operationId: getPublicPrice
      summary: Get price (no auth)
      description: |
        Returns live pricing for a country/service combination.
        No API key required — suitable for displaying prices to unauthenticated users.
      tags: [Pricing]
      security: []
      parameters:
        - name: country
          in: query
          required: true
          schema:
            type: string
          example: us
        - name: service
          in: query
          required: true
          schema:
            type: string
          example: telegram
      responses:
        "200":
          description: Price data returned successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PriceResponse'
        "404":
          $ref: '#/components/responses/NotFound'

  /api/v2/getCountries:
    get:
      operationId: getCountries
      summary: List available countries
      description: Returns all countries with available virtual numbers. No authentication required.
      tags: [Reference]
      security: []
      responses:
        "200":
          description: Country list returned successfully.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Country'

  /api/v2/getServices:
    get:
      operationId: getServices
      summary: List available services
      description: Returns all services that support SMS verification. No authentication required.
      tags: [Reference]
      security: []
      responses:
        "200":
          description: Service list returned successfully.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Service'

tags:
  - name: Orders
    description: Place, monitor, cancel, and retry virtual number orders.
  - name: Account
    description: Balance and order history.
  - name: Pricing
    description: Check prices and availability before ordering.
  - name: Reference
    description: Lists of supported countries and services.
