MetricSign
Start free
error-reference8 min·

Why Databricks Notebooks Reject raw_input — and How to Authenticate APIs Without a Terminal

StdinNotImplementedError kills OAuth flows in Databricks because notebooks have no stdin. The fix requires restructuring how you acquire tokens — not patching the prompt.

Lees dit artikel in het Nederlands →

Notebooks don't have a terminal — and your OAuth library assumes they do

The error is blunt:

StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.

This surfaces when a Python library calls input() (or raw_input() in Python 2) to prompt the user for something — a URL to paste back, a confirmation, a code. In a local terminal, that works. In a Databricks notebook, there is no stdin file descriptor attached to the kernel process. The IPython kernel that powers Databricks notebooks explicitly raises StdinNotImplementedError instead of hanging forever on a read that will never complete.

The Spotify API is a common trigger, but it is not the only one. Any OAuth 2.0 Authorization Code flow that uses a local redirect and then asks the user to paste a URL back will hit this wall. The spotipy library's SpotifyOAuth.get_access_token() method, when it cannot open a browser and has no cached token, falls through to an input() call asking for the redirect URL. That single line kills the notebook cell.

The important thing to understand: this is not a bug in spotipy or in Databricks. It is an architectural mismatch. Authorization Code flow was designed for applications with a user sitting at a browser. Databricks notebooks are headless compute environments that happen to have a web UI for editing code. The notebook UI does not proxy stdin to the kernel. No amount of configuration will change that — you need a different authentication strategy.

Client Credentials flow eliminates the prompt entirely

If your workload does not need user-specific data — you are pulling public playlists, track metadata, audio features, or catalog search results — switch from SpotifyOAuth to SpotifyClientCredentials. This is the single most common fix for this error.

import spotipy
from spotipy.oauth2 import SpotifyClientCredentials

auth_manager = SpotifyClientCredentials(
    client_id=dbutils.secrets.get(scope="spotify", key="client_id"),
    client_secret=dbutils.secrets.get(scope="spotify", key="client_secret")
)
sp = spotipy.Spotify(auth_manager=auth_manager)

# This works — no input() call, no browser redirect
results = sp.search(q="artist:Radiohead", type="track", limit=10)

Client Credentials flow is a direct POST to https://accounts.spotify.com/api/token with your client ID and secret. Spotify returns an access token valid for 3600 seconds. No user consent screen, no redirect URI, no stdin. The spotipy library handles refresh automatically.

Two things to note. First, store your credentials in Databricks secrets (dbutils.secrets), not in notebook cells. Hardcoded secrets in notebooks get committed to version control, logged in job output, and visible in the Databricks workspace to anyone with read access. Second, Client Credentials tokens cannot access user-private endpoints: saved tracks, playlists marked private, user profile details, playback control. If you need those, read the next section.

OAuth token lifecycle for Databricks jobs Authorize interactively on local machine Browser consent → receive auth code Exchange auth code for access + refresh token Store refresh token in Databricks secrets Databricks job loads refresh token from secrets POST refresh_token grant to API provider Receive new access token (no input() needed) Execute API calls with access token Token revoked? → Job returns 401 → Alert
OAuth token lifecycle for Databricks jobs

Pre-authorize tokens outside Databricks, then inject them

When you genuinely need user-scoped access — pulling a user's saved library, reading private playlists, or writing to a playlist on their behalf — you need an Authorization Code token. But you cannot run that flow inside Databricks. The solution is to separate token acquisition from token usage.

Step 1: Run the OAuth flow locally. On your laptop, execute the authorization once:

from spotipy.oauth2 import SpotifyOAuth

sp_oauth = SpotifyOAuth(
    client_id="YOUR_CLIENT_ID",
    client_secret="YOUR_CLIENT_SECRET",
    redirect_uri="http://localhost:8080/callback",
    scope="user-library-read playlist-read-private",
    cache_path=".spotify_token_cache"
)
token_info = sp_oauth.get_access_token(as_dict=True)

This opens your browser, you consent, and spotipy writes the token (including the refresh token) to .spotify_token_cache.

Step 2: Store the refresh token in Databricks secrets:

databricks secrets put-secret spotify refresh_token --string-value "AQD..."

Step 3: In your Databricks notebook, build a token refresh mechanism that never calls input():

import requests

def refresh_spotify_token():
    refresh_token = dbutils.secrets.get(scope="spotify", key="refresh_token")
    client_id = dbutils.secrets.get(scope="spotify", key="client_id")
    client_secret = dbutils.secrets.get(scope="spotify", key="client_secret")
    response = requests.post(
        "https://accounts.spotify.com/api/token",
        data={
            "grant_type": "refresh_token",
            "refresh_token": refresh_token,
            "client_id": client_id,
            "client_secret": client_secret
        }
    )
    return response.json()["access_token"]

sp = spotipy.Spotify(auth=refresh_spotify_token())

This pattern works for any OAuth provider, not just Spotify. The localhost redirect URI that the original community poster used is fine for the local step — it just cannot run inside Databricks.

The MemoryCacheHandler sidestep for spotipy specifically

Spotipy's architecture has a cleaner escape hatch than raw requests.post calls: the CacheHandler interface. Instead of letting spotipy try to read and write .cache files (which also causes permission issues on DBFS), inject a MemoryCacheHandler pre-loaded with your token info.

from spotipy.oauth2 import SpotifyOAuth
from spotipy.cache_handler import MemoryCacheHandler

token_info = {
    "access_token": "current_access_token_here",
    "refresh_token": dbutils.secrets.get(scope="spotify", key="refresh_token"),
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "user-library-read playlist-read-private",
    "expires_at": 0  # Forces immediate refresh
}

cache_handler = MemoryCacheHandler(token_info=token_info)
auth_manager = SpotifyOAuth(
    client_id=dbutils.secrets.get(scope="spotify", key="client_id"),
    client_secret=dbutils.secrets.get(scope="spotify", key="client_secret"),
    redirect_uri="http://localhost:8080/callback",
    scope="user-library-read playlist-read-private",
    cache_handler=cache_handler,
    open_browser=False
)

sp = spotipy.Spotify(auth_manager=auth_manager)

Setting expires_at to 0 forces spotipy to refresh the token using the refresh token on the first API call. Because the refresh grant is a server-to-server POST, it never triggers input(). The open_browser=False flag prevents the fallback browser-open attempt, which also fails silently in Databricks since there is no desktop environment.

This approach keeps you within spotipy's managed token lifecycle — automatic refresh, scope validation, retry logic — without the StdinNotImplementedError. It also avoids the file system issues that CacheFileHandler causes on cluster-attached storage, where the working directory may reset between job runs or across cluster restarts.

Silent token expiration is the real production risk

The community thread ended without a confirmed resolution, which is typical. Someone gets the notebook working, moves on, and schedules it as a Databricks job. Then it fails silently three weeks later.

Here is why. Spotify refresh tokens issued under the Authorization Code flow do not expire on a fixed schedule, but they can be revoked: when the user changes their password, when the app's API credentials are rotated, or when Spotify invalidates tokens during security events. When that happens, the refresh call returns HTTP 400 with {"error": "invalid_grant"}. Your job fails. If you are not monitoring job outcomes, the data just stops updating.

For Databricks jobs pulling from external APIs, the failure mode is not an immediate crash — it is stale data. The job runs, the token refresh fails, the API call returns 401, and depending on your error handling the job might even succeed with zero rows written. Downstream tables look current because the job timestamp updates. The data does not.

MetricSign monitors Databricks job outcomes and flags runs where output volume drops to zero or deviates from historical patterns. Instead of discovering three weeks later that your Spotify pipeline silently stopped ingesting data, you get an alert on the first anomalous run with context about what changed.

The broader lesson applies beyond Spotify. Any Databricks job that depends on external OAuth tokens — Salesforce, HubSpot, Google Analytics, Stripe — carries the same risk. Build token validation into your pipeline: check the HTTP status of a lightweight API call before running the full extraction, and raise an explicit exception on 401. Do not let a silent auth failure masquerade as an empty result set.

Other libraries that throw the same StdinNotImplementedError

Spotipy is the most-discussed case, but any Python package that calls input() at runtime will fail identically in Databricks. Common culprits include:

The google-auth-oauthlib library's InstalledAppFlow.run_console() method, which prompts the user to visit a URL and paste back an authorization code. In Databricks, use google.oauth2.service_account.Credentials.from_service_account_info() instead, loading the service account JSON from Databricks secrets.

The tweepy library's OAuth 1.0a flow (OAuthHandler.get_authorization_url() followed by input() for the verifier PIN). Use Bearer Token authentication (tweepy.Client with a bearer token) for read-only access, or pre-authorize and store tokens.

Any custom script that uses input() for confirmation prompts like input('Are you sure you want to delete? y/n'). Replace these with explicit boolean parameters or configuration flags when running in automated environments.

The fix is always the same structural change: separate the interactive human step from the automated compute step. Authenticate where you have a browser and a keyboard. Execute where you have compute and a scheduler. Pass tokens — never prompts — between the two.

You can detect this proactively by searching your codebase for input( calls in any module that will run on Databricks:

import inspect, spotipy
source = inspect.getsource(spotipy.oauth2)
assert 'input(' not in source, 'This module uses input() — will fail in Databricks'

That assertion is crude but effective as a pre-flight check in your notebook's setup cell.

Frequently asked questions

Can I patch input() in Databricks to make OAuth flows work?+
Technically you can monkey-patch builtins.input to return a hardcoded string, but this is fragile and dangerous. The OAuth flow expects a valid redirect URL with an authorization code, which changes every time. A hardcoded response would either fail or reuse an expired code. The correct approach is to use a non-interactive auth flow (Client Credentials) or pre-authorize tokens externally and inject them via secrets.
Why does open_browser=False not fix the StdinNotImplementedError?+
The open_browser=False flag only prevents spotipy from calling webbrowser.open(). It does not disable the input() fallback. When spotipy cannot open a browser and has no cached token, it prints the authorization URL and calls input() to ask you to paste the redirect URL. That input() call is what triggers StdinNotImplementedError. You need to either use Client Credentials or supply a pre-cached token via MemoryCacheHandler.
Do Spotify refresh tokens expire?+
Spotify refresh tokens do not have a fixed expiration time, but they can become invalid. Password changes, app credential rotation, explicit revocation by the user, and Spotify security events can all invalidate a refresh token. When this happens, the refresh call returns HTTP 400 with error invalid_grant. Your Databricks job needs explicit error handling for this case — otherwise it silently produces empty results.

Related integrations

Related articles