Docs
View as Markdown

URL lookup

GET /v1/lookup?url= takes the public URL of a post, video, repository, package, article or profile and answers exactly as the matching endpoint would. You do not have to know which platform endpoint to call or which parameters it takes: paste the link, and the response tells you what it resolved to.

shell
GET https://api.onekey.dev/v1/lookup?url={public URL}
x-api-key: YOUR_API_KEY

When to use it#

  • Your input is a link a user pasted, a URL from a feed, or a bookmark, and you want the record behind it.
  • You handle many platforms and would rather not keep a table of URL formats and endpoint parameters in your own code.
  • You still get every guarantee of the endpoint it resolves to: the same response format, the same price, the same caching, the same refunds.

Resolution is plain string matching against URL shapes declared on the endpoints themselves (the table below). Nothing is guessed and the URL is never fetched: only the platform's own data source is.

The request#

curl "https://api.onekey.dev/v1/lookup?url=https://github.com/facebook/react" \
  -H "x-api-key: YOUR_API_KEY"
ParameterRequiredWhat it is
urlyesAn absolute http or https URL, at most 2048 characters. Encode it as you would any query value.
cursornoPassed through to the resolved endpoint when it pages (see Pagination). Today none of the endpoints reached by URL page, so you will not need it yet.

The response#

The body is the resolved endpoint's normal response plus one extra top-level key, resolved. This is github.com/facebook/react as recorded from the live API:

json
{
  "success": true,
  "data": {
    "entity": {
      "id": "10270250",
      "name": "react/react",
      "description": "The library for web and native user interfaces.",
      "url": "https://github.com/react/react",
      "stats": { "stars": 250835, "forks": 51416, "openIssues": 1384, "watchers": 6601 },
      "language": "JavaScript",
      "createdAt": "2013-05-24T16:15:54Z",
      "updatedAt": "2026-09-30T03:07:37Z",
      "tags": ["declarative", "frontend", "javascript", "library", "react", "ui"]
    },
    "package": { "pushedAt": "2026-09-29T22:40:19Z", "license": "MIT", "defaultBranch": "main", "archived": false, "homepageUrl": "https://react.dev" },
    "author": { "username": "react", "avatarUrl": "https://avatars.githubusercontent.com/u/102812?v=4" }
  },
  "metadata": {
    "platform": "github",
    "endpoint": "repo",
    "cached": false,
    "stale": false,
    "age_s": 0.015,
    "fetched_at": "2026-09-30T03:40:59.096767+00:00",
    "credits_charged": 1,
    "request_id": "bcecb897f94848c1b5dd5cc82f68b0f4",
    "idempotent_replay": false
  },
  "error": null,
  "resolved": { "platform": "github", "endpoint": "repo", "params": { "owner": "facebook", "repo": "react" } }
}
FieldMeaning
resolved.platform, resolved.endpointThe endpoint that answered. metadata.platform and metadata.endpoint say the same.
resolved.paramsThe parameters extracted from the URL, after the endpoint's own normalisation. Calling GET /v1/{platform}/{endpoint} with exactly these gives the same answer.
data, metadata, paginationAs on the resolved endpoint's page, including its computed block where it has one.

A URL that resolves to a list endpoint answers with that endpoint's list and, if it pages, its pagination block.

What resolves#

65 URL shapes lead to 54 endpoints on 29 platforms. This table is generated from the same declarations the API matches against, so it is always current; the live list is also x-resolves-to on /v1/lookup in /openapi.json, and features.lookup in GET /v1/platforms marks each endpoint that has a shape.

URL shapeResolves toExample
bsky.app/profile/{actor}/post/{rkey}bluesky.post_threadhttps://bsky.app/profile/did:plc:z72i7hdynmk6r22z27h6tvur/post/3mw2cdr44fc2a
bsky.app/profile/{handle}bluesky.profilehttps://bsky.app/profile/bsky.app
crates.io/crates/{name}crates.cratehttps://crates.io/crates/serde
dev.to/{username}/{slug}devto.article_by_pathhttps://dev.to/devteam/changelog-query-the-dev-api-from-a-javascript-frontend-329j
dev.to/{username}devto.profilehttps://dev.to/ben
hub.docker.com/r/{namespace}/{repo}dockerhub.repohttps://hub.docker.com/r/library/nginx
gist.github.com/*/{id}github.gisthttps://gist.github.com/…/6efdf9ca47239507e6a47a208580dc68
gist.github.com/{id}github.gisthttps://gist.github.com/6efdf9ca47239507e6a47a208580dc68
github.com/{owner}/{repo}/issues/{number}github.issuehttps://github.com/facebook/react/issues/1
github.com/orgs/{org}github.orghttps://github.com/orgs/github
github.com/{username}github.profilehttps://github.com/torvalds
github.com/{owner}/{repo}/pull/{number}github.pullhttps://github.com/facebook/react/pull/1
github.com/{owner}/{repo}github.repohttps://github.com/facebook/react
gitlab.com/{project+}/-/issues/{iid}gitlab.issuehttps://gitlab.com/gitlab-org/gitlab/-/issues/1
gitlab.com/{project+}/-/merge_requests/{iid}gitlab.merge_requesthttps://gitlab.com/gitlab-org/gitlab/-/merge_requests/1
gitlab.com/{project+}gitlab.projecthttps://gitlab.com/gitlab-org/gitlab
gitlab.com/{username}gitlab.userhttps://gitlab.com/gitlab-bot
news.ycombinator.com/item?id={id}hackernews.itemhttps://news.ycombinator.com/item?id=8863
news.ycombinator.com/user?id={username}hackernews.userhttps://news.ycombinator.com/user?id=pg
huggingface.co/{owner}/{name}huggingface.modelhttps://huggingface.co/google-bert/bert-base-uncased
huggingface.co/{name}huggingface.userhttps://huggingface.co/julien-c
instagram.com/p/{shortcode}instagram.posthttps://instagram.com/p/CUbHfhpswxt
instagram.com/reel/{shortcode}instagram.posthttps://instagram.com/reel/CUbHfhpswxt
instagram.com/{handle}instagram.profilehttps://instagram.com/instagram
komi.io/{handle}komi.pagehttps://komi.io/komi
lnk.bio/{handle}linkbio.pagehttps://lnk.bio/linkbio
link.me/{handle}linkme.pagehttps://link.me/linkme
linktr.ee/{handle}linktree.pagehttps://linktr.ee/linktree
lobste.rs/s/{id}/**lobsters.storyhttps://lobste.rs/s/q2rymh
lobste.rs/~{username}lobsters.userhttps://lobste.rs/~jcs
blog.naver.com/{handle}naver.bloghttps://blog.naver.com/blogpeople
npmjs.com/package/{name}npm.packagehttps://npmjs.com/package/left-pad
npmjs.com/package/{name+}npm.package-
openlibrary.org/works/{id}/**openlibrary.workhttps://openlibrary.org/works/OL45804W
producthunt.com/posts/{slug}producthunt.posthttps://producthunt.com/posts/example
pypi.org/project/{name}pypi.packagehttps://pypi.org/project/requests
reddit.com/r/*/comments/{id}/**reddit.posthttps://reddit.com/r/…/comments/abc121
redd.it/{id}reddit.posthttps://redd.it/abc121
reddit.com/r/{subreddit}reddit.subreddit_abouthttps://reddit.com/r/python
reddit.com/user/{username}reddit.user_abouthttps://reddit.com/user/spez
reddit.com/u/{username}reddit.user_abouthttps://reddit.com/u/spez
rubygems.org/gems/{name}rubygems.gemhttps://rubygems.org/gems/rails
soundcloud.com/{user}soundcloud.profilehttps://soundcloud.com/forss
soundcloud.com/{user}/{track}soundcloud.trackhttps://soundcloud.com/forss/flickermood
open.spotify.com/album/{id}spotify.albumhttps://open.spotify.com/album/6dVIqQ8qmQ5GBnJ9shOYGE
open.spotify.com/artist/{id}spotify.artisthttps://open.spotify.com/artist/4Z8W4fKeB5YxbusRsdQVPb
open.spotify.com/track/{id}spotify.trackhttps://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC
stackoverflow.com/questions/{id}/**stackexchange.questionhttps://stackoverflow.com/questions/11227809
stackoverflow.com/q/{id}stackexchange.questionhttps://stackoverflow.com/q/11227809
stackoverflow.com/users/{id}/**stackexchange.userhttps://stackoverflow.com/users/22656
tiktok.com/@{handle}tiktok.profilehttps://tiktok.com/@tiktok
tiktok.com/*/video/{id}tiktok.videohttps://tiktok.com/…/video/6718335390845095173
twitch.tv/{login}twitch.profilehttps://twitch.tv/shroud
twitch.tv/videos/{id}twitch.videohttps://twitch.tv/videos/2886470967
en.wikipedia.org/wiki/{title}wikipedia.articlehttps://en.wikipedia.org/wiki/Alan%20Turing
x.com/*/status/{id}x.tweethttps://x.com/…/status/1519781379172495360
twitter.com/*/status/{id}x.tweethttps://twitter.com/…/status/1519781379172495360
x.com/{handle}x.userhttps://x.com/jack
twitter.com/{handle}x.userhttps://twitter.com/jack
youtube.com/channel/{id}youtube.channelhttps://youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw
youtube.com/@{handle}youtube.channel_by_handlehttps://youtube.com/@rickastley
youtube.com/watch?v={id}youtube.videohttps://youtube.com/watch?v=dQw4w9WgXcQ
youtu.be/{id}youtube.videohttps://youtu.be/dQw4w9WgXcQ
youtube.com/shorts/{id}youtube.videohttps://youtube.com/shorts/dQw4w9WgXcQ
youtube.com/embed/{id}youtube.videohttps://youtube.com/embed/dQw4w9WgXcQ

In a URL shape, {name} is one path segment captured as that parameter, {name+} is two or more segments, … is one segment whose value is ignored, and a shape ending in /** ignores anything after it. Examples are built from each endpoint's own example parameters.

Platforms whose host is chosen by the user, such as Mastodon and Lemmy instances, have no shape: there is no fixed host to match.

How a URL is matched#

  1. The scheme must be http or https. Which one does not matter.
  2. The host is lower-cased and a leading www., m. or mobile. is dropped, so https://m.github.com/torvalds/linux resolves like github.com/torvalds/linux.
  3. A trailing /, repeated slashes and the #fragment are ignored. Query parameters the shape does not name are ignored (?utm_source=... is harmless); the ones it names, such as id in news.ycombinator.com/item?id={id}, must be present.
  4. When two shapes on one host could both match, the one with more fixed segments wins: github.com/orgs/{org} beats github.com/{owner}/{repo} for github.com/orgs/facebook. Shapes that could tie are refused when the catalog is built, so a URL never resolves to two endpoints.
  5. The captured value must satisfy the endpoint's own parameter rules (the Constraints column on its page). github.com/orgs/Bad_Org! matches the shape but not the parameter, so it does not resolve.

Anything else is 422 UNSUPPORTED_URL. The message never repeats your URL.

Edge cases#

  • The URL is not fetched. Only the platform's data source is. A link that exists but points at a private or deleted record answers the way the endpoint does: 404 NOT_FOUND, free.
  • One-segment GitLab paths resolve to the user endpoint. gitlab.com/<name> is ambiguous between a user and a group; it resolves to gitlab.user, and a group at that path answers 404 NOT_FOUND (refunded).
  • A URL can resolve and still fail. The lookup only picks the endpoint; the fetch can fail like any other call (502, refunded).
  • Cache hits are free here too. Looking up news.ycombinator.com/item?id=8863 right after calling hackernews.item with id=8863 is a cache hit (X-Cache: HIT, credits_charged: 0): both build the same parameters.
  • Idempotency works as on a direct call. Send an Idempotency-Key; it is bound to the lookup query string, so the same URL with the same key is replayed for free for 24 hours. A lookup and a direct endpoint call are different requests for that purpose even when they resolve to the same parameters.
  • Different URLs for the same record are cache-equivalent when they resolve to the same parameters: youtu.be/{id}, youtube.com/watch?v={id} and youtube.com/shorts/{id} all become youtube.video with the same id.

Unsupported URLs#

shell
curl "https://api.onekey.dev/v1/lookup?url=https://example.com/whatever" \
  -H "x-api-key: YOUR_API_KEY"
# HTTP/1.1 422 Unprocessable Content
json
{
  "success": false,
  "data": null,
  "metadata": { "request_id": "bb8c4300f1fb4017b9e2eeaa4e0681bf" },
  "error": {
    "code": "unsupported_url",
    "type": "UNSUPPORTED_URL",
    "message": "No endpoint matches this URL",
    "request_id": "bb8c4300f1fb4017b9e2eeaa4e0681bf",
    "doc_url": "https://onekey.dev/errors/UNSUPPORTED_URL"
  }
}

A value that is not an absolute http(s) URL at all (not-a-url, or over 2048 characters) is the same 422 UNSUPPORTED_URL with the message url must be an absolute http(s) URL. Leaving url out is 422 INVALID_PARAMS. None of these are charged.

What it costs#

Exactly the resolved endpoint's price, shown on its page: endpoints cost 1 to 8 credits. There is no surcharge for the lookup itself. A cache hit is free (a cached response over 100 KB costs 0.2 credit), a not found or a source failure is refunded, and an unsupported URL is rejected before any charge. metadata.credits_charged tells you what this call cost.

Errors it can return#

StatusTypeWhen
422UNSUPPORTED_URLNo URL shape matches, or the value is not an absolute http(s) URL. Free.
422INVALID_PARAMSurl is missing. Free.
404NOT_FOUNDThe URL resolved, but the record does not exist on the platform. Free.
401UNAUTHORIZEDBad or missing key.
402INSUFFICIENT_CREDITS, KEY_BUDGET_EXCEEDEDThe resolved endpoint's price cannot be covered. Nothing charged.
429RATE_LIMITEDWait Retry-After seconds.
502UPSTREAM_ERROR and the other UPSTREAM_* typesThe platform failed. Refunded; retry with backoff.

Plus the idempotency answers when you send an Idempotency-Key. See Errors for the full list.

Try it: URL to data · URL to endpoint finder