+1 (726) 227-2971

Working with the Looker API 4.0 and SDKs (2026)

Updated August 2026. The original 2023 version of this article used Looker API 3.1, which was removed from Looker in 2023. Everything below targets API 4.0 and the official SDKs.

Looker exposes almost everything you can do in the UI through a REST API: run queries, manage users and roles, deploy LookML, render dashboards to PDF, and read System Activity. Since 2023 there is exactly one API version, 4.0. If you have scripts that still call /api/3.1/..., they have been failing since your instance upgraded past Looker 23.18; see the migration map at the end.

API keys and authentication

API access is granted per user. In Admin > Users, edit the user (ideally a dedicated service user with only the roles the integration needs) and click Edit Keys to create an API key. What Looker used to call an "API3 key" is now just an API key; the admin UI dropped the "3" along with the old API. You get a client_id and a client_secret. Treat the secret like a password: it is shown once and it grants whatever that user can do.

Authentication is a two-step exchange. Post the key to /api/4.0/login and you receive a short-lived access token (about an hour) to send as a bearer token on every subsequent call:

# Replace <CLIENT_ID> and <CLIENT_SECRET> with your API key
curl -s 'https://<YOUR_LOOKER_INSTANCE>/api/4.0/login' \
  --data-urlencode 'client_id=<CLIENT_ID>' \
  --data-urlencode 'client_secret=<CLIENT_SECRET>'
# {"access_token":"...","token_type":"Bearer","expires_in":3600}

The API host for Looker (original) is your instance URL; many instances listen on port 19999 for the API. For Looker (Google Cloud core) the instance URL issued by the Google Cloud console is used directly, without a separate port.

Use the SDK, not raw requests

You can drive the API with curl and requests, but the official SDKs handle login, token refresh, typed request bodies and pagination for you. The Python SDK is looker_sdk:

pip install looker-sdk

Configure it with environment variables (preferred over a looker.ini file that tends to get committed by accident):

export LOOKERSDK_BASE_URL="https://<YOUR_LOOKER_INSTANCE>"
export LOOKERSDK_CLIENT_ID="<CLIENT_ID>"
export LOOKERSDK_CLIENT_SECRET="<CLIENT_SECRET>"

Then:

import looker_sdk

sdk = looker_sdk.init40()   # API 4.0 client; logs in lazily with the env vars above
me = sdk.me()
print(me.display_name, [r for r in me.role_ids])

SDKs with the same shape exist for TypeScript/JavaScript (@looker/sdk), Kotlin, Swift, Go and Ruby, all in the looker-open-source GitHub organization.

Running a query: run_inline_query

The most common integration task is "get me the numbers." run_inline_query takes a JSON query body, the same structure an Explore builds for you: a model, an explore (the API field is still called view), a list of fully qualified fields, filters, sorts and a limit. It is not SQL, and it is not LookML. Looker compiles it through your LookML model, so joins, access filters and measure definitions are applied exactly as they are in the UI.

import looker_sdk
from looker_sdk import models40 as models

sdk = looker_sdk.init40()

body = models.WriteQuery(
    model="ecommerce",
    view="orders",                       # the explore name
    fields=["orders.created_month", "orders.total_revenue", "users.country"],
    filters={"orders.created_date": "12 months", "users.country": "USA,Canada"},
    sorts=["orders.created_month desc"],
    limit="500",
)

rows = sdk.run_inline_query(result_format="json", body=body)
# rows is a JSON string; parse it
import json
for row in json.loads(rows):
    print(row["orders.created_month"], row["users.country"], row["orders.total_revenue"])

result_format accepts json, json_detail, csv, txt, html, md, xlsx, sql and png. Ask for sql when you want to see exactly what Looker generated; it is the fastest way to debug a filter expression.

If the query already exists as a saved Look, run_look is simpler and keeps the definition under your analysts' control:

csv_text = sdk.run_look(look_id="42", result_format="csv")

Loading results into another system

The 2023 version of this article pushed rows into PostgreSQL with psycopg2. The pattern is still valid; the SDK just makes the fetch side shorter and safer. With json_detail you also get field metadata, which is useful for building the target table:

import json, psycopg2, looker_sdk
from looker_sdk import models40 as models

sdk = looker_sdk.init40()
result = json.loads(sdk.run_inline_query(
    result_format="json",
    body=models.WriteQuery(model="ecommerce", view="orders",
                           fields=["orders.created_date", "orders.total_revenue"],
                           filters={"orders.created_date": "7 days"}, limit="5000"),
))

conn = psycopg2.connect(host="<DB_HOST>", dbname="<DB_NAME>", user="<DB_USER>", password="<DB_PASSWORD>")
with conn, conn.cursor() as cur:
    cur.executemany(
        "INSERT INTO daily_revenue (day, revenue) VALUES (%s, %s) ON CONFLICT (day) DO UPDATE SET revenue = EXCLUDED.revenue",
        [(r["orders.created_date"], r["orders.total_revenue"]) for r in result],
    )

For anything beyond a few thousand rows, do not page Looker query results into another database. Schedule the Look to deliver to S3, GCS or SFTP, or better, materialize the logic in the warehouse where both systems can read it.

Beyond queries

  • Users and access: all_users, create_user, set_user_roles, set_user_attribute_user_value let you sync users from an HR system and drive row-level security through user attributes.
  • LookML deploys: deploy_ref_to_production and the project deploy webhook integrate Looker into CI.
  • Rendering: create_dashboard_render_task produces PDFs and PNGs for distribution systems Looker's scheduler does not reach.
  • Artifact API: update_artifacts and artifact give extensions and integrations a small key-value store on the instance, namespaced per application, so you do not need an external database for configuration.
  • System Activity: run inline queries against the system__activity model to pull usage and performance data into your own monitoring.

If your scripts still call /api/3.1, here is the migration map

  • Endpoints: the path prefix changes from /api/3.1/ to /api/4.0/. Most endpoint names are unchanged; a handful were renamed or split, and several response fields became strictly typed (ids that were integers are now strings).
  • Login: identical, but the key is managed under Edit Keys rather than "API3 keys."
  • Query bodies: run_inline_query bodies are the same shape; view still means explore.
  • SDK users: replace looker_sdk.init31() with init40() and models31 with models40, then fix type errors; the SDK's typing will find most breaking changes for you.
  • Secrets: treat the migration as a chance to rotate keys, move them to a secret manager, and give each integration its own service user with the narrowest role that works.

Need help moving an integration off a dead API, or building a new one? Our Looker consultants do this every week; get in touch.