Download OpenAPI specification:Download
Answers questions about a phone number: which network serves it, whether it was ported and from whom, every port on record, and whether it is reachable right now. One canonical response shape for every country; the data behind it differs per country, and the response says what kind of data answered and how fresh it is.
HTTP Basic. Send the account username and password on every request as
Authorization: Basic <base64(username:password)>. The username is
matched without regard to case; the password is matched exactly. Any
other scheme is refused. Always call the API over HTTPS.
Each view needs a scope granted to the account:
| Scope | Grants |
|---|---|
numbers:read |
GET /v1/numbers/{number} |
numbers:porting-history |
the porting-history view, together with numbers:read |
numbers:live-status |
the live-status view, together with numbers:read |
Missing or wrong credentials answer 401 with a Basic challenge. Access
is checked on every request, and a refusal that other credentials would
not fix answers 403: a calling address the client's IP lists do not
allow, a client that may not use the API, or a view whose scope is not
granted. Contact Spirius support to enable the API or add a scope.
The number in the path is E.164: digits with a leading +, the digits
alone, or a 00 international prefix. Spaces and dashes are ignored. A
number in national format, of the wrong length, or with an unassigned
country code is not an error: the answer is HTTP 200 with the finding
invalid_number. The canonical digits (no +) are returned in
number.e164 and in the Content-Location header, the canonical path of
the view. On invalid_number the number was not parsed: number.e164
carries the input as received and no Content-Location is set.
| View | Path | Returns | HTTP caching |
|---|---|---|---|
| Base | GET /v1/numbers/{number} |
number, network, portability, findings, source |
until source.valid_until; no-store when it is null |
| Porting history | GET /v1/numbers/{number}/porting-history |
base + porting_history |
until the earlier valid_until; no-store when either is null |
| Live status | GET /v1/numbers/{number}/live-status |
base + live_status |
until the earlier valid_until; no-store when either is null |
What a client may cache follows from the answer's content, not from the view alone: see Freshness and caching.
An optional field the data has nothing for is absent, never null. The one
exception is source.valid_until, which is null when there is no such time:
a number_range answer, or a live status that is not reused (unavailable).
Findings are answers, not errors, and come with HTTP 200:
| Finding | Meaning |
|---|---|
invalid_number |
The input is not a well-formed E.164 number. Nothing else is looked up. |
unallocated_range |
The number lies in no allocated range. |
unsupported_country |
No data is available for the country, or the number is a global service number. |
not_mobile |
The number is a landline or a service number; number.type says which. |
source.basis says what kind of data answered, and valid_until says how
long it stays current:
| Data | source.basis |
valid_until |
|---|---|---|
| National portability registry, authoritative for the serving provider | portability_registry |
The next registry snapshot, daily |
Live status reachable, absent |
live_network |
Minutes |
Live status barred |
live_network |
Hours |
Live status unknown_subscriber, not_provisioned |
live_network |
About a day |
Live status unavailable |
live_network |
null, never reused |
| Number range only, serving provider unknown | number_range |
null |
A live status is reused until its valid_until instead of querying the
network again. as_of and checked_at keep the time the data was obtained,
so a reused answer shows its age.
Every view answer carries these headers:
| Header | Value | When |
|---|---|---|
ETag |
Strong validator of the exact body | Always. Send it in If-None-Match to get 304 Not Modified. |
Cache-Control |
private, max-age=N |
Until the earliest valid_until of the answer's parts. |
Cache-Control |
private, no-cache |
The earliest valid_until has passed, for example when a registry snapshot is late. |
Cache-Control |
private, no-store |
A part has valid_until: null, or the answer is a finding only. |
Vary |
Authorization |
Always. |
When the base answer resolves but the requested aspect cannot be obtained,
the view still answers 200 with the aspect present and marked:
live_status.state is unavailable, or porting_history carries its
source and no ports. Only a failure of the base answer itself is an
error.
Errors are answered as application/problem+json (RFC 9457) with type,
title, status, detail and instance.
| Status | type |
When |
|---|---|---|
| 401 | https://developer.spirius.com/problems/unauthorized | Missing or wrong credentials. |
| 403 | https://developer.spirius.com/problems/forbidden | The calling address is not allowed, the client may not use the API, or the scope of the view is not granted. |
| 429 | https://developer.spirius.com/problems/rate-limited | The client's request rate is exceeded; Retry-After gives the pause in seconds. |
| 429 | https://developer.spirius.com/problems/quota-exceeded | The client's monthly lookup quota is exhausted; Retry-After is when it resets. |
| 503 | https://developer.spirius.com/problems/provider-unavailable | No data source could answer for the country right now; Retry-After in seconds. |
| 503 | https://developer.spirius.com/problems/service-unavailable | The lookup could not be completed right now; Retry-After in seconds. |
| 404 | https://developer.spirius.com/problems/not-found | No resource exists at the path. |
| 405 | https://developer.spirius.com/problems/method-not-allowed | The method is not allowed on the path. Every view is GET. |
| 500 | https://developer.spirius.com/problems/internal-error | An unexpected error. The detail asks you to contact support with the request path and time. |
Every account has a request rate: up to a burst of requests at once, then a
sustained number of lookups per second. It may also have a monthly lookup
quota. Both are agreed per account. Exceeding either answers 429 with
Retry-After. A lookup that fails on the service side is never counted.
The base view: serving network, portability state, findings and the
freshness of the data behind them. Any input that is not a well-formed
E.164 number answers 200 with the finding invalid_number;
number.e164 then carries the input as received and no
Content-Location is set. Needs the numbers:read scope.
| number required | string Example: +46701234567 The number, E.164 with or without the leading plus, or with a 00 prefix. Spaces and dashes are ignored. |
| If-None-Match | string An |
{- "number": {
- "e164": "46701234567",
- "country": "SE",
- "type": "mobile",
- "service_code": "MTGSM"
}, - "network": {
- "service_provider": {
- "id": "A92",
- "id_scheme": "snpac_spid",
- "name": "Telia Sverige AB (mobile)",
- "brands": [
- "Telia",
- "Halebop"
]
}, - "host_network": {
- "mcc": "240",
- "mnc": "01",
- "name": "Telia"
}
}, - "portability": {
- "ported": true,
- "range_holder": {
- "id": "A66",
- "id_scheme": "snpac_spid",
- "name": "Hi3G Access AB"
}
}, - "findings": [ ],
- "source": {
- "basis": "portability_registry",
- "as_of": "2026-09-10T22:00:00Z",
- "valid_until": "2026-09-12T08:00:00Z"
}
}The base view plus porting_history: every port on record newest first
where the registry retains the history, otherwise the latest port
alone. When the base resolves but the history cannot be obtained, the
aspect is present with its source and no ports. On
invalid_number, number.e164 carries the input as received. Needs
numbers:read and numbers:porting-history.
| number required | string Example: +46701234567 The number, E.164 with or without the leading plus, or with a 00 prefix. Spaces and dashes are ignored. |
| If-None-Match | string An |
{- "number": {
- "e164": "46701234567",
- "country": "SE",
- "type": "mobile"
}, - "network": {
- "service_provider": {
- "id": "A92",
- "id_scheme": "snpac_spid",
- "name": "Telia Sverige AB (mobile)"
}
}, - "portability": {
- "ported": true
}, - "findings": [ ],
- "source": {
- "basis": "portability_registry",
- "as_of": "2026-09-10T22:00:00Z",
- "valid_until": "2026-09-12T08:00:00Z"
}, - "porting_history": {
- "ports": [
- {
- "ported_at": "2026-08-14",
- "donor": {
- "id": "A66",
- "id_scheme": "snpac_spid",
- "name": "Hi3G Access AB"
}, - "recipient": {
- "id": "A92",
- "id_scheme": "snpac_spid",
- "name": "Telia Sverige AB (mobile)"
}
}
], - "history_since": "2001-01-01",
- "source": {
- "basis": "portability_registry",
- "as_of": "2026-09-10T22:00:00Z",
- "valid_until": "2026-09-12T08:00:00Z"
}
}
}The base view plus live_status, a live query of the mobile network,
or a recent one reused for a time that depends on the state (see
Freshness and caching; checked_at is when the network answered, and
Cache-Control gives the exact remaining validity). When no live answer can be obtained, the aspect is present
with state: unavailable and the base answer stands. On
invalid_number, number.e164 carries the input as received. A live
answer is cacheable until its valid_until. Needs
numbers:read and numbers:live-status.
| number required | string Example: +46701234567 The number, E.164 with or without the leading plus, or with a 00 prefix. Spaces and dashes are ignored. |
| If-None-Match | string An |
{- "number": {
- "e164": "46701234567",
- "country": "SE",
- "type": "mobile"
}, - "network": {
- "service_provider": {
- "id": "A92",
- "id_scheme": "snpac_spid",
- "name": "Telia Sverige AB (mobile)"
}
}, - "portability": {
- "ported": true
}, - "findings": [ ],
- "source": {
- "basis": "portability_registry",
- "as_of": "2026-09-10T22:00:00Z",
- "valid_until": "2026-09-12T08:00:00Z"
}, - "live_status": {
- "state": "reachable",
- "checked_at": "2026-09-11T09:41:12Z",
- "source": {
- "basis": "live_network",
- "as_of": "2026-09-11T09:41:12Z",
- "valid_until": "2026-09-11T09:56:12Z"
}
}
}