Skip to content

Hand the User Agent Inspector API a User-Agent string and it hands back the browser, the operating system, the device, and whether it's a bot. This page shows the call, what comes back for real strings, and where the string itself stops telling the truth.

The call

One parameter, user_agent. Let curl do the URL encoding, the string is full of spaces, slashes and brackets:

curl -G "https://apixies.io/api/v1/inspect-user-agent" \
     -H "X-API-Key: YOUR_API_KEY" \
     --data-urlencode "user_agent=Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1"
{
  "status": "success",
  "data": {
    "is_bot": false,
    "device": { "family": "iPhone", "model": "iPhone", "brand": "Apple" },
    "os": { "family": "iOS", "major": "18", "minor": "5", "patch": null },
    "browser": { "family": "Mobile Safari", "major": "18", "minor": "5", "patch": null }
  }
}

You need a key. Sign up, create one in the dashboard, and you get 75 requests a day. Version numbers come back as strings, and anything the parser can't find is null.

You can also paste a string into the User Agent Parser tool and skip the key.

What comes back for real strings

I ran current browsers through it. Versions are shown as major.minor.

Sent browser os device.family
Chrome 140, Windows Chrome 140.0 Windows 10 Other
Chrome 140, macOS Chrome 140.0 Mac OS X 10.15 Mac
Firefox 143, Windows Firefox 143.0 Windows 10 Other
Firefox 128, Linux Firefox 128.0 Linux Other
Safari 18.5, macOS Safari 18.5 Mac OS X 10.15 Mac
Safari, iPhone Mobile Safari 18.5 iOS 18.5 iPhone
Safari, iPad Mobile Safari 17.6 iOS 17.6 iPad
Edge 140 Edge 140.0 Windows 10 Other
Opera 124 Opera 124.0 Windows 10 Other
Samsung Internet 28, Galaxy S24 Samsung Internet 28.0 Android 14 Samsung SM-S921B
Chrome 116, Pixel 7 Chrome Mobile 116.0 Android 13 Pixel 7
Instagram in-app, iPhone Instagram 340.0 iOS 17.5 iPhone
Facebook in-app, Galaxy A54 Facebook 471.0 Android 14 Samsung SM-A546B
Internet Explorer 11 IE 11.0 Windows 10 Other

It gets the hard ones right. Edge and Opera both carry Chrome/140 in their string and aren't mistaken for Chrome. The in-app browsers show up as Instagram and Facebook, which matters, because those webviews are where the odd layout bugs live.

Three things to know before you build on it.

There's no "desktop" or "mobile" field. device.family is a device name. On Windows and Linux it's Other, on a Mac it's Mac, on phones it's the model. If you want a device class, make one yourself:

function deviceClass(data) {
  if (data.is_bot) return "bot";
  if (data.device.family === "iPad") return "tablet";
  if (data.os.family === "iOS" || data.browser.family.includes("Mobile")) return "mobile";
  if (data.os.family === "Android") return "mobile";
  return "desktop";
}

That puts Android tablets under mobile. Their strings don't say "tablet" anywhere, so nothing can do better from the header alone.

Browser names are the parser's names. It's Mobile Safari, not Safari. Chrome Mobile, not Chrome. Mac OS X, not macOS. IE. Match on what you get back, not on what you'd call it.

Junk doesn't fail. A string that isn't a User-Agent comes back as a success with Other everywhere and is_bot: false. An empty string, or one over 1,024 characters, is a 422.

What the string can't tell you

Look at the OS column again. Windows 10, every time, and macOS 10.15 for a Mac that's years past that. It's not a parser bug. The browsers stopped saying.

Chrome froze those parts of its User-Agent a few years ago, on purpose, to make fingerprinting harder. Desktop Chrome always claims Windows 10 or macOS 10.15.7, and its version is always major.0.0.0. On Android it goes further. This is what a current phone running Chrome sends:

Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36
{
  "device": { "family": "K", "model": "K", "brand": "Generic_Android" },
  "os": { "family": "Android", "major": "10", "minor": null, "patch": null },
  "browser": { "family": "Chrome Mobile", "major": "140", "minor": "0", "patch": "0" }
}

Android 10 and a model called K, whatever phone it really is. Safari and Firefox pin the macOS version the same way. The Pixel 7 row in the table is an older, unreduced string, which is why it still has a model.

So trust the browser family and the major version. Trust the OS family. Treat the OS version and the Android model as missing for modern Chrome, even when there's a value in the field. Samsung Internet and the in-app browsers still send real models.

Bots

is_bot comes from a separate crawler list, not from the browser parser:

Sent is_bot browser.family
Googlebot/2.1 true Googlebot
bingbot/2.0 true bingbot
GPTBot/1.2 true GPTBot
curl/8.5.0 true curl
python-requests/2.32.3 true Python Requests
okhttp/4.12.0 true okhttp
PostmanRuntime/7.43.0 true Other

HTTP libraries count as bots. And since the two lists are separate, a string can be flagged as a bot while the family stays Other, like Postman here. Check is_bot first and use the family as a label.

It only reads the header, and anyone can write a header. The bot detection guide covers what to do about that.

In code

JavaScript

async function parseUserAgent(userAgent) {
  const params = new URLSearchParams({ user_agent: userAgent });
  const res = await fetch(`https://apixies.io/api/v1/inspect-user-agent?${params}`, {
    headers: { "X-API-Key": process.env.APIXIES_API_KEY },
  });
  const body = await res.json();

  if (body.status !== "success") {
    throw new Error(`${body.code}: ${body.message}`);
  }
  return body.data;
}

const ua = await parseUserAgent(
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0"
);
console.log(`${ua.browser.family} ${ua.browser.major} on ${ua.os.family}`);
// Edge 140 on Windows

Python

import os
import requests

def parse_user_agent(user_agent):
    res = requests.get(
        "https://apixies.io/api/v1/inspect-user-agent",
        params={"user_agent": user_agent},
        headers={"X-API-Key": os.environ["APIXIES_API_KEY"]},
        timeout=10,
    )
    body = res.json()
    if body["status"] != "success":
        raise RuntimeError(f"{body['code']}: {body['message']}")
    return body["data"]

ua = parse_user_agent(
    "Mozilla/5.0 (Linux; Android 14; SM-S921B) AppleWebKit/537.36 (KHTML, like Gecko) "
    "SamsungBrowser/28.0 Chrome/130.0.0.0 Mobile Safari/537.36"
)
print(f"{ua['browser']['family']} {ua['browser']['major']} on {ua['device']['family']}")
# Samsung Internet 28 on Samsung SM-S921B

PHP

function parseUserAgent(string $userAgent): array
{
    $context = stream_context_create(['http' => [
        'header' => 'X-API-Key: ' . getenv('APIXIES_API_KEY'),
        'ignore_errors' => true,
    ]]);

    $body = json_decode(file_get_contents(
        'https://apixies.io/api/v1/inspect-user-agent?' . http_build_query(['user_agent' => $userAgent]),
        false,
        $context
    ), true);

    if ($body['status'] !== 'success') {
        throw new RuntimeException("{$body['code']}: {$body['message']}");
    }

    return $body['data'];
}

$ua = parseUserAgent($_SERVER['HTTP_USER_AGENT'] ?? '');
echo $ua['browser']['family'] . ($ua['is_bot'] ? ' (bot)' : '') . "\n";

One habit worth having: don't call this once per page view. A site sees the same few hundred strings all day, so look each one up once and keep the answer. With 75 requests a day you'll want to anyway.

Next steps

Try the User Agent Inspector API

Free tier is for development & small projects. 75 requests/day with a registered account.

cookies

We use analytics cookies to see how the site gets used. Nothing loads until you accept. Privacy policy