Snowflake Cortex AI Token Usage Monitoring: The Complete Guide

Written by

in

Somewhere on your team, an AI_CLASSIFY job is running on a table larger than anyone realised. Or a Cortex Agent is looping through a multi-step workflow that seemed cheap in testing. Or a developer left a search service indexed and running in a dev environment that nobody is querying anymore. None of these will trigger your existing resource monitors. All of them will show up on your AI Credits bill.

If you’ve already read our piece on where the hidden Cortex AI token costs live, you know what you’re paying for. This article is about building the monitoring stack that catches those costs in real time — before they land on the invoice. That means three ACCOUNT_USAGE views, three automation patterns, and a clear understanding of what each one covers and what it misses.

TL;DR

  • The primary monitoring view for AI SQL functions is SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY — generally available since March 2026, with latency as low as 2 minutes and a maximum of 5 minutes. Use it as your canonical source; do not sum it with the older CORTEX_AISQL_USAGE_HISTORY or you will double-count.
  • Cortex Agents have their own view: CORTEX_AGENT_USAGE_HISTORY (GA Feb 25 2026). Each row is one agent request, with aggregated credits plus granular sub-call detail for every tool the agent invoked.
  • Deep observability — traces, spans, conversation threads — lives in SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS. Cortex Search writes here only when REQUEST_LOGGING is enabled on the service. Built-in AI SQL functions do not write to this table.
  • Three automation patterns: account-level monthly spend alerts via Snowflake Alerts, per-user monthly limits enforced by hourly Tasks that auto-revoke and auto-restore access, and runaway query cancellation via SYSTEM$CANCEL_QUERY.
  • The prerequisite for per-user limits is revoking SNOWFLAKE.CORTEX_USER from the PUBLIC role. Without that step, users can bypass all per-user controls by switching to any other role that still carries the database role.
  • Resource Monitors still do not cover AI Credits. You must build Cortex-specific alerting separately against the usage history views.

The Three Views and What Each One Covers

The first thing to get clear is the view taxonomy. Snowflake has iterated on this several times since Cortex launched, and the current state as of mid-2026 is three distinct views with distinct coverage. Using the wrong one doesn’t produce an error — it just produces incomplete data.

ViewCoversLatencyAvailable Since
CORTEX_AI_FUNCTIONS_USAGE_HISTORYAll AI SQL functions: AI_COMPLETE, AI_CLASSIFY, AI_SUMMARIZE, AI_SENTIMENT, AI_TRANSLATE, AI_FILTER, AI_EXTRACT, AI_PARSE_DOCUMENT, AI_AGG, AI_EMBED_TEXT2–5 minNov 17 2025
CORTEX_AGENT_USAGE_HISTORYCortex Agents invoked via the Agent API or CoWork. One row per agent request, includes per-tool sub-call breakdownNear real-timeFeb 25 2026 (GA)
AI_OBSERVABILITY_EVENTS (SNOWFLAKE.LOCAL)Agent traces and spans; Cortex Search request logs (if REQUEST_LOGGING enabled); CoCo spans for every promptVaries by serviceRolling
CORTEX_AISQL_USAGE_HISTORYOlder view, still present. Overlaps with CORTEX_AI_FUNCTIONS_USAGE_HISTORY. Do not sum both.Use new view insteadLegacy
CORTEX_SEARCH_SERVING_USAGE_HISTORYCortex Search serving compute (the continuous GB/month charge)Account Usage latencyOn GA

One critical note on AI_OBSERVABILITY_EVENTS: Snowflake’s AI Observability docs are explicit that built-in AI SQL functions like AI_COMPLETE and AI_CLASSIFY do not write traces to this table. Monitor those with CORTEX_AI_FUNCTIONS_USAGE_HISTORY. The observability table is for agents, CoCo prompts, and search requests — where you need conversation-level detail, not just credit aggregates.

Basic Usage Monitoring Queries

These are your daily driver queries. Run them on a schedule or wire them into a BI dashboard. The official Snowflake cost management docs provide the canonical versions of these patterns — reproduced here with explanatory context.

Daily credit burn by function and model

This is your first view into where tokens are actually going. Sort by ai_credits DESC and your most expensive function-model combination usually jumps out immediately.

-- Daily credit consumption by function and model — last 30 days
-- Canonical source for AI SQL functions
SELECT
  DATE_TRUNC('day', start_time)   AS usage_day,
  function_name,
  model_name,
  SUM(credits)                    AS ai_credits,
  SUM(input_tokens)               AS input_tokens,
  SUM(output_tokens)              AS output_tokens,
  COUNT(DISTINCT query_id)        AS distinct_queries
FROM SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY
WHERE start_time >= DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP BY 1, 2, 3
ORDER BY usage_day DESC, ai_credits DESC;

Monthly spend by user

Join to USERS to get email and default role — makes it far easier to follow up with a specific person when their consumption spikes.

-- Monthly credit consumption by user — last 3 months
SELECT
  DATE_TRUNC('month', h.start_time)  AS usage_month,
  u.name                             AS user_name,
  u.email,
  u.default_role,
  SUM(h.credits)                     AS ai_credits,
  COUNT(DISTINCT h.query_id)         AS distinct_queries
FROM SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY h
JOIN SNOWFLAKE.ACCOUNT_USAGE.USERS u ON h.user_id = u.user_id
WHERE h.start_time >= DATEADD('month', -3, CURRENT_TIMESTAMP())
GROUP BY 1, 2, 3, 4
ORDER BY usage_month DESC, ai_credits DESC;

Cortex Agent attribution

For agent workloads, use CORTEX_AGENT_USAGE_HISTORY separately. Each row covers one agent request and includes granular sub-call detail — you can see exactly which tool leg (Analyst, Search, SQL) consumed the most credits within each request.

-- Agent credit attribution by agent and user — last 30 days
SELECT
  DATE_TRUNC('day', start_time)  AS usage_day,
  agent_id,
  user_id,
  SUM(total_credits)             AS ai_credits,
  COUNT(request_id)              AS requests,
  SUM(input_tokens)              AS input_tokens,
  SUM(output_tokens)             AS output_tokens
FROM SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AGENT_USAGE_HISTORY
WHERE start_time >= DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP BY 1, 2, 3
ORDER BY usage_day DESC, ai_credits DESC;

If you’ve connected AI agents to Snowflake through an MCP server, the agent requests still flow through the same Cortex infrastructure and appear in this view — you don’t need a separate monitoring path for MCP-invoked agents.

Automation Pattern 1: Account-Level Monthly Spend Alert

Resource Monitors don’t cover AI Credits. That means you need a separate alerting mechanism. Snowflake Alerts — the native scheduled condition-check object — are the right tool. The pattern is: a NOTIFICATION INTEGRATION wired to email recipients, an Alert that fires hourly against the usage view, and a stored procedure that sends the email and prevents duplicate alerts within a calendar month.

The key implementation detail from Snowflake’s docs: the alert tracks an AI_FUNCTIONS_ALERT_STATE table to ensure only one email fires per calendar month per alert name. Without that guard, a threshold breach at 9 AM would send 15 hourly emails by midnight. The stored procedure checks the state table first, inserts a record if none exists for the current month, then sends the notification.

Email delivery prerequisite: For SYSTEM$SEND_EMAIL to work, every recipient address must satisfy three conditions simultaneously: listed in ALLOWED_RECIPIENTS on the notification integration, used as the to_email argument in the procedure body, and set as the verified EMAIL field on a Snowflake user in the account. Missing any one of the three produces a generic “not allowed” error with no indication of which condition failed.

-- Minimal alert setup — replace 1000 with your actual threshold
CREATE OR REPLACE NOTIFICATION INTEGRATION ai_cost_alerts
  TYPE = EMAIL
  ENABLED = TRUE
  ALLOWED_RECIPIENTS = ('[email protected]');

-- Alert: fires every hour if monthly spend exceeds threshold
CREATE OR REPLACE ALERT ai_functions_monthly_spend_alert
  WAREHOUSE = 
  SCHEDULE = 'USING CRON 0 * * * * UTC'
  IF (EXISTS (
    SELECT 1
    FROM SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY
    WHERE start_time >= DATE_TRUNC('month', CURRENT_TIMESTAMP())
    HAVING SUM(credits) > 1000  -- adjust threshold
  ))
  THEN
    CALL SEND_MONTHLY_SPEND_ALERT(1000);

ALTER ALERT ai_functions_monthly_spend_alert RESUME;

Automation Pattern 2: Per-User Monthly Spending Limits

Account-level alerts tell you the house is on fire. Per-user limits prevent any single user from starting it. The implementation uses a role gate: access to Cortex AI functions flows through a dedicated AI_FUNCTIONS_USER_ROLE, and an hourly Task revokes that role from any user who exceeds their monthly credit budget. A separate monthly Task restores it on the first of each month.

The critical prerequisite, which the docs call out explicitly: revoke SNOWFLAKE.CORTEX_USER from the PUBLIC role before setting any per-user limits. By default, every user in a Snowflake account has access to Cortex AI through PUBLIC. If you don’t close that hole first, a user who hits their limit on AI_FUNCTIONS_USER_ROLE can simply switch to any other role that still carries the database role — and the hourly revocation does nothing.

-- Step 1: Close the PUBLIC role bypass (run as ACCOUNTADMIN)
USE ROLE ACCOUNTADMIN;
REVOKE DATABASE ROLE SNOWFLAKE.CORTEX_USER FROM ROLE PUBLIC;

-- Audit: confirm no other roles carry it unexpectedly
SHOW GRANTS OF DATABASE ROLE SNOWFLAKE.CORTEX_USER;

-- Step 2: Create the gated access role
CREATE ROLE IF NOT EXISTS AI_FUNCTIONS_USER_ROLE;
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE AI_FUNCTIONS_USER_ROLE;

-- Step 3: Grant access to specific users with individual credit limits
-- (See full GRANT_AI_FUNCTIONS_ACCESS procedure in Snowflake docs)
CALL GRANT_AI_FUNCTIONS_ACCESS('alice_analyst', 1000);  -- 1000 AI Credits/month
CALL GRANT_AI_FUNCTIONS_ACCESS('bob_engineer',  2000);  -- 2000 AI Credits/month

The access control table (AI_FUNCTIONS_ACCESS_CONTROL) tracks each user’s monthly limit, active status, revocation timestamp, and revocation reason. When the hourly MONITOR_AI_FUNCTIONS_SPENDING task runs, it joins the table against CORTEX_AI_FUNCTIONS_USAGE_HISTORY, finds users who have exceeded their limit for the current month, and calls REVOKE ROLE AI_FUNCTIONS_USER_ROLE FROM USER <name> for each. On the first of the next month, MONTHLY_AI_FUNCTIONS_ACCESS_REFRESH restores the role to everyone in the table — no manual intervention needed.

Long-running query exemption: If some users legitimately need to run extended Cortex jobs, create a separate AI_FUNCTIONS_USER_LONG_RUNNING_ROLE and add a NOT ARRAY_CONTAINS check in the revocation procedure’s HAVING clause to exclude queries run under that role from cancellation. Users adopt it explicitly when they need it, keeping the default enforcement tight.

Automation Pattern 3: Runaway Query Detection and Cancellation

The third loop is the most operationally immediate. Runaway queries — AI function calls on unexpectedly large tables, or agents caught in loops — can accumulate significant credits in a single hour. The detection pattern works because CORTEX_AI_FUNCTIONS_USAGE_HISTORY splits usage into one-hour windows and includes an IS_COMPLETED flag. A still-running query across multiple hourly windows has all its rows with IS_COMPLETED = FALSE. Aggregate credits by QUERY_ID, check that no row is completed, and if the sum exceeds your threshold — cancel it.

-- Core detection CTE — finds running queries that have already exceeded the threshold
WITH query_credits AS (
  SELECT
    h.query_id,
    ANY_VALUE(h.user_id)        AS user_id,
    SUM(h.credits)              AS total_credits,
    MIN(h.start_time)           AS first_seen,
    BOOLOR_AGG(h.is_completed)  AS any_completed
  FROM SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY h
  WHERE h.start_time >= DATEADD('hour', -48, CURRENT_TIMESTAMP())
  GROUP BY h.query_id
  HAVING SUM(h.credits) > 50          -- your credit threshold
     AND BOOLOR_AGG(h.is_completed) = FALSE  -- still running
)
SELECT qc.query_id, u.name AS user_name, qc.total_credits, qc.first_seen
FROM query_credits qc
LEFT JOIN SNOWFLAKE.ACCOUNT_USAGE.USERS u ON qc.user_id = u.user_id;

The full implementation in Snowflake’s official cost management docs wraps this into a stored procedure that calls SYSTEM$CANCEL_QUERY for each hit, handles cancellation failures gracefully (logging them as CANCEL FAILED), and sends an email alert with the query ID, user, functions invoked, credits consumed, and warehouse ID. One important note from those docs: cancelling a query stops further accumulation but does not refund credits already billed up to the cancellation point. Early detection is everything.

The Gotchas

Summing CORTEX_AISQL_USAGE_HISTORY and CORTEX_AI_FUNCTIONS_USAGE_HISTORY together double-counts.The older view still exists and returns data. Both cover AI SQL functions. Pick one and discard the other. The newer CORTEX_AI_FUNCTIONS_USAGE_HISTORY is the canonical source — it also covers AI_PARSE_DOCUMENT, which the older view misses.

The CORTEX_AGENT_USAGE_HISTORY view does not break out MCP-specific metadata.Agents invoked via the MCP server appear in this view, but the METADATA column contains interface and role context that varies by invocation path. If you need to distinguish MCP-sourced agent calls from direct API calls, parse the METADATA column and filter by interface type. The view’s REQUEST_ID is your correlation key for tying a row to a specific conversation turn.

Cortex Search’s serving compute does not appear in CORTEX_AI_FUNCTIONS_USAGE_HISTORY.The continuous GB/month idle charge for Cortex Search is a separate billing meter in CORTEX_SEARCH_SERVING_USAGE_HISTORY. If your monitoring queries only touch the functions view, you have a blind spot on one of the most surprising cost items in the Cortex stack. Add a separate daily roll-up query against the search serving view and alert separately.

The 5-minute latency means the hourly Task and Alert windows have a gap.The usage view has up to 5 minutes of latency. An hourly Task that fires at :00 will not see credits consumed at :58. For runaway detection this is mostly fine — you’re looking for hours of accumulation, not minutes. For per-user limits on very tight budgets, factor this in: a user who hits their limit at 11:58 PM may run one more minute before the midnight Task catches them.

QUERY_TAG is your best cost attribution tool — but only if you set it.CORTEX_AI_FUNCTIONS_USAGE_HISTORY includes a QUERY_TAG column. If teams set ALTER SESSION SET QUERY_TAG = 'project:data-quality team:analytics' before their Cortex calls, you can group spend by project or team in your monitoring queries without any schema changes. Without it, you’re attributing by user alone, which falls apart when service accounts or shared roles invoke the functions.

The One Principle

“Build your Cortex monitoring stack before you scale usage, not after the first surprise bill. The views exist, the alert patterns are documented — the only cost is an afternoon of setup.”

FAQ

Do Snowflake Resource Monitors cover Cortex AI Credits?

No. Resource Monitors only track Platform Credits consumed by virtual warehouses. Cortex AI Credits are a separate billing currency and require separate monitoring via CORTEX_AI_FUNCTIONS_USAGE_HISTORY and Snowflake Alerts. This is the most common gap in Cortex cost governance — teams assume their existing resource monitors will catch AI overage, and they don’t.

Which view should I use to monitor all Cortex AI costs in one place?

No single view covers everything. Use CORTEX_AI_FUNCTIONS_USAGE_HISTORY for AI SQL functions, CORTEX_AGENT_USAGE_HISTORY for agent workloads, and CORTEX_SEARCH_SERVING_USAGE_HISTORY for the Search idle serving charge. Join or union them in a dashboard for a complete picture, but never sum the older CORTEX_AISQL_USAGE_HISTORY alongside the newer functions view — that creates double-counting.

How do I set per-user spending limits for Cortex AI?

The approach is role-based: revoke SNOWFLAKE.CORTEX_USER from the PUBLIC role, create a dedicated AI_FUNCTIONS_USER_ROLE, and grant it only to users you’ve provisioned in an access control table with individual monthly credit limits. An hourly Snowflake Task then queries CORTEX_AI_FUNCTIONS_USAGE_HISTORY, identifies users who have exceeded their limit, and revokes the role automatically. A second monthly Task restores access on the first of each month.

Can I cancel a runaway Cortex AI query automatically?

Yes, using SYSTEM$CANCEL_QUERY called from a stored procedure that an hourly Task triggers. The detection logic aggregates credits by query ID across hourly windows in CORTEX_AI_FUNCTIONS_USAGE_HISTORY and checks that BOOLOR_AGG(is_completed) = FALSE — confirming the query is still running. Cancellation stops further accumulation but does not refund credits already consumed up to that point.

How do I monitor Cortex Agents specifically?

Use SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AGENT_USAGE_HISTORY, which went GA on February 25 2026. Each row represents one agent request and includes both aggregated credit totals and granular sub-call detail for every tool the agent invoked (Analyst, Search, SQL). For conversation-level traces and spans, query SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS using the request ID as the correlation key.

What is QUERY_TAG and why does it matter for Cortex monitoring?

QUERY_TAG is a session-level metadata field that appears in CORTEX_AI_FUNCTIONS_USAGE_HISTORY. When your pipelines set it with ALTER SESSION SET QUERY_TAG = 'project:X team:Y' before Cortex calls, you can group token spend by project, team, or feature in your monitoring queries without any schema changes. Without it, you’re limited to attributing costs by user ID, which breaks down for service accounts and shared roles.

Related reading: Identifying hidden Cortex AI token costs · Using MCP Servers with Snowflake · Governing AI agents in Snowflake · Building RAG with Cortex Search · Snowflake Cortex AI cost management (official) · Snowflake AI Observability docs (official)