API documentation

One resource in, one structured signal out.

NicheObserver accepts a domain, hostname, origin or HTTP(S) URL and returns a monthly CrUX snapshot with an explicit resource scope, adjusted Domain reach, direct Best origin reach, history since October 2022 and estimated country shares.

Contract first. The machine-readable contract is maintained alongside this documentation and the API release. This page explains the semantics clients need to build safely. API access requires a server-side bearer key; never put a key in browser code.
A free API for your tools. Use our scores for free in your public tool by crediting NicheObserver next to the scores. A link to our site is optional. Example: Source: NicheObserver Reach. See the Terms of Service.

01 · Quickstart

Look up a resource

The canonical route is GET /v1/domain?resource=…. Authentication uses Authorization: Bearer. The resource is normalized by the Rust data plane with its embedded Public Suffix List; Laravel checks the request shape and quota but does not apply a second parser.

curl --get 'https://api.nicheobserver.com/v1/domain' \
  --data-urlencode 'resource=https://www.example.co.uk/articles?id=42#intro' \
  -H 'Authorization: Bearer ro_live_<public_id>_<secret>'

A valid resource absent from the current snapshot is a business response with found: false. It arrives as HTTP 404 carrying the full body, not an error object, so a client that treats every 404 as a failure will discard the most common answer when scoring cold lists. It is not the same as malformed input, which returns 400 with an error key, nor an upstream failure. It consumes one lookup unit.

02 · Resource model

Domain, hostname, origin or URL

You can send the web resource you already have. NicheObserver resolves the registrable domain and reports what it actually used:

InputScopeWhat is looked up
example.comdomainThe registered domain and its observed origins.
www.example.co.ukdomainThe registrable domain example.co.uk.
https://app.example.comoriginThe exact scheme + host + port origin, with its parent domain context.
https://example.com/path?q=1originThe origin; path, query and fragment do not change CrUX identity.

URLs with credentials, IP addresses, unsupported schemes and public-suffix-only values are rejected. IDNs are converted to the normalized representation returned in lookup. A domain lookup can expose best_origin and up to ten origins; an origin lookup filters the returned origins to the requested origin and can still report that the parent domain exists.

03 · Batch

Up to 1,000 resources per request

Use POST /v1/domains:lookup with the public property resources. Every array entry costs one lookup-rate token and, on capped plans, one daily lookup, including duplicates and strings that Rust later classifies as invalid. The whole batch is rejected when its weighted rate or remaining capped quota is insufficient; there is no partial admission.

curl -X POST 'https://api.nicheobserver.com/v1/domains:lookup' \
  -H 'Authorization: Bearer ro_live_<public_id>_<secret>' \
  -H 'Content-Type: application/json' \
  -d '{"resources":["example.com","https://app.example.com/login","www.example.co.uk"]}'

The response is 200 for a processed batch and keeps item-level statuses such as found, not_found, invalid and duplicate. The same lookup fields are available per item, including snapshot_month, reach_score, adjusted_reach_score, reach_score_model, popularity_rank_bucket, history, best_market and best_origin.

04 · Origins

Paginate the origin list deliberately

The main lookup returns the best origin and at most ten origins. For the full bounded list, call GET /v1/domain/{resource}/origins. Pages default to 1,000 entries and are capped at 5,000. Each page costs one unit.

curl --get 'https://api.nicheobserver.com/v1/domain/example.com/origins' \
  --data-urlencode 'limit=1000' \
  --data-urlencode 'cursor=0' \
  -H 'Authorization: Bearer ro_live_<public_id>_<secret>'

Read next_cursor until it is null. The cursor is an offset within one immutable snapshot, not a permanent identifier. When snapshot_month changes, discard it and restart at zero. An origin input returns only the matching origin; a domain input returns the domain's observed origin page.

05 · Authentication

Create, store and rotate keys

Sign in for free with Google or a verified email address, then create a key on the developers page in the form ro_live_<public_id>_<secret>. The secret has at least 256 bits of random entropy, is displayed once and cannot be recovered. Store it in a server-side secret manager.

  1. Give each integration its own key name; keys share the account limits.
  2. Create the replacement key before revoking the old key.
  3. Deploy the replacement and verify a harmless /v1/stats request.
  4. Revoke the old key. A revoked key cannot be reactivated.

Only the bearer header is accepted. Query strings, cookies, browser bundles and client-side extensions must never contain a secret. /v1/stats also requires a key and costs zero lookup units.

06 · Limits

Quotas, headers and stable errors

Limits are account-scoped across all keys. A batch of N resources consumes N lookup-rate tokens and, on capped plans, N daily lookups. Free has no daily cap and allows 5 lookups per second. On capped plans, the daily counter resets at 00:00:00 UTC; lookups never roll over.

PlanLookups/dayLookups/sBatchKeys
FreeUnlimited51,0001
Builder100,000251,0002
Scale1,000,000501,0005
UnlimitedUnlimited1001,0001,000

Authenticated lookup responses include the RateLimit-* lookup-token headers, X-Quota-Policy and X-Request-Id. Capped plans also receive X-Quota-Limit, X-Quota-Remaining and X-Quota-Reset; Free and Unlimited omit those three numeric quota headers. Program against the machine-readable error, not the English message.

StatusCodeMeaningUnits
400invalid_jsonMalformed JSON or request shape.0
401invalid_api_keyMissing, unknown or revoked key.0
413batch_too_largeMore than 1,000 entries or body over 512 KiB.0
429rate_limited / daily_quota_exceededRetry after the header-supplied delay.0
503/504dependency_unavailable / upstream_timeoutTechnical dependency failure; observed upstream failures are refunded.0 net

A business lookup with found:false costs one unit. It shares the 404 status with a route that does not exist, and is told apart by its body: a business answer carries lookup, domain and found, never an error key. A malformed request stopped before admission costs nothing.

07 · Client examples

curl, PHP, JavaScript and Python

These examples run on a server. Put NICHEOBSERVER_API_KEY in the process environment or a secret manager, never in a committed file.

PHP

$ch = curl_init('https://api.nicheobserver.com/v1/domain?resource=' . rawurlencode('example.com'));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NICHEOBSERVER_API_KEY')],
]);
$body = json_decode(curl_exec($ch), true, 512, JSON_THROW_ON_ERROR);
curl_close($ch);

JavaScript (Node.js)

const resource = encodeURIComponent('https://app.example.com/login');
const response = await fetch(`https://api.nicheobserver.com/v1/domain?resource=${resource}`, {
  headers: { Authorization: `Bearer ${process.env.NICHEOBSERVER_API_KEY}` },
});
if (!response.ok) throw new Error((await response.json()).error);
const lookup = await response.json();

Python

import os
import requests

response = requests.get(
    "https://api.nicheobserver.com/v1/domain",
    params={"resource": "example.com"},
    headers={"Authorization": f"Bearer {os.environ['NICHEOBSERVER_API_KEY']}"},
    timeout=10,
)
response.raise_for_status()
lookup = response.json()

Monthly history and country shares

Domain lookups and batch results include history from October 2022: 47 months through August 2026. New snapshots extend this history. Points are ordered from oldest to newest; a missing observation is represented by observed: false. The website shows 24 months by default; a free account unlocks the older history.

country_shares describes the snapshot month. Each history[] point also has its own country_shares. These are estimated audience shares for the resolved domain, including when the input is an origin or URL. They are distinct from best_market, which reports the strongest geographic signal.

"country_shares": {
  "observed_countries": 6,
  "other_basis_points": 3300,
  "countries": [
    { "country": "BR", "basis_points": 4800 },
    { "country": "IN", "basis_points": 1400 },
    { "country": "US", "basis_points": 500 }
  ]
}

This example represents Brazil 48%, India 14%, United States 5%, and other countries 33%. Divide basis_points by 100 to obtain a percentage. Up to five countries are returned, in descending share order. Their basis points plus other_basis_points sum to 10,000; do not rescale the displayed countries to 100%. observed_countries counts countries observed in CrUX before the display limit. Codes use ISO 3166-1 alpha-2, with the CLDR XK exception.

country_shares: null means no usable estimate is available for that month, rather than a zero share. Country-share model v3 is calibrated against Similarweb using each month's CrUX profile. Estimates are not analytics visit counts, and older months do not inherit the current country's share.

Country shares JSON schema

08 · Method

Buckets, markets and monthly freshness

CrUX buckets are ranges

adjusted_reach_score is the primary Domain reach signal, from 0 to 100. reach_score is the direct Best origin anchor, mapped from the source bucket; popularity_rank_bucket keeps that discrete range beside it. The adjusted score aggregates the observed footprint and can exceed the direct anchor, but remains below 100 unless the direct score is 100. Neither field is an exact position or a visit count. A score of 0 means the resource was not observed in that month.

Best market is a classification

best_market is a separate, conservative CrUX-derived classification. It can be resolved for the registered domain and independently for an origin. The public hint contains only country, basis and confidence. It does not claim a country-level allocation of activity; an absent result means not observed or not resolved, not a conclusion about the resource.

Freshness

snapshot_month is the YYYYMM release represented by the immutable lookup snapshot. The current artifact covers the latest month and the historical window extends back to October 2022. An observed:false history point means no record for that month. It is not an error and it is not a claim that the resource was inactive.

NicheObserver publishes blue/green-compatible artifacts with matching months. Clients should display the observation month alongside any stored result and restart origins pagination when the snapshot month changes.

09 · Glossary / Glossaire

Use these terms consistently

CrUX / Chrome UX Report
Google's public dataset of real-world Chrome user experiences. It is not a universal census of the web.
Domain / domaine enregistré
The registrable domain derived with the Public Suffix List, such as example.co.uk.
Hostname
A DNS host such as www.example.com; the API may map it to its parent registrable domain.
Origin / origin
Scheme, host and port together, for example https://app.example.com.
Bucket / bucket
A discrete source range or label. A bucket is not a one-to-one position.
Best market / marché principal
A cautious country classification with an explicit basis and confidence, not a country activity allocation.
Snapshot month / mois du snapshot
The YYYYMM data release represented by the loaded immutable artifact.
Found, not_found, no_data
Processing states. A successful response with no useful observation is distinct from an invalid input or technical outage.

10 · Sources

Public source, attribution and boundaries

The underlying source is Google's Chrome UX Report. The official BigQuery guide documents monthly tables, origin-level data and country datasets. Google's methodology explains eligibility, aggregation, filtering and fuzzing; the tools overview explains the different update cadences. We link to BigQuery directly for users who prefer the raw public dataset.

NicheObserver is an independent interface and API. It is not affiliated with, sponsored by or endorsed by Google. “Chrome”, “Google” and “Chrome UX Report” remain their respective owners' marks. We publish no per-domain SEO pages and do not present an interrogated resource as an indexable public profile.

Legal status: pre-launch draft. Terms/AUP and privacy summarize the intended behavior and remain subject to entity, payment, support and retention decisions.