{
  "info": {
    "name": "Turonay SMS API (handler_api compatible)",
    "description": "Postman collection for the Turonay reseller API.\n\n1. Set the collection variable `apiKey` (profile page at https://turonay.com).\n2. Run `getNumber` - it stores `activationId` and `phoneNumber` automatically.\n3. Run `getStatus` until `STATUS_OK:<code>`, then `setStatus 6`. Run `setStatus 8` to cancel (free until a code arrives).\n\nDesigned to be compatible with the widely used handler_api protocol; in most integrations changing `baseUrl` and the API key is enough (there are known differences, so verify service codes, country ids and prices with `getPrices`). You are billed only when the SMS code arrives; the price is reserved at purchase. Docs: https://turonay.com/sms-onay-api",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://turonay.com/stubs/handler_api.php",
      "type": "string",
      "description": "Equivalent alternative: https://turonay.com/api/"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "secret",
      "description": "Your Turonay API key"
    },
    {
      "key": "service",
      "value": "wa",
      "type": "string"
    },
    {
      "key": "country",
      "value": "62",
      "type": "string"
    },
    {
      "key": "activationId",
      "value": "",
      "type": "string"
    },
    {
      "key": "phoneNumber",
      "value": "",
      "type": "string"
    },
    {
      "key": "smsCode",
      "value": "",
      "type": "string"
    },
    {
      "key": "balance",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "getBalance",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
              "const body = pm.response.text().trim();",
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
              "",
              "pm.test('ACCESS_BALANCE:<amount>', () => pm.expect(body).to.match(/^ACCESS_BALANCE:\\d+(\\.\\d+)?$/));",
              "const m = body.match(/^ACCESS_BALANCE:(.+)$/);",
              "if (m) { pm.collectionVariables.set('balance', m[1]); }"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "Available balance (TRY) after reservations. Response: ACCESS_BALANCE:<amount>.",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getBalance",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getBalance"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "getPrices",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "const data = pm.response.json();",
              "pm.test('JSON object country -> service -> {cost,count}', () => {",
              "  pm.expect(data).to.be.an('object');",
              "  Object.values(data).forEach(svcs => Object.values(svcs).forEach(e => {",
              "    pm.expect(e).to.have.property('cost');",
              "    pm.expect(e).to.have.property('count');",
              "  }));",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "Prices you will pay (TRY, after tier discount) and stock. Optional service/country filters. JSON: {country:{service:{cost,count}}}.",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getPrices",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getPrices"
            },
            {
              "key": "service",
              "value": "{{service}}",
              "disabled": true,
              "description": "Optional filter"
            },
            {
              "key": "country",
              "value": "{{country}}",
              "disabled": true,
              "description": "Optional filter"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "getCountries",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('JSON object', () => pm.expect(pm.response.json()).to.be.an('object'));"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "Countries you can actually buy from. JSON: {\"62\":{\"id\":62,\"eng\":\"Turkey\",\"visible\":1}}.",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getCountries",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getCountries"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "getNumbersStatus",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('JSON object', () => pm.expect(pm.response.json()).to.be.an('object'));"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "Stock per service for one country. country is required. JSON: {\"wa_0\":\"744716\"} (counts are strings).",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getNumbersStatus&country={{country}}",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getNumbersStatus"
            },
            {
              "key": "country",
              "value": "{{country}}"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "getOperators",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "const d = pm.response.json();",
              "pm.test('status=success', () => pm.expect(d.status).to.eql('success'));"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "Operators per country. Currently every country returns [\"any\"]. country is optional.",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getOperators",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getOperators"
            },
            {
              "key": "country",
              "value": "{{country}}",
              "disabled": true,
              "description": "Optional"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "getNumber",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
              "const body = pm.response.text().trim();",
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
              "",
              "// Success: ACCESS_NUMBER:<activationId>:<phone>",
              "const m = body.match(/^ACCESS_NUMBER:(\\d+):(\\d+)$/);",
              "if (m) {",
              "  pm.collectionVariables.set('activationId', m[1]);",
              "  pm.collectionVariables.set('phoneNumber', m[2]);",
              "  console.log('activationId=' + m[1] + ' phone=' + m[2]);",
              "}",
              "pm.test('ACCESS_NUMBER or a known error code', () => {",
              "  pm.expect(body).to.match(/^(ACCESS_NUMBER:\\d+:\\d+|NO_NUMBERS|NO_BALANCE|BAD_SERVICE|NO_PROVIDER|NO_CONNECTION|WRONG_MAX_PRICE(:\\d+(\\.\\d+)?)?)$/);",
              "});",
              "const w = body.match(/^WRONG_MAX_PRICE:(.+)$/);",
              "if (w) { console.log('Price is ' + w[1] + ' TRY, above your maxPrice. Nothing was purchased.'); }"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "Allocates a number and RESERVES its price on your balance. You are billed only when the SMS code arrives. Optional: operator (any), maxPrice (TRY; if the current price is higher you get WRONG_MAX_PRICE:<price> and no number is kept). The activation id is stored in {{activationId}} for the other requests.",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getNumber&service={{service}}&country={{country}}",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getNumber"
            },
            {
              "key": "service",
              "value": "{{service}}"
            },
            {
              "key": "country",
              "value": "{{country}}"
            },
            {
              "key": "operator",
              "value": "any",
              "disabled": true,
              "description": "Optional; any = no preference"
            },
            {
              "key": "maxPrice",
              "value": "15",
              "disabled": true,
              "description": "Optional price ceiling in TRY; 0 = no limit"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "getNumberV2",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
              "const body = pm.response.text().trim();",
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
              "",
              "let j = null;",
              "try { j = JSON.parse(body); } catch (e) { /* plain-text error code */ }",
              "if (j) {",
              "  pm.collectionVariables.set('activationId', String(j.activationId));",
              "  pm.collectionVariables.set('phoneNumber', String(j.phoneNumber));",
              "  pm.test('activationCost is a string, currency is 949', () => {",
              "    pm.expect(j.activationCost).to.be.a('string');",
              "    pm.expect(j.currency).to.eql(949);",
              "  });",
              "} else {",
              "  pm.test('plain-text error code', () => pm.expect(body).to.match(/^(NO_NUMBERS|NO_BALANCE|BAD_SERVICE|NO_PROVIDER|NO_CONNECTION|WRONG_MAX_PRICE(:.+)?)$/));",
              "}"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "Same as getNumber, but success is JSON (price, currency 949, expiry, operator). Errors are still plain-text codes.",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getNumberV2&service={{service}}&country={{country}}",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getNumberV2"
            },
            {
              "key": "service",
              "value": "{{service}}"
            },
            {
              "key": "country",
              "value": "{{country}}"
            },
            {
              "key": "operator",
              "value": "any",
              "disabled": true,
              "description": "Optional"
            },
            {
              "key": "maxPrice",
              "value": "15",
              "disabled": true,
              "description": "Optional, TRY"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "getStatus",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
              "const body = pm.response.text().trim();",
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
              "",
              "pm.test('known status', () => pm.expect(body).to.match(/^(STATUS_WAIT_CODE|STATUS_WAIT_RESEND|STATUS_WAIT_RETRY:.+|STATUS_OK:.+|STATUS_CANCEL|NO_ACTIVATION|NO_CONNECTION|NO_PROVIDER)$/));",
              "const ok = body.match(/^STATUS_OK:(.+)$/);",
              "if (ok) { pm.collectionVariables.set('smsCode', ok[1]); console.log('SMS code: ' + ok[1]); }"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "Poll every 3-5 s (never 1 s). STATUS_WAIT_CODE, STATUS_WAIT_RESEND, STATUS_WAIT_RETRY:<previous code>, STATUS_OK:<code>, STATUS_CANCEL. Billing is finalised when the code is first seen.",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getStatus&id={{activationId}}",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getStatus"
            },
            {
              "key": "id",
              "value": "{{activationId}}"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "getStatusV2",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
              "const body = pm.response.text().trim();",
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
              "",
              "let j = null;",
              "try { j = JSON.parse(body); } catch (e) { /* still waiting: plain text */ }",
              "if (j && j.sms) {",
              "  pm.collectionVariables.set('smsCode', j.sms.code);",
              "  pm.test('sms.code present', () => pm.expect(j.sms.code).to.be.a('string'));",
              "} else {",
              "  pm.test('plain-text status while waiting', () => pm.expect(body).to.match(/^(STATUS_|NO_)/));",
              "}"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "JSON {verificationType, sms:{dateTime,code,text}, call:null} once a code exists; otherwise the same plain text as getStatus.",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getStatusV2&id={{activationId}}",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getStatusV2"
            },
            {
              "key": "id",
              "value": "{{activationId}}"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "getActiveActivations",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "const d = pm.response.json();",
              "pm.test('success list or NO_ACTIVATIONS', () => {",
              "  if (d.status === 'success') { pm.expect(d.activeActivations).to.be.an('array'); }",
              "  else { pm.expect(d.error).to.eql('NO_ACTIVATIONS'); }",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "description": "Open activations (last 24 h, not cancelled) as JSON. {\"status\":\"error\",\"error\":\"NO_ACTIVATIONS\"} when none.",
        "url": {
          "raw": "{{baseUrl}}?api_key={{apiKey}}&action=getActiveActivations",
          "host": [
            "{{baseUrl}}"
          ],
          "query": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "description": "Your API key (profile page)."
            },
            {
              "key": "action",
              "value": "getActiveActivations"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "setStatus",
      "item": [
        {
          "name": "setStatus 1 - ready",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
                  "const body = pm.response.text().trim();",
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
                  "",
                  "pm.test('expected reply', () => pm.expect(body).to.match(/^(ACCESS_READY|BAD_STATUS|EARLY_CANCEL_DENIED|NO_ACTIVATION|NO_CONNECTION|NO_PROVIDER|STATUS_[A-Z_]+)$/));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "description": "Informational (SMS sent / number ready). No money moves. Response: ACCESS_READY.",
            "url": {
              "raw": "{{baseUrl}}?api_key={{apiKey}}&action=setStatus&id={{activationId}}&status=1",
              "host": [
                "{{baseUrl}}"
              ],
              "query": [
                {
                  "key": "api_key",
                  "value": "{{apiKey}}",
                  "description": "Your API key (profile page)."
                },
                {
                  "key": "action",
                  "value": "setStatus"
                },
                {
                  "key": "id",
                  "value": "{{activationId}}"
                },
                {
                  "key": "status",
                  "value": "1"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "setStatus 3 - another SMS",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
                  "const body = pm.response.text().trim();",
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
                  "",
                  "pm.test('expected reply', () => pm.expect(body).to.match(/^(ACCESS_RETRY_GET|BAD_STATUS|EARLY_CANCEL_DENIED|NO_ACTIVATION|NO_CONNECTION|NO_PROVIDER|STATUS_[A-Z_]+)$/));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "description": "Request another SMS on the same number. Free; you are billed only once. Response: ACCESS_RETRY_GET. Then poll getStatus: STATUS_WAIT_RETRY:<old> -> STATUS_OK:<new>.",
            "url": {
              "raw": "{{baseUrl}}?api_key={{apiKey}}&action=setStatus&id={{activationId}}&status=3",
              "host": [
                "{{baseUrl}}"
              ],
              "query": [
                {
                  "key": "api_key",
                  "value": "{{apiKey}}",
                  "description": "Your API key (profile page)."
                },
                {
                  "key": "action",
                  "value": "setStatus"
                },
                {
                  "key": "id",
                  "value": "{{activationId}}"
                },
                {
                  "key": "status",
                  "value": "3"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "setStatus 6 - complete",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
                  "const body = pm.response.text().trim();",
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
                  "",
                  "pm.test('expected reply', () => pm.expect(body).to.match(/^(ACCESS_ACTIVATION|BAD_STATUS|EARLY_CANCEL_DENIED|NO_ACTIVATION|NO_CONNECTION|NO_PROVIDER|STATUS_[A-Z_]+)$/));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "description": "Complete the activation. Valid only after a code arrived (otherwise BAD_STATUS). Response: ACCESS_ACTIVATION.",
            "url": {
              "raw": "{{baseUrl}}?api_key={{apiKey}}&action=setStatus&id={{activationId}}&status=6",
              "host": [
                "{{baseUrl}}"
              ],
              "query": [
                {
                  "key": "api_key",
                  "value": "{{apiKey}}",
                  "description": "Your API key (profile page)."
                },
                {
                  "key": "action",
                  "value": "setStatus"
                },
                {
                  "key": "id",
                  "value": "{{activationId}}"
                },
                {
                  "key": "status",
                  "value": "6"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "setStatus 8 - cancel",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
                  "const body = pm.response.text().trim();",
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
                  "",
                  "pm.test('expected reply', () => pm.expect(body).to.match(/^(ACCESS_CANCEL|BAD_STATUS|EARLY_CANCEL_DENIED|NO_ACTIVATION|NO_CONNECTION|NO_PROVIDER|STATUS_[A-Z_]+)$/));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "description": "Cancel. Without a code: ACCESS_CANCEL and the reserved amount is released. After a code arrived: EARLY_CANCEL_DENIED and no refund.",
            "url": {
              "raw": "{{baseUrl}}?api_key={{apiKey}}&action=setStatus&id={{activationId}}&status=8",
              "host": [
                "{{baseUrl}}"
              ],
              "query": [
                {
                  "key": "api_key",
                  "value": "{{apiKey}}",
                  "description": "Your API key (profile page)."
                },
                {
                  "key": "action",
                  "value": "setStatus"
                },
                {
                  "key": "id",
                  "value": "{{activationId}}"
                },
                {
                  "key": "status",
                  "value": "8"
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "POST variant - getNumber (form-encoded)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Turonay answers application errors with HTTP 200 and a plain-text code, so always check the body.",
              "const body = pm.response.text().trim();",
              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
              "pm.test('API key accepted', () => pm.expect(body).to.not.eql('BAD_KEY'));",
              "",
              "const m = body.match(/^ACCESS_NUMBER:(\\d+):(\\d+)$/);",
              "if (m) { pm.collectionVariables.set('activationId', m[1]); pm.collectionVariables.set('phoneNumber', m[2]); }"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [],
        "description": "Every action also accepts POST application/x-www-form-urlencoded; many handler_api clients use POST. Query string wins if a parameter appears in both.",
        "url": {
          "raw": "{{baseUrl}}",
          "host": [
            "{{baseUrl}}"
          ]
        },
        "body": {
          "mode": "urlencoded",
          "urlencoded": [
            {
              "key": "api_key",
              "value": "{{apiKey}}",
              "type": "text"
            },
            {
              "key": "action",
              "value": "getNumber",
              "type": "text"
            },
            {
              "key": "service",
              "value": "{{service}}",
              "type": "text"
            },
            {
              "key": "country",
              "value": "{{country}}",
              "type": "text"
            }
          ]
        }
      },
      "response": []
    }
  ]
}
