Queries
Access the Chalk API.
Chalk implements OAuth for authentication to the online query interface. Two kinds of credentials can be used to access Chalk resources:
Both personal and service credentials can be used to query Chalk, and potentially to modify your Chalk deployment’s settings. This means that these credentials are sensitive and must be kept secret.
You can create and manage service credentials in the Chalk dashboard or using the Chalk CLI.
When you use the CLI to create credentials, you will be asked to authenticate yourself on Chalk’s web dashboard.
Then, you will receive a client_id and client_secret. Once generated, client_id cannot be changed. However,
client_secret can be rotated if your security practices require this or if you suspect that client_secret has
been compromised.
Once you have generated your client_id and client_secret, you can make authenticated requests to Chalk.
Chalk has published API client libraries for several languages. These libraries handle exchanging a client_id
and client_secret for an access_token which can be used to access Chalk.
from chalk.client import ChalkClient
client = ChalkClient(client_id="...", client_secret="...")
client.query(
input={
User.id: "1",
},
output=[
User.identity.is_voip_phone,
User.fraud_score,
],
)
We recommend using the chalk cli tool to authenticate a curl request. You can use chalk token to acquire an
access_token that is suitable for use as a Bearer token:
curl -H "Authorization: Bearer $(chalk token)" \
https://api.chalk.ai/v1/who-am-i
If you’re implementing a custom API client for a language that Chalk hasn’t published a library for, you may need to
fetch an access_token using the OAuth Client Credentials
grant flow. You can use the token endpoint in Chalk’s API to execute this flow:
client_idstringclient_secretstringgrant_typeclient_credentialsaccess_tokenstringexpires_ininttoken_typestringYou can create a service token in the Chalk dashboard by navigating to the “Service Tokens” tab in the “Settings” page. You can then specify permissions for the token, as well as datasource and feature tags for datasource-based and feature-based RBAC (Role-Based Access Control), respectively.

Use the token obtained from the Client Credentials grant flow in the Authorization: Bearer <ACCESS_TOKEN> header
that your client sends along all authenticated requests. For example:
curl -H "Authorization: Bearer <ACCESS_TOKEN>" https://api.chalk.ai/v1/who-am-i
will return a 200 response and a JSON object containing a short description of the requesting user. This is
convenient for verifying that you are using a valid access_token.
Use role-based access control to limit which data an agent can use when it queries Chalk. Access control comes in two flavors:
Both systems are tag-based. Use datasource permissions for broad boundaries such as warehouses, databases, vendors, regions, or product areas. Use feature permissions when sensitive and non-sensitive values live in the same datasource and need feature-level control.
Datasources and features carry tags, and roles or service tokens map those tags to permissions.
Features use the standard feature tags argument:
from chalk.features import feature, features
@features
class User:
id: int
ssn: str = feature(tags=["pii"])
Datasources use permission_tags:
from chalk.sql import SnowflakeSource
signups = SnowflakeSource(
name="SIGNUPS",
permission_tags=["warehouse:signups"],
)
The same tag-permission map is shared by datasource permissions and feature permissions. For
example, a role or service token can map pii to AllowInternal and warehouse:signups to
Allow.
When Chalk checks a resource, tags that are not configured on the role or token are ignored. If multiple tags have explicit permissions, the most restrictive wins:
Deny > AllowInternal > Allow > AllowDownstream
If none of a datasource or feature’s tags have an explicit permission, Chalk uses the role or service token’s default permission. The default does not participate in the most-restrictive comparison when at least one tag has an explicit permission.
You can set the default to Allow, AllowInternal, or Deny, but not AllowDownstream:
Allow — features are returnable and datasources are accessible unless their tags
specify otherwise. This is the default.AllowInternal — features may be computed and used as intermediate inputs, but cannot
be returned. Datasources are denied unless a tag explicitly maps to Allow.Deny — features cannot be computed or returned, and datasources cannot be accessed,
unless their tags explicitly grant the required access.A query is permitted only if the requesting agent has access to every datasource and feature needed by the query plan. Datasource permissions are checked for each selected datasource. Feature permissions are checked for requested outputs and for the intermediate features needed to compute them.
To keep policies easy to audit, avoid reusing the same tag name for unrelated datasource and feature
policies. For example, prefer separate names such as warehouse:signups and feature:pii when the
tags represent different access boundaries.
Datasource permissions control whether a query can use a datasource at all. A datasource permission
tag grants access only when it maps to Allow. Datasource permission tags currently apply only to
SQL datasources. Dashboard-defined tags are available for the supported SQL integrations shown on
the Data Sources page.
The only datasource outcomes are allow and deny. AllowInternal and AllowDownstream are
feature-specific permissions, so they do not grant datasource access. Because the same tag can be
used by features and datasources, Chalk treats Deny, AllowInternal, and AllowDownstream as
denied for datasources rather than accidentally granting datasource access through a
feature-specific permission.
If a datasource has no tags with explicit permissions on the role or token, Chalk uses the default
permission. Only default Allow grants access to that datasource.
You can define datasource permission tags in code:
from chalk.sql import SnowflakeSource
signups = SnowflakeSource(
name="SIGNUPS",
permission_tags=["warehouse:signups"],
)For supported integrations, we recommend defining datasource permission tags in the Chalk dashboard. Changing a dashboard integration requires permission to manage datasources, so the dashboard provides a permission-gated access-policy boundary. It is also a natural place for the policy when it owns the full datasource configuration, such as account IDs, authentication, or warehouse details.
A dashboard-defined datasource still needs to be referenced by name in code when it is used by a Chalk app. Use an explicit datasource name and make sure the dashboard integration name and ChalkPy datasource name match exactly, including case:
from chalk.sql import SnowflakeSource
signups = SnowflakeSource(name="SIGNUPS")Changes to datasource permission tags in the dashboard take effect only after the next deployment. Chalk resolves the effective tags and injects them into the query servers at deploy time, so redeploy the environment after adding, changing, or removing dashboard-defined permission tags for those changes to take effect.
When code and the dashboard both define permission tags for the same datasource, the complete non-empty dashboard tag set takes precedence. Code-defined tags are used only when that datasource has no dashboard-defined tags. Consequently, removing all tags in the dashboard exposes any code-defined fallback tags after the next deployment; remove those code tags too if the datasource should remain untagged.
Datasource permission tags are different from routing tags. Routing tags, shown in the dashboard
as Routing Tags: Datasource Query Tags, select which datasource route to use for a query.
Permission tags authorize access to the datasource that was selected. The permission-tag mapping is
encoded on the requesting agent through its assigned role or service token.
Feature permissions control which features can be computed and returned. You tag your features, then a service token or role maps each tag to one of four permissions.
| Permission | Usable | Returnable | Declassifies |
|---|---|---|---|
Deny | No | No | No |
AllowInternal | Yes | No | No |
Allow | Yes | Yes | No |
AllowDownstream | Yes | Yes | Yes |
Allow, AllowInternal, and Deny each apply to the single feature they tag:
Allow — the feature can be computed and returned to the caller.Deny — the feature cannot be computed or used at all; any query that needs it, whether as an
output or as an intermediate input, fails.AllowInternal — the feature can be computed and used as an input to other features, but is
never returned to the caller. Use it for sensitive raw values that should stay inside the query.These three grant or deny access to that one tagged feature only — the permission does not flow to
features computed from it. AllowDownstream is the exception: a feature tagged AllowDownstream is
returnable like Allow, but it also acts as a declassification boundary, clearing the features
derived from it. Because that behavior spans the feature graph, it has its
own section below.
A query is permitted only if every requested output feature resolves to Allow or
AllowDownstream; an AllowInternal or Deny output fails the query.
AllowDownstream is the one permission that crosses the feature graph. A feature tagged
AllowDownstream is returnable, and it declassifies features derived from it — but with a strict
rule: a derived feature is cleared (resolves to AllowDownstream, and so becomes returnable) only
when every one of its inputs is AllowDownstream. Declassification therefore propagates along chains
whose entire input set is cleared, and stops the moment a non-cleared input is mixed in.
That same all-inputs rule is what lets AllowDownstream override a Deny: a feature tagged Deny
is still cleared if all of its inputs are AllowDownstream, because the output is then a pure
derivative of already-cleared data.
The canonical use case is an aggregation over sensitive data: the individual records are private,
but a statistic over them is safe to expose. Here the raw transaction amounts are PII
(AllowInternal, never returned), and the per-user average over them is declassified:
from chalk import _
from chalk.features import features, feature, DataFrame
@features
class Transaction:
id: int
user_id: "User.id"
# AllowInternal: usable to compute other features, but never returned on its own.
amount: float = feature(tags=["pii"])
@features
class User:
id: int
transactions: DataFrame[Transaction]
# AllowDownstream: an aggregate over sensitive amounts, safe to expose.
avg_transaction_amount: float = feature(
tags=["cleared"],
expression=_.transactions[_.amount].mean(),
)With a token mapping pii -> AllowInternal and cleared -> AllowDownstream:
$ chalk query --in user.id=1 --out user.avg_transaction_amount # ok: cleared by AllowDownstream
$ chalk query --in transaction.id=1 --out transaction.amount # rejected: AllowInternal, not returnableThe override does not apply when the inputs are mixed. Take a feature computed from three inputs
that resolve to Allow, AllowDownstream, and Deny:
Deny input taints the result — any Deny in the lineage forces the output to Deny, so it is
blocked regardless of the AllowDownstream input.Deny input aside, a mix of Allow and AllowDownstream is not
all-AllowDownstream, so the result is not cleared: it falls back to its own tag (or the default)
and does not declassify features further downstream.In short, AllowDownstream clears a derivative only when the entire lineage feeding it is cleared.
AllowInternal and AllowDownstream for feature-level behavior only. They do not grant
datasource access.