Programmatic access from outside Snowflake
These instructions help you call an API or a Model Context Protocol (MCP) server that you deployed to Posit Connect. The Posit Team Native App manages that installation, hosted in Snowflake Snowpark Container Services (SPCS).
These instructions apply when the caller runs outside Snowflake, such as a script on your laptop or an MCP client in your editor. Use the instructions in Programmatic access from within Snowflake when your code runs inside the Native App, in a Workbench session or in content on Connect.
How authentication works
Each request passes two authentication layers. Snowflake authorizes reaching the endpoint, and Connect authorizes the caller against the content’s access settings.
| Layer | Header | Value |
|---|---|---|
| Snowflake ingress | Authorization |
Snowflake Token="<snowflake-token>" |
| Connect | X-RSC-Authorization |
Key <connect-api-key> |
The Snowflake token is either a session token generated from a connection or a programmatic access token. Both travel in the same header.
Snowflake reads the Authorization header, so Connect reads its API key from the X-RSC-Authorization header instead. Connect gives X-RSC-Authorization priority over Authorization.
Credentials
Connect API key
Visit your Connect installation and provision an API key.
Snowflake connection
A connection in your connections.toml file, described in Publish from outside Snowflake, is the credential we recommend. Snowflake connections support key-pair authentication, programmatic access tokens, OAuth tokens, and externalbrowser sign-in, so one script covers both interactive and automated use. The Python connector exchanges whichever authenticator you configured for a short-lived session token.
In R, the snowflakeauth package does the same exchange for connections that use key-pair authentication or an OAuth token. It does not support externalbrowser sign-in against a service endpoint. Such a connection produces a credential the endpoint does not accept, and the endpoint answers with a sign-in page rather than an authentication error.
Snowflake programmatic access token
A programmatic access token is a single Snowflake credential that you send as a header, which makes it convenient for a quick test. MCP clients send a fixed set of headers, so they require a token. A token expires after 15 days by default, and you can set an expiry of up to 365 days.
In Snowsight, go to Governance & security > Users & roles, select your user, and click Generate new token under Programmatic access tokens. You can create the same token in SQL.
Snowflake SQL
ALTER USER IF EXISTS <user> ADD PROGRAMMATIC ACCESS TOKEN <token-name>
ROLE_RESTRICTION = '<role>'
DAYS_TO_EXPIRY = 30;The statement prints the token in the token_secret column. Snowflake shows that value once, so store it somewhere safe.
Restrict the token to a role that can reach the endpoint, which is the same role you use to open Connect in a browser. A token restricted to a role without that access reaches Snowflake and then stops there.
A service endpoint expects a programmatic access token in the same Snowflake Token= header as a session token. The Snowflake tutorial on sending requests to a service endpoint with a programmatic access token shows the same header. The Bearer header shown in the token documentation applies to the Snowflake REST APIs.
Allow token authentication
A Snowflake administrator must configure the account before token authentication works. A person can generate a programmatic access token without a network policy, but cannot authenticate with one. Until the account allows it, requests fail with 401 Network policy is required.
An administrator either subjects the user to a network policy, or applies an authentication policy that relaxes the requirement.
Snowflake SQL
CREATE AUTHENTICATION POLICY pat_auth_policy
PAT_POLICY = (NETWORK_POLICY_EVALUATION = ENFORCED_NOT_REQUIRED);
ALTER USER "<user>" SET AUTHENTICATION POLICY pat_auth_policy;ENFORCED_NOT_REQUIRED drops the requirement for a network policy, and still enforces any policy that already applies to the user. Use NOT_ENFORCED in place of it when a policy that applies to the user rejects the address the requests come from. That value stops Snowflake from evaluating network policies during token authentication, so weigh it against the network controls your account relies on.
Set the values in your environment
The examples read your credentials from the environment rather than holding them in the file, which is the convention the Connect cookbook follows. CONNECT_SERVER holds the ingress URL of your Connect installation. Export the variables your credential needs: a Snowflake connection needs the first two, and a programmatic access token needs all three.
Terminal
export CONNECT_SERVER="https://<instance-id>-<account-id>.snowflakecomputing.app"
export CONNECT_API_KEY="<your-connect-api-key>"
export SNOWFLAKE_PAT="<your-snowflake-pat>"Dependencies
- requests makes the HTTP requests in the Python examples.
- snowflake-connector-python generates a session token from a Snowflake connection. The programmatic access token example does not need it.
- httr2 makes the HTTP requests in the R examples.
- snowflakeauth generates a session token from a Snowflake connection in R. The programmatic access token example does not need it.
- Node.js provides the
npxcommand that Claude Desktop uses to runmcp-remote. The other MCP clients do not need it.
Terminal
python -m pip install requests snowflake-connector-pythonR console
install.packages(c("httr2", "snowflakeauth"))Call an API
Connect serves deployed content at https://<instance-id>-<account-id>.snowflakecomputing.app/content/<content-guid>/, followed by the path your API exposes.
Each example sets both authentication headers once, then makes ordinary HTTP requests.
Authenticate with a Snowflake connection, which keeps one script working for both a person and an unattended job. Reach for a programmatic access token when you want a single header and no extra package, such as a quick test from a shell.
This variant generates a session token from the connection named in CONNECTION_NAME. The token expires within the hour, so construct a new ConnectSession in a long-running program.
call_api.py
import os
import requests
import snowflake.connector
CONNECTION_NAME = "default"
CONNECT_SERVER = os.environ["CONNECT_SERVER"]
CONNECT_API_KEY = os.environ["CONNECT_API_KEY"]
API_URL = f"{CONNECT_SERVER}/content/<content-guid>/path/to/api"
class ConnectSession(requests.Session):
"""A requests session that authenticates to Connect hosted in Snowflake."""
def __init__(self, connection_name, api_key):
super().__init__()
with snowflake.connector.connect(
connection_name=connection_name,
session_parameters={"PYTHON_CONNECTOR_QUERY_RESULT_FORMAT": "json"},
) as connection:
# Keep the token usable after this connection closes.
connection._server_session_keep_alive = True
token = connection._rest._token_request("ISSUE")["data"]["sessionToken"]
# Snowflake reads the Authorization header, so Connect reads the API
# key from X-RSC-Authorization.
self.headers["Authorization"] = f'Snowflake Token="{token}"'
self.headers["X-RSC-Authorization"] = f"Key {api_key}"
session = ConnectSession(CONNECTION_NAME, CONNECT_API_KEY)
response = session.get(API_URL)
response.raise_for_status()
print(response.json())With a programmatic access token, you do not need the connector.
call_api.py
import os
import requests
CONNECT_SERVER = os.environ["CONNECT_SERVER"]
CONNECT_API_KEY = os.environ["CONNECT_API_KEY"]
SNOWFLAKE_PAT = os.environ["SNOWFLAKE_PAT"]
API_URL = f"{CONNECT_SERVER}/content/<content-guid>/path/to/api"
class ConnectSession(requests.Session):
"""A requests session that authenticates to Connect hosted in Snowflake."""
def __init__(self, snowflake_pat, api_key):
super().__init__()
# Snowflake reads the Authorization header, so Connect reads the API
# key from X-RSC-Authorization.
self.headers["Authorization"] = f'Snowflake Token="{snowflake_pat}"'
self.headers["X-RSC-Authorization"] = f"Key {api_key}"
session = ConnectSession(SNOWFLAKE_PAT, CONNECT_API_KEY)
response = session.get(API_URL)
response.raise_for_status()
print(response.json())Requests made with this session behave like any other requests session, which means session.post(API_URL, json={"n": 1}), query parameters, and timeouts work as usual. The session also reaches every other URL on your Connect installation, including the Connect server API.
call_api.py
# List the content on this Connect installation, including each content GUID.
response = session.get(f"{CONNECT_SERVER}/__api__/v1/content")snowflake_credentials() returns the Authorization header for the endpoint named in spcs_endpoint. It uses the default role of the connection, and takes a role argument when that role cannot reach the endpoint.
R console
library(httr2)
library(snowflakeauth)
connection_name <- "default"
connect_api_key <- Sys.getenv("CONNECT_API_KEY")
connect_server <- Sys.getenv("CONNECT_SERVER")
api_url <- paste0(connect_server, "/content/<content-guid>/path/to/api")
credentials <- snowflake_credentials(
snowflake_connection(connection_name),
spcs_endpoint = connect_server
)
headers <- c(
credentials,
list(`X-RSC-Authorization` = paste("Key", connect_api_key))
)
req <- request(api_url)
req <- req_headers(req, !!!headers)
resp <- req_perform(req)
resp_body_json(resp)With a programmatic access token, you do not need snowflakeauth. Write the same header yourself.
R console
library(httr2)
connect_api_key <- Sys.getenv("CONNECT_API_KEY")
snowflake_pat <- Sys.getenv("SNOWFLAKE_PAT")
connect_server <- Sys.getenv("CONNECT_SERVER")
api_url <- paste0(connect_server, "/content/<content-guid>/path/to/api")
headers <- list(
Authorization = sprintf('Snowflake Token="%s"', snowflake_pat),
`X-RSC-Authorization` = paste("Key", connect_api_key)
)
req <- request(api_url)
req <- req_headers(req, !!!headers)
resp <- req_perform(req)
resp_body_json(resp)The request behaves like any other httr2 request, so req_body_json(), query parameters, and timeouts work as usual. It also reaches every other URL on your Connect installation, including the Connect server API.
R console
# List the content on this Connect installation, including each content GUID.
req <- request(paste0(connect_server, "/__api__/v1/content"))
req <- req_headers(req, !!!headers)
resp <- req_perform(req)Use an MCP server
Connect serves an MCP server at its content URL followed by /mcp.
MCP clients send a fixed set of headers with every request, so they need a programmatic access token rather than a session token. They also need the account configuration described in Allow token authentication. Each client reads the credentials from SNOWFLAKE_PAT and CONNECT_API_KEY, so export them as shown in Set the values in your environment before starting the client. Visual Studio Code prompts for the values instead of reading the environment.
Connect can authenticate MCP clients with OAuth, but that flow is unavailable through Snowflake. The client sends its Connect OAuth token in the Authorization header, which Snowflake has already claimed for its own authentication. Use a programmatic access token and an API key instead.
Add the server to your project configuration. Single quotes keep the environment variable names in the file rather than writing your credentials into it.
Terminal
claude mcp add --transport http --scope project my-mcp-server \
"https://<instance-id>-<account-id>.snowflakecomputing.app/content/<content-guid>/mcp" \
--header 'Authorization: Snowflake Token="${SNOWFLAKE_PAT}"' \
--header 'X-RSC-Authorization: Key ${CONNECT_API_KEY}'That command writes the following configuration, which you can also edit directly.
.mcp.json
{
"mcpServers": {
"my-mcp-server": {
"type": "http",
"url": "https://<instance-id>-<account-id>.snowflakecomputing.app/content/<content-guid>/mcp",
"headers": {
"Authorization": "Snowflake Token=\"${SNOWFLAKE_PAT}\"",
"X-RSC-Authorization": "Key ${CONNECT_API_KEY}"
}
}
}
}Posit Assistant reads MCP server definitions from ~/.posit/assistant/settings.json for all projects, or from .posit/assistant/settings.json within one project.
~/.posit/assistant/settings.json
{
"mcpServers": {
"my-mcp-server": {
"type": "remote",
"url": "https://<instance-id>-<account-id>.snowflakecomputing.app/content/<content-guid>/mcp",
"headers": {
"Authorization": "Snowflake Token=\"{env:SNOWFLAKE_PAT}\"",
"X-RSC-Authorization": "Key {env:CONNECT_API_KEY}"
}
}
}
}Visual Studio Code prompts for each input the first time it starts the server, then stores the values securely.
.vscode/mcp.json
{
"inputs": [
{
"id": "snowflake-pat",
"type": "promptString",
"description": "Snowflake programmatic access token",
"password": true
},
{
"id": "connect-api-key",
"type": "promptString",
"description": "Connect API key",
"password": true
}
],
"servers": {
"my-mcp-server": {
"type": "http",
"url": "https://<instance-id>-<account-id>.snowflakecomputing.app/content/<content-guid>/mcp",
"headers": {
"Authorization": "Snowflake Token=\"${input:snowflake-pat}\"",
"X-RSC-Authorization": "Key ${input:connect-api-key}"
}
}
}
}Claude Desktop does not send custom headers to a remote MCP server, so route the connection through mcp-remote, which runs locally and adds them. Edit ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, or %APPDATA%\Claude\claude_desktop_config.json on Windows.
claude_desktop_config.json
{
"mcpServers": {
"my-mcp-server": {
"command": "npx",
"args": [
"mcp-remote",
"https://<instance-id>-<account-id>.snowflakecomputing.app/content/<content-guid>/mcp",
"--header",
"Authorization:${SNOWFLAKE_HEADER}",
"--header",
"X-RSC-Authorization:${CONNECT_HEADER}"
],
"env": {
"SNOWFLAKE_HEADER": "Snowflake Token=\"<your-snowflake-pat>\"",
"CONNECT_HEADER": "Key <your-connect-api-key>"
}
}
}
}Set each header value in env and reference it from args, because Claude Desktop does not preserve spaces within an argument.
Notes
- Connect identifies the caller by the API key. The Snowflake credential only authorizes reaching the endpoint, so the API key must belong to a user that the content access settings permit.
- Content that allows anonymous access needs no API key. Omit the
X-RSC-Authorizationheader in that case. - The internal address
https://connectdoes not resolve outside the Native App, and a caller inside the application cannot use this ingress URL. Within the application, Connect presents a certificate issued for its internal name, so a request to the ingress URL fails the TLS check before it reaches authentication. _token_requestis an internal function of the Python Snowflake connector, and applies only to the Python example that calls it. Snowflake documents this approach but does not guarantee that it works with future versions of the connector.