{
  "openapi": "3.1.0",
  "info": {
    "title": "OTPVN API",
    "version": "1.0",
    "description": "OTPVN's API for renting numbers to receive OTPs. Answers are JSON, errors follow problem+json. Every request needs the API token from the account page, sent in the Authorization header."
  },
  "externalDocs": {
    "url": "https://otpvn.com/en/api-docs"
  },
  "servers": [
    {
      "url": "https://api.otpvn.com"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "API token (32 characters) from the account page"
      }
    },
    "parameters": {
      "lang": {
        "name": "lang",
        "in": "query",
        "required": false,
        "description": "Language of the error detail: vi (default), en or zh. The X-Lang header works too.",
        "schema": {
          "type": "string",
          "enum": [
            "vi",
            "en",
            "zh"
          ],
          "default": "vi"
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string",
            "description": "Stable error code to branch on"
          },
          "detail": {
            "type": "string",
            "description": "The reason, in the language of the request"
          }
        }
      }
    }
  },
  "paths": {
    "/v1/balance": {
      "get": {
        "operationId": "balance",
        "summary": "Get account balance",
        "description": "The account's current balance.",
        "parameters": [
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "balance": {
                      "type": "integer",
                      "description": "Current balance, in VND"
                    }
                  }
                },
                "example": {
                  "balance": 186500
                }
              }
            }
          },
          "401": {
            "description": "invalid_token: The token is missing, wrong or no longer valid; api_disabled: The account has no API access",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "too_many_requests: Too fast: over the requests per second of the account, or too many rentals in a row; wait for Retry-After and try again",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/carriers": {
      "get": {
        "operationId": "carriers",
        "summary": "List carriers",
        "description": "Carrier names for the carriers field when renting. Vietnam: MOBIFONE, VINAPHONE, VIETTEL, VIETNAMOBILE, ITELECOM, WINTEL, GTEL. Laos: UNITEL, ETL, BEELINE, LAOTEL. The list also has carriers of other markets that are not used yet.",
        "parameters": [
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer",
                        "description": "Carrier id"
                      },
                      "name": {
                        "type": "string",
                        "description": "Carrier name, used in the carriers field"
                      }
                    }
                  }
                },
                "example": [
                  {
                    "id": 1,
                    "name": "MOBIFONE"
                  },
                  {
                    "id": 2,
                    "name": "VINAPHONE"
                  },
                  {
                    "id": 3,
                    "name": "VIETTEL"
                  },
                  {
                    "id": 4,
                    "name": "VIETNAMOBILE"
                  },
                  {
                    "id": 5,
                    "name": "ITELECOM"
                  },
                  {
                    "id": 6,
                    "name": "VODAFONE_RO"
                  },
                  {
                    "id": 7,
                    "name": "WINTEL"
                  },
                  {
                    "id": 8,
                    "name": "METFONE"
                  },
                  {
                    "id": 9,
                    "name": "UNITEL"
                  },
                  {
                    "id": 10,
                    "name": "ETL"
                  },
                  {
                    "id": 11,
                    "name": "BEELINE"
                  },
                  {
                    "id": 12,
                    "name": "LAOTEL"
                  },
                  {
                    "id": 13,
                    "name": "GTEL"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "invalid_token: The token is missing, wrong or no longer valid; api_disabled: The account has no API access",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "too_many_requests: Too fast: over the requests per second of the account, or too many rentals in a row; wait for Retry-After and try again",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/services": {
      "get": {
        "operationId": "services",
        "summary": "List services",
        "description": "Id, name and price of the services open to the API. Prices can change, so fetch the list again from time to time.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "vn: Vietnam (default) · la: Laos",
            "schema": {
              "type": "string"
            },
            "example": "vn"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer",
                        "description": "Service id, used as serviceId"
                      },
                      "name": {
                        "type": "string",
                        "description": "Service name"
                      },
                      "price": {
                        "type": "integer",
                        "description": "Price per rental, in VND"
                      }
                    }
                  }
                },
                "example": [
                  {
                    "id": 7,
                    "name": "Facebook",
                    "price": 800
                  },
                  {
                    "id": 1,
                    "name": "Momo",
                    "price": 350
                  }
                ]
              }
            }
          },
          "401": {
            "description": "invalid_token: The token is missing, wrong or no longer valid; api_disabled: The account has no API access",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "too_many_requests: Too fast: over the requests per second of the account, or too many rentals in a row; wait for Retry-After and try again",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rentals": {
      "post": {
        "operationId": "rent",
        "summary": "Rent",
        "description": "Rents a new number for the service. The price is charged right away and refunded automatically if no code arrives within 4–7 minutes.",
        "parameters": [
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "serviceId"
                ],
                "properties": {
                  "serviceId": {
                    "type": "integer",
                    "description": "Service id, from the service list"
                  },
                  "country": {
                    "type": "string",
                    "description": "la to rent a Lao SIM · omit to rent a Vietnamese SIM"
                  },
                  "carriers": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Carriers you want. Leave it out to take any carrier of that country."
                  },
                  "includePrefixes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Prefixes you want, with the leading 0 (e.g. 096). Leave it out to take any prefix."
                  },
                  "excludePrefixes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Prefixes you don't want, with the leading 0 (e.g. 097)."
                  }
                }
              },
              "example": {
                "serviceId": 1,
                "carriers": [
                  "VINAPHONE",
                  "VIETTEL"
                ],
                "includePrefixes": [
                  "096",
                  "097",
                  "098"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer",
                      "description": "Rental id, used to get the OTP"
                    },
                    "phone": {
                      "type": "string",
                      "description": "Full number as stored, used for the phone field when renting again"
                    },
                    "phoneLocal": {
                      "type": "string",
                      "description": "The number to enter in the app, without the leading 0 or country code"
                    },
                    "countryIso": {
                      "type": "string",
                      "description": "Country code: VN or LA"
                    },
                    "countryCode": {
                      "type": "string",
                      "description": "International dialling code: 84 or 856"
                    },
                    "balance": {
                      "type": "integer",
                      "description": "Balance left after the charge, in VND"
                    }
                  }
                },
                "example": {
                  "id": 1234122,
                  "phone": "0987654321",
                  "phoneLocal": "987654321",
                  "countryIso": "VN",
                  "countryCode": "84",
                  "balance": 185300
                }
              }
            }
          },
          "400": {
            "description": "invalid_body: The request body is not valid JSON or a required field is missing; invalid_carrier: The carrier does not belong to the chosen country; invalid_prefix: The prefix does not belong to the chosen carriers",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "invalid_token: The token is missing, wrong or no longer valid; api_disabled: The account has no API access",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "api_not_supported: The service is not open to the API; insufficient_balance: Insufficient balance; out_of_stock: No matching number available, try again later; service_closed: The service is temporarily closed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "too_many_requests: Too fast: over the requests per second of the account, or too many rentals in a row; wait for Retry-After and try again; waiting_limit: Too many rentals waiting for a code at once; detail gives the limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "maintenance: Short system maintenance, try again in a few minutes; busy: System busy, try again later",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "history",
        "summary": "Rental history",
        "description": "Rentals filtered by service, status and date range, newest first. All 5 parameters are required.",
        "parameters": [
          {
            "name": "serviceId",
            "in": "query",
            "required": true,
            "description": "Service id",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "status",
            "in": "query",
            "required": true,
            "description": "0: waiting for SMS · 1: completed · 2: expired",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "limit",
            "in": "query",
            "required": true,
            "description": "Maximum number of records",
            "schema": {
              "type": "integer"
            },
            "example": 100
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "description": "Start date, yyyy-MM-dd",
            "schema": {
              "type": "string"
            },
            "example": "2026-09-01"
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "description": "End date, yyyy-MM-dd",
            "schema": {
              "type": "string"
            },
            "example": "2026-09-27"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer",
                        "description": "Rental id"
                      },
                      "serviceId": {
                        "type": "integer",
                        "description": "Service id"
                      },
                      "serviceName": {
                        "type": "string",
                        "description": "Service name"
                      },
                      "price": {
                        "type": "integer",
                        "description": "Price per rental, in VND"
                      },
                      "status": {
                        "type": "integer",
                        "description": "0: waiting for SMS · 1: completed · 2: expired, refunded"
                      },
                      "content": {
                        "type": "string",
                        "description": "Message text. For voice-call codes, a link to the recording. Absent until a message arrives"
                      },
                      "code": {
                        "type": "string",
                        "description": "The extracted OTP code. Absent until a code arrives"
                      },
                      "isAudio": {
                        "type": "boolean",
                        "description": "true if the code came by phone call"
                      },
                      "phone": {
                        "type": "string",
                        "description": "Full number as stored, used for the phone field when renting again"
                      },
                      "phoneLocal": {
                        "type": "string",
                        "description": "The number to enter in the app, without the leading 0 or country code"
                      },
                      "countryIso": {
                        "type": "string",
                        "description": "Country code: VN or LA"
                      },
                      "countryCode": {
                        "type": "string",
                        "description": "International dialling code: 84 or 856"
                      },
                      "createdAt": {
                        "type": "string",
                        "description": "Rental time, ISO 8601 with the Vietnam offset (+07:00)"
                      }
                    }
                  }
                },
                "example": [
                  {
                    "id": 1234101,
                    "serviceId": 1,
                    "serviceName": "Momo",
                    "price": 600,
                    "status": 1,
                    "content": "https://audio.otpvn.com/20260927101215_7f3c2a.wav",
                    "code": "731905",
                    "isAudio": true,
                    "phone": "0987654321",
                    "phoneLocal": "987654321",
                    "countryIso": "VN",
                    "countryCode": "84",
                    "createdAt": "2026-09-27T10:12:03.51+07:00"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "invalid_query: A query parameter is wrong or missing",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "invalid_token: The token is missing, wrong or no longer valid; api_disabled: The account has no API access",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "too_many_requests: Too fast: over the requests per second of the account, or too many rentals in a row; wait for Retry-After and try again",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rentals/rerent": {
      "post": {
        "operationId": "rerent",
        "summary": "Rent a number again",
        "description": "Rent the same number again for the same service, e.g. when an app asks to verify again. Only within about 15–30 minutes of the previous rental, and while the SIM is still on the system.",
        "parameters": [
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "serviceId",
                  "phone"
                ],
                "properties": {
                  "serviceId": {
                    "type": "integer",
                    "description": "Service id, from the service list"
                  },
                  "phone": {
                    "type": "string",
                    "description": "The phone field of the previous rental"
                  }
                }
              },
              "example": {
                "serviceId": 1,
                "phone": "0987654321"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer",
                      "description": "Rental id, used to get the OTP"
                    },
                    "phone": {
                      "type": "string",
                      "description": "Full number as stored, used for the phone field when renting again"
                    },
                    "phoneLocal": {
                      "type": "string",
                      "description": "The number to enter in the app, without the leading 0 or country code"
                    },
                    "countryIso": {
                      "type": "string",
                      "description": "Country code: VN or LA"
                    },
                    "countryCode": {
                      "type": "string",
                      "description": "International dialling code: 84 or 856"
                    },
                    "balance": {
                      "type": "integer",
                      "description": "Balance left after the charge, in VND"
                    }
                  }
                },
                "example": {
                  "id": 1234190,
                  "phone": "0987654321",
                  "phoneLocal": "987654321",
                  "countryIso": "VN",
                  "countryCode": "84",
                  "balance": 184700
                }
              }
            }
          },
          "400": {
            "description": "invalid_body: The request body is not valid JSON or a required field is missing",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "invalid_token: The token is missing, wrong or no longer valid; api_disabled: The account has no API access",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "api_not_supported: The service is not open to the API; insufficient_balance: Insufficient balance; service_closed: The service is temporarily closed; service_unavailable: The service cannot be rented again yet, contact support; rerent_unavailable: The number to rent again is not available right now; rerent_forbidden: Cannot rent again: more than 15–30 minutes have passed since the previous rental, or the SIM is no longer on the system; rerent_taken: Someone else is renting this number; out_of_stock: No matching number available, try again later",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "too_many_requests: Too fast: over the requests per second of the account, or too many rentals in a row; wait for Retry-After and try again; waiting_limit: Too many rentals waiting for a code at once; detail gives the limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "maintenance: Short system maintenance, try again in a few minutes; busy: System busy, try again later",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rentals/{id}": {
      "get": {
        "operationId": "rental",
        "summary": "Get the OTP of a rental",
        "description": "Status and OTP of a rental. Call it every 5 seconds until status is no longer 0.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id returned when renting",
            "schema": {
              "type": "integer"
            },
            "example": 1234122
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer",
                      "description": "Rental id"
                    },
                    "serviceId": {
                      "type": "integer",
                      "description": "Service id"
                    },
                    "serviceName": {
                      "type": "string",
                      "description": "Service name"
                    },
                    "price": {
                      "type": "integer",
                      "description": "Price per rental, in VND"
                    },
                    "status": {
                      "type": "integer",
                      "description": "0: waiting for SMS · 1: completed · 2: expired, refunded"
                    },
                    "content": {
                      "type": "string",
                      "description": "Message text. For voice-call codes, a link to the recording. Absent until a message arrives"
                    },
                    "code": {
                      "type": "string",
                      "description": "The extracted OTP code. Absent until a code arrives"
                    },
                    "isAudio": {
                      "type": "boolean",
                      "description": "true if the code came by phone call"
                    },
                    "phone": {
                      "type": "string",
                      "description": "Full number as stored, used for the phone field when renting again"
                    },
                    "phoneLocal": {
                      "type": "string",
                      "description": "The number to enter in the app, without the leading 0 or country code"
                    },
                    "countryIso": {
                      "type": "string",
                      "description": "Country code: VN or LA"
                    },
                    "countryCode": {
                      "type": "string",
                      "description": "International dialling code: 84 or 856"
                    },
                    "createdAt": {
                      "type": "string",
                      "description": "Rental time, ISO 8601 with the Vietnam offset (+07:00)"
                    }
                  }
                },
                "example": {
                  "id": 1234122,
                  "serviceId": 1,
                  "serviceName": "Momo",
                  "price": 600,
                  "status": 1,
                  "content": "486460 la ma xac thuc Momo cua ban",
                  "code": "486460",
                  "isAudio": false,
                  "phone": "0987654321",
                  "phoneLocal": "987654321",
                  "countryIso": "VN",
                  "countryCode": "84",
                  "createdAt": "2026-09-27T10:15:24.88+07:00"
                }
              }
            }
          },
          "401": {
            "description": "invalid_token: The token is missing, wrong or no longer valid; api_disabled: The account has no API access",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "rental_not_found: The account has no such rental",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "too_many_requests: Too fast: over the requests per second of the account, or too many rentals in a row; wait for Retry-After and try again",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  }
}