Quickstart
- Get a free API key — 1,000 credits, no card.
- Send a user-agent string. One request = one credit.
curl https://useragentparser.xyz/v1/parse \
-H "X-Api-Key: $UAP_KEY" \
-H "Content-Type: application/json" \
-d '{"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1"}'
const res = await fetch("https://useragentparser.xyz/v1/parse", {
method: "POST",
headers: { "X-Api-Key": process.env.UAP_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ userAgent: req.headers["user-agent"] }),
});
const { browser, os, device, bot } = await res.json();
import os, requests
r = requests.post(
"https://useragentparser.xyz/v1/parse",
headers={"X-Api-Key": os.environ["UAP_KEY"]},
json={"userAgent": user_agent},
timeout=5,
)
info = r.json()
Authentication
Send your API key in the X-Api-Key header (Authorization: Bearer uap_… works too). Get a key on the account page — it is shown once; you can generate a new one at any time, and the previous key stops working within a minute.
Keep the key on your server. If you call the API from a browser, anyone can read the key from your page and spend your credits.
Parse one user agent
POST /v1/parse
Content-Type: application/json
X-Api-Key: uap_…
{"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1"}
Costs 1 credit. The API never reads your own User-Agent header — always pass the string you want parsed.
There is deliberately no GET variant: URLs end up in request logs, and we promise not to log the strings you send. A GET returns 405 explaining this — use POST with a JSON body.
Response fields
{
"browser": { "name": "Mobile Safari", "version": "17.5", "major": "17", "type": null },
"engine": { "name": "WebKit", "version": "605.1.15" },
"os": { "name": "iOS", "version": "17.5" },
"device": { "type": "mobile", "vendor": "Apple", "model": "iPhone" },
"cpu": { "architecture": null },
"bot": { "isBot": false, "isAIBot": false }
}
| Field | Type | Values |
|---|---|---|
browser.name | string | null | e.g. Chrome, Mobile Safari, Edge, GPTBot, curl |
browser.version | string | null | Full version as written in the string, e.g. 131.0.0.0 |
browser.major | string | null | Major version only, e.g. 131 — handy for grouping |
browser.type | string | null | crawler, cli, library, fetcher, inapp, email, mediaplayer — or null for an ordinary browser |
engine.name | string | null | Rendering engine, e.g. Blink, WebKit, Gecko |
engine.version | string | null | e.g. 605.1.15 |
os.name | string | null | e.g. Windows, macOS, iOS, Android, Linux |
os.version | string | null | e.g. 17.5, 14, 10 |
device.type | string | null | mobile, tablet, smarttv, console, wearable, xr, embedded — or null. Desktop browsers report no device type, so null usually means desktop. |
device.vendor | string | null | e.g. Apple, Samsung, Google |
device.model | string | null | e.g. iPhone, SM-S921B, Pixel 8 |
cpu.architecture | string | null | e.g. amd64, arm64, ia32 — when the string reveals it |
bot.isBot | boolean | true for crawlers, CLIs, HTTP libraries and fetchers |
bot.isAIBot | boolean | true for AI crawlers and assistants (GPTBot, ClaudeBot, PerplexityBot, …) |
Every key is always present; values the string does not reveal are null. We may add new fields in future; we will not remove or rename existing ones within /v1.
Each successful response carries X-Credits-Remaining: your approximate remaining balance.
Parse up to 100 at once
POST /v1/parse/batch
{"userAgents": ["Mozilla/5.0 (…)", "curl/8.5.0", ""]}
→ {"results": [ {…parsed…}, {…parsed…}, {"error": {"code": "invalid_request", "message": "…"}} ]}
Results come back in input order. Each valid string costs 1 credit; invalid entries return an inline error and are free.
Errors
Errors use HTTP status codes and a JSON body: {"error": {"code": "…", "message": "…"}}.
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_request | Missing/empty user agent, or longer than 2,048 characters |
| 400 | — | Malformed JSON body (rejected before it reaches the API; the body may not be JSON) |
| 401 | unauthorized | Missing or invalid API key |
| 402 | insufficient_credits | Not enough credits — top up; the same key keeps working afterwards |
| 405 | method_not_allowed | GET with the user agent in the URL — use POST |
| 413 | payload_too_large | More than 100 items in a batch, or body over 256 KB |
| 5xx | internal | Our fault — safe to retry with backoff; not charged |
Credits
- 1 credit = 1 parsed user agent. New accounts get 1,000 free credits.
- Each purchase is valid for 24 months. Credits expiring soonest are used first.
- No subscription and no automatic charges: when credits run out, requests return 402 until you top up.
- Balance accounting is near-real-time; under heavy parallel load you may briefly be served a few requests past zero — those are on us.
- Packs start at €5 for 25,000 credits; see pricing. Prices exclude tax; taxes may apply depending on your location and are calculated at checkout. Every purchase comes with an invoice.
Server-side Google Tag Manager
Streaming events from server-side GTM to BigQuery or a warehouse? Our free sGTM variable template adds browser, OS, device and bot fields to every event — no code, no parser library in your container.
Download the sGTM template (.tpl)
- In your server container: Templates → Variable templates → New → ⋮ → Import, choose the downloaded file, Save.
- Create a Constant variable holding your API key, e.g.
{{UAP key}}. - Variables → New → the UserAgent Parser template you imported: API key
{{UAP key}}, pick what to return — e.g. Device category, Browser name, Is AI crawler or the full object. - Use the variable in your BigQuery / warehouse tag (or any tag) like any other value.
| Setting | Default | Notes |
|---|---|---|
| User-agent source | event data user_agent | Set by the GA4 client. Alternatively the incoming request's User-Agent header or a custom value. |
| Return | full object | Or one field: browser.name, os.name, device.type, bot.isAIBot, … plus Device category (desktop / mobile / tablet / bot / …). |
| Cache | on, 24 h | Results are cached per user-agent on each sGTM instance, so returning browsers cost one credit per instance per day — not one per event. |
| Timeout | 2,000 ms | On timeout, errors or an empty balance the variable is simply undefined; your event is never blocked. |
Each variable that misses the cache makes its own request. If you need several fields per event, use one variable returning the full object where your tag accepts objects, or rely on the cache — once a user-agent is cached, every further field for it is free.
n8n, Make and Zapier
No-code tools can’t install a parser library, but they can make an HTTP request. Use their generic HTTP step:
Method POST
URL https://useragentparser.xyz/v1/parse
Header X-Api-Key: ‹your key, stored as a credential›
Body type JSON
Body { "userAgent": ‹user-agent field from the trigger› }
Then branch on the result, e.g. device.type equals mobile, or bot.isAIBot is true. In n8n this is the HTTP Request node, in Make the HTTP → Make a request module, in Zapier Webhooks by Zapier → POST.
Browsers and CORS
The parse endpoints send CORS headers for any origin, so they can be called from a browser — but see the note about exposing your key under Authentication.
What we keep
We do not store or log the user-agent strings you send: they are parsed in memory and only a request count is kept against your credits. The API accepts them in POST bodies only, never in URLs, so they never land in URL logs. Details in the privacy policy.