Google Knowledge Graph Search API: Tutorial & Python Examples

The Google Knowledge Graph Search API is a single read-only HTTP endpoint that takes a text query or an entity id and returns matching entities from Google’s Knowledge Graph as JSON-LD. It requires a Google Cloud API key, it answers at https://kgsearch.googleapis.com/v1/entities:search, and every match arrives with an @id such as kg:/m/0dl567 plus a resultScore. It is a lookup service over a subset of the graph, not a window onto the data behind knowledge panels.

What the API does and does not do

The API answers one question: which entities in its index match this string or this id. It returns the entity’s name, its schema.org types, a short description, a longer description with a source link, an image reference and a website. Nothing else.

Three limits matter before writing any code against it. The index is a subset of the Google Knowledge Graph, so an entity that appears in a knowledge panel may return no result here, and the reverse also happens. The response carries no relationships: there is no way to ask for an entity’s founder, birthplace or parent organization, which is the difference between this API and a queryable graph such as the Wikidata Query Service. And the API is not the mechanism that produces knowledge panels, so nothing done through it changes what a panel shows.

What it is good for is reconciliation. Given a list of names from a spreadsheet, a CMS or an extraction pipeline, the API says whether Google resolves each name to an entity and which one, and hands back the id and the types it assigns. That makes it a checking tool for entity SEO work and a labeling step in entity linking pipelines.

Getting a key

The API is billed and quota-managed through Google Cloud. Create or select a project in the Google Cloud console, enable the Knowledge Graph Search API for that project, then create an API key credential. The key is passed as the key query parameter on every request. Restrict the key to the Knowledge Graph Search API in the console, because an unrestricted key in client-side code is a key anyone can spend.

A working curl call

curl -s -G "https://kgsearch.googleapis.com/v1/entities:search" \
  --data-urlencode "query=Marie Curie" \
  --data-urlencode "languages=en" \
  --data-urlencode "limit=5" \
  --data-urlencode "indent=true" \
  --data-urlencode "key=$KG_API_KEY"

Using -G with --data-urlencode puts each parameter in the query string with correct escaping, which matters as soon as a query contains a space or an ampersand. Set KG_API_KEY in the shell environment rather than pasting the key into a command that lands in shell history.

The response is a JSON-LD document. Trimmed to one match, it has this shape:

{
  "@context": {
    "@vocab": "http://schema.org/",
    "goog": "http://schema.googleapis.com/",
    "EntitySearchResult": "goog:EntitySearchResult",
    "detailedDescription": "goog:detailedDescription",
    "resultScore": "goog:resultScore",
    "kg": "http://g.co/kg"
  },
  "@type": "ItemList",
  "itemListElement": [
    {
      "@type": "EntitySearchResult",
      "result": {
        "@id": "kg:/m/0jkv",
        "name": "Marie Curie",
        "@type": ["Thing", "Person"],
        "description": "Physicist",
        "image": {
          "contentUrl": "https://example.com/image.jpg",
          "url": "https://example.com/license-page"
        },
        "detailedDescription": {
          "articleBody": "Maria Salomea Sklodowska-Curie was a Polish and naturalised-French physicist and chemist ...",
          "url": "https://en.wikipedia.org/wiki/Marie_Curie",
          "license": "https://creativecommons.org/licenses/by-sa/3.0/"
        },
        "url": "https://example.org/official-site"
      },
      "resultScore": 12345.6
    }
  ]
}

The request parameters

ParameterPurpose
queryThe text to match. Mutually useful with, but independent of, ids.
idsLook up a known entity by id, for example /m/0jkv. Repeatable.
limitHow many entities to return. Defaults to 20, capped at 500.
languagesOne or more language codes, in preference order.
typesRestrict results to given schema.org types, for example Person. Repeatable.
prefixWhen true, matches on a name prefix, which suits autocomplete.
indentPretty-prints the JSON.

Two details save time. When looking up by id, drop the kg: prefix: the ids parameter takes /m/0jkv, while the response returns kg:/m/0jkv. And types filters on the same type names schema.org publishes, so a request restricted to Person will not return an organization that happens to share the name. The vocabulary itself is covered at schema.org.

The response fields

Only these fields are documented, and code should treat every one of them except @id as optional.

  • @id is the entity identifier, in the form kg:/m/0jkv. Most IDs use the /m/ form inherited from Freebase machine IDs (MIDs); /g/ IDs also occur. The id is the only part of a result stable enough to store as a key.
  • name is the primary display name in the requested language.
  • @type is an array of schema.org type names, always including Thing.
  • description is a short label, often two or three words, such as “Physicist” or “American football team”.
  • detailedDescription is an object with articleBody, url and license. The body is usually the opening of a Wikipedia article, and the license is the Wikipedia content license, so any interface that displays it needs to display the attribution too.
  • image is an object with contentUrl (the image file) and url (the page the image came from). Do not hotlink contentUrl in production without checking the licensing on url.
  • url is a website for the entity, typically the official site.

There is no confidence field beyond resultScore, no relationship data, no Wikidata Q-ID field, and no field carrying the knowledge panel content. Anything else seen in a response is undocumented and can change.

What resultScore means

resultScore is a match score for this lookup. It expresses how well the entity matched the query string that was sent, and it exists so a caller can rank candidate matches against each other within one response. It is not an authority score, a prominence ranking, an importance metric, or anything a search engine uses to order results.

Two consequences follow. Scores are only comparable inside a single response, because the scale is unbounded and shifts with the query, so a score of 8,000 for one name and 300,000 for another says nothing about which entity matters more. And a high score is not evidence that Google considers the entity well understood. Reports that treat resultScore as an entity strength metric are reading a string-matching number as a knowledge signal.

The useful pattern is relative: if the top result scores an order of magnitude above the second, the match is likely unambiguous; if the top two are close, the name is ambiguous and needs a types filter or more context before it can be reconciled automatically.

Python: reconciling a list of names

The API has no cursor or page token. limit caps the number of entities in one response at 500, and there is no way to ask for the next 500, so a large job is many narrow queries rather than one paged one. Google’s documentation also notes that higher limits increase the chance of a request timing out, which argues for small limits and retries.

import os
import time
import requests

ENDPOINT = "https://kgsearch.googleapis.com/v1/entities:search"
API_KEY = os.environ["KG_API_KEY"]
MAX_LIMIT = 500  # documented ceiling; there is no pagination past it


def search_entities(query, limit=5, languages="en", types=None, retries=3):
    """Return the itemListElement list for one query, or [] on failure."""
    params = {
        "query": query,
        "limit": min(limit, MAX_LIMIT),
        "languages": languages,
        "key": API_KEY,
    }
    if types:
        params["types"] = types  # requests repeats the key for a list

    for attempt in range(retries):
        response = requests.get(ENDPOINT, params=params, timeout=20)
        if response.status_code == 200:
            return response.json().get("itemListElement", [])
        if response.status_code in (429, 500, 503):
            time.sleep(2 ** attempt)
            continue
        response.raise_for_status()
    return []


def flatten(element):
    """Pull the documented fields out of one result, tolerating absences."""
    result = element.get("result", {})
    detailed = result.get("detailedDescription") or {}
    image = result.get("image") or {}
    return {
        "id": result.get("@id"),
        "name": result.get("name"),
        "types": [t for t in result.get("@type", []) if t != "Thing"],
        "description": result.get("description"),
        "article_url": detailed.get("url"),
        "article_body": detailed.get("articleBody"),
        "image_url": image.get("contentUrl"),
        "website": result.get("url"),
        "score": element.get("resultScore"),
    }


def reconcile(name, types=None):
    """Best match for a name, plus a flag for an ambiguous lookup."""
    matches = [flatten(e) for e in search_entities(name, limit=5, types=types)]
    if not matches:
        return None
    top = matches[0]
    runner_up = matches[1]["score"] if len(matches) > 1 else 0
    top_score = top["score"] or 0
    top["ambiguous"] = bool(runner_up and top_score < runner_up * 3)
    return top


for name in ["Marie Curie", "University of Florida", "Apollo"]:
    match = reconcile(name, types="Person" if name == "Marie Curie" else None)
    if match is None:
        print(f"{name}: no match")
    else:
        flag = " (ambiguous)" if match["ambiguous"] else ""
        print(f"{name}: {match['id']} {match['name']} "
              f"[{', '.join(match['types'])}] score={match['score']}{flag}")

Three things in that code exist because of how the API behaves rather than out of general caution. flatten uses result.get("detailedDescription") or {} instead of .get(..., {}), because a key present with a null value would otherwise break the chained lookup. types is stripped of Thing, which every entity carries and which therefore identifies nothing. And the ambiguity flag compares the top two scores instead of testing an absolute threshold, since the scale is not comparable across queries.

For a batch of thousands of names, cache by exact query string, keep concurrency low, and store the returned @id rather than the name, because the id is what stays stable when a display name changes.

For developers

Treat the API as a reconciliation service with a narrow contract. Store @id, and store the query string that produced it so a later re-run can be compared. Do not build a data model on description, which is a free-text label that changes without notice. Cache aggressively: the same query returns the same answer for long stretches, and the quota is a shared project resource.

When you need relationships, the API is the wrong tool. Reconcile the name here to get Google’s ID, then query Wikidata for the facts. Wikidata carries the Freebase ID as property P646 on many items, which gives a path from a /m/ ID to a Q-ID and from there to a full statement graph over SPARQL. Coverage of P646 is partial, so treat a missing value as normal rather than as an error.

For SEOs

The honest use of this API is diagnosis. Query a brand, an author or a product name and see whether Google’s index resolves it to an entity at all, which @type it assigns, and which page it returns in url. A name that returns nothing, or returns a different organization, or is typed as something other than what it is, describes a reconciliation problem that markup and corroborating sources can address. The work itself happens elsewhere: sameAs links, covered at sameAs schema, an accurate Organization schema block, and consistent naming across the sources Google reads.

The API cannot measure progress. Because the index is a subset of the graph and resultScore reflects string matching, a rising score is not a sign of growing entity authority, and an absent result is not proof that Google does not know the entity. Tools that chart resultScore over time as an entity strength graph are charting noise. For what actually feeds a panel, see how to get a knowledge panel.

Related pages

FAQ

Is the Google Knowledge Graph Search API free?

It requires a Google Cloud project and an API key, and usage is subject to that project’s quota. There is no separate public endpoint without a key. Quotas and any charges are set in the Google Cloud console for the project, so check the current terms there rather than relying on figures published elsewhere.

What does resultScore mean in the Knowledge Graph Search API?

It is a match score for the lookup: how well an entity matched the query string that was sent. It is not an authority, importance, or prominence metric. Scores are comparable only within a single response, because the scale is unbounded and shifts with the query, so tracking a score over time measures nothing useful.

Does the API show what appears in a knowledge panel?

No. The API queries a subset of the Knowledge Graph and returns a fixed set of fields: id, name, types, description, detailed description, image, and url. Knowledge panels are assembled separately from many sources, so an entity can have a panel and no API result, or an API result and no panel.

How do I get an entity’s Wikidata Q-ID from a Knowledge Graph API result?

The API does not return Q-IDs. The usual route is the Freebase ID: the API returns IDs of the form /m/0jkv, and many Wikidata items record the same value in property P646, so a SPARQL query on P646 maps the ID to a Q-ID. Coverage is partial, so expect some ids to have no match.

Sources and further reading

  • Google, Knowledge Graph Search API documentation: https://developers.google.com/knowledge-graph
  • Google, Knowledge Graph Search API reference for entities.search: https://developers.google.com/knowledge-graph/reference/rest/v1
  • Google Cloud, creating and restricting API keys: https://cloud.google.com/docs/authentication/api-keys
  • Wikidata, property P646 (Freebase ID): https://www.wikidata.org/wiki/Property:P646
  • schema.org, Thing: https://schema.org/Thing