Chalk implements OAuth for authentication to the online query interface. Two kinds of credentials can be used to access Chalk resources:

  • Personal Credentials: Full access to all resources on your account. This allows a client to act as you.
  • Service Credentials: Access scoped to a specific project and environment. Used for computers to talk to Chalk. Generated via the web dashboard.

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.


Authenticating an API client

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,
    ],
)


Authenticating CURL

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

Fetching an Access Token

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:

Request

POST
https://api.chalk.ai/v1/oauth/token
Attributes
client_idstring
Your client_id
client_secretstring
Your client_secret
grant_typeclient_credentials
The grant_type field must always be "client_credentials".

Response

Attributes
access_tokenstring
The access_token that you should use in the Authorization header for authenticated requests.
expires_inint
Number of seconds until the access_token expires.
token_typestring
This field will always be "Bearer"

Creating a Service Token in the Dashboard

You 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.

Service Token Creation


Authenticating a Request Using an Access Token

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.


Role-based access control (RBAC)

Use role-based access control to limit which data an agent can use when it queries Chalk. Access control comes in two flavors:

  • Datasource permissions gate access to the datasources used by a query.
  • Feature permissions gate access to the features computed and returned by a query.

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.

Shared tag model

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

Default permission

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:

  • Default Allow — features are returnable and datasources are accessible unless their tags specify otherwise. This is the default.
  • 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.
  • Default 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

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

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.

PermissionUsableReturnableDeclassifies
DenyNoNoNo
AllowInternalYesNoNo
AllowYesYesNo
AllowDownstreamYesYesYes
  • Usable — can be computed and used as an input to other features (i.e. appear in the query plan).
  • Returnable — can be returned to the query’s caller.
  • Declassifies — clears the features derived from it, making them returnable too.

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: the declassification boundary

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 returnable

The override does not apply when the inputs are mixed. Take a feature computed from three inputs that resolve to Allow, AllowDownstream, and Deny:

  • The Deny input taints the result — any Deny in the lineage forces the output to Deny, so it is blocked regardless of the AllowDownstream input.
  • Even setting the 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.

Best practices

  • Use datasource permissions for coarse access boundaries, such as a warehouse, database, vendor, region, or product area.
  • Use feature permissions when sensitive and non-sensitive data live in the same datasource or when policy depends on the meaning of a specific feature.
  • Prefer defining datasource permission tags in the dashboard. Dashboard changes are permission-gated and take precedence over code-defined tags; use code tags only as intentional fallbacks.
  • Keep datasource and feature tag names distinct unless you intentionally want one role/token entry to control both.
  • Use AllowInternal and AllowDownstream for feature-level behavior only. They do not grant datasource access.
  • To keep feature policies easy to audit, prefer declaring feature permission tags in one central place. See Centrally manage feature-permission tags.