AI usage and cost attribution across agents, CoWork, Cortex Code, and AI functions¶
One reconciled view of Cortex AI credits by surface, user, agent, model, and cost center, plus budgets and per-user quotas.
| Stage | Operate |
| Topics | AI Observability, Cortex Code, Cortex AI Functions |
| Status | GA |
| Time to complete | 30min |
| Edition | Enterprise |
| Regions | Any region with Cortex AI usage views in ACCOUNT_USAGE |
| Privileges | ACCOUNTADMIN for 01_setup.sql, 04_budget_and_quota.sql and 99_teardown.sql; everything else runs as a read-only COOKBOOK_* role |
| Related skills | cost-intelligence (bundled with Cortex Code) |
| Last verified | 2026-10-04 |
| Authors | Parker Erickson, Bala Krishnamoorthy |
| Code | View all files or download ai-usage-cost-attribution.zip, which includes this page as README.md. Teardown: sql/99_teardown.sql |
This recipe is a reference implementation: review the code and adapt it to your environment before production use.
Build a small set of views over SNOWFLAKE.ACCOUNT_USAGE that answer “who is spending AI credits, on which surface, with which model, and for which cost center”, and that reconcile to the credits on your bill. The recipe then adds the two spend controls Snowflake offers for AI: a tag-based budget that runs your own action, and a per-user quota that blocks users on its own.
When to use this¶
- You need showback or chargeback for Cortex Agents, Snowflake CoWork, Cortex Code (CoCo), AI functions, and Cortex Analyst in one place.
- You want to know which interface drives agent traffic (SQL, REST/external, MCP, Snowsight, Teams).
- You want to set a spend limit and need to choose between a budget and a quota.
When not to use this:
- One-off questions such as “what did AI cost last week”. Ask the bundled
cost-intelligenceskill in Cortex Code instead. - Organization-wide reporting across accounts. The same views exist in
ORGANIZATION_USAGE(organization account only, higher latency); this recipe covers one account. - Per-request tracing of what an agent did. See diagnose-agent-requests.
How it works¶
Key concepts:
- One source view per billing service type. Each usage view maps to one service type in
METERING_DAILY_HISTORY(agents toCORTEX_AGENTS, CoWork toSNOWFLAKE_COWORK, Cortex Code to theSNOWFLAKE_COCO*types, AI functions toAI_FUNCTIONS).V_RECONCILE_METERINGproves the totals match the bill, which is the check that nothing is counted twice. CoWork requests are not in the agent view and agent requests are not in the CoWork view, so aUNION ALLof the two is safe.SNOWFLAKE_COCO_USAGE_HISTORYalready covers the CLI, Desktop, and Snowsight interfaces, so the per-interfaceCORTEX_CODE_*views are left out. - Nested token detail. In the agent and CoWork views,
TOKENS_GRANULARandCREDITS_GRANULARare arrays of{sub_request_id: {service_type: {model: {input, cache_read_input, cache_write_input, output}}, start_time}}. The recipe flattens four levels and keeps only object values, which drops the siblingstart_timekey.TOKENSon the row equals the sum of all four categories. - Cache ratio.
inputcounts only the uncached input tokens, so the prompt-cache ratio iscache_read_input / (input + cache_read_input + cache_write_input). Dividing byinputalone gives numbers far above 100%. - Two kinds of credits per agent request.
TOKEN_CREDITSis AI spend.METADATA:sql_query_creditsis warehouse compute run by the request (for example, Cortex Analyst SQL) and is billed as warehouse metering. The views keep them in separate columns so AI totals stay reconcilable. - Budgets observe, quotas block. A budget evaluates periodically and calls your stored procedure at a threshold; after the limit is crossed, the action can take up to 8 hours (2 hours with the latency-optimized option). A per-user quota blocks further AI requests from a user within minutes of reaching the limit, with no code of yours. The attribution views cover every surface in the title, but the spend controls this recipe creates do not: the budget covers tagged agents, and the quota covers Cortex Agents and AI functions only.
Prerequisites¶
| Requirement | Notes |
|---|---|
| Enterprise Edition | Budgets and per-user quotas |
ACCOUNTADMIN | Only for 01_setup.sql (role, grants), 04_budget_and_quota.sql (budget and quota creator roles), and 99_teardown.sql |
| AI usage in the account | Any mix of agents, CoWork, Cortex Code, AI functions. Empty views return empty results, not errors |
SNOWFLAKE.USAGE_VIEWER and SNOWFLAKE.SECURITY_VIEWER | Granted by 01_setup.sql. USAGE_VIEWER exposes the Cortex usage views and METERING_DAILY_HISTORY; SECURITY_VIEWER exposes ACCOUNT_USAGE.USERS |
Quickstart¶
sql/01_setup.sqlasACCOUNTADMIN: createsCOOKBOOK_AI_USAGE_COST_ATTRIBUTION, its warehouse, theCOOKBOOK_AI_USAGE_COST_ATTRIBUTION_ANALYSTrole (granted to you), and theCOST_CENTERtag.sql/02_views.sqlas the analyst role: creates the six views.sql/03_attribution_queries.sql: the reports (surface mix, top users, per-agent cost, model and cache mix, Cortex Code, cost centers, REST API tokens, reconciliation).- Optional,
sql/04_budget_and_quota.sql: a tag-based agent budget with a custom action and a per-user quota scoped to tagged users. It ends withGET_USERS()and sets no limit. - Optional,
sql/05_quota_limit.sql: sets the per-user daily limit and turns on blocking. Run it only afterGET_USERS()in 04 returned the users you expect, and at least 10 minutes after 04, because scope changes take 5 to 10 minutes to propagate. sql/99_teardown.sqlwhen done.
Walkthrough¶
Read access without IMPORTED PRIVILEGES¶
The analyst role owns the recipe database, so the views run with that role’s read-only grants, not with ACCOUNTADMIN’s:
Anyone you later grant SELECT on these views can read the usage data they expose, through the owner’s privileges. Grant them the way you would grant the underlying views.
Flattening the granular columns¶
CREDITS_GRANULAR has the same shape and is joined back on sub-request, service type, and model. In Cortex Code, TOKENS_GRANULAR is a plain object keyed by model, so one flatten is enough.
Users behind USER_ ID¶
CORTEX_AI_FUNCTIONS_USAGE_HISTORY and CORTEX_REST_API_USAGE_HISTORY carry USER_ID only. The views join ACCOUNT_USAGE.USERS on USER_ID to get the name. CORTEX_ANALYST_USAGE_HISTORY is an hourly roll-up by USERNAME with no request id.
Interface mix for agents¶
The documented examples for METADATA:interaction_interface are agent_admin_ui, sql_function, microsoft_teams, and external; other values can appear (for example mcp), and NULL means unknown or a row older than the field. The view groups on the raw value, mapping NULL to unknown, instead of hard-coding a list.
Cost centers from tags¶
USER_TAGS and AGENT_TAGS hold one entry per level at which a tag applies (account, database, schema, agent). Pick one value per request before summing, or a request tagged at two levels is counted twice. Query Q6 in 03_attribution_queries.sql ranks the agent-level tag first.
Budget versus quota¶
| Budget | Per-user quota | |
|---|---|---|
| Scope | Tagged objects (agents), or tagged users on a shared resource | Each user in scope, same limit for all |
| Cycle | Monthly | Monthly, weekly, daily (UTC) |
| Enforcement | Your stored procedure at a threshold | Built-in block for AI domains, or your procedure |
| Timing | Up to 8 hours after the limit (2 hours latency-optimized) | Within minutes; some overshoot is expected |
| Pooled limit | Yes, one ceiling for the tagged set | No, per user only |
The quota in 04_budget_and_quota.sql monitors two domains, CORTEX AGENT and AI FUNCTION. CoWork and Cortex Code spend from tagged users is reported by the views but is not counted against the quota and is never blocked. To include it, add the two other AI domains to the same quota before you set the limit:
The agent budget covers only agents carrying the COST_CENTER tag; it does not see CoWork, Cortex Code, or AI function spend.
The sample custom action only logs. Replace its INSERT with your enforcement, for example the revoke pattern in the resource-budget documentation.
Verify¶
01_setup.sqlends with a row count per source view; every view returns a number (zero is fine), none returns an access error.- Q1 lists your surfaces with
pct_of_ai_creditssumming to 100. - Q4 shows
cache_read_ratiobetween 0 and 1 for each model. - Q8 shows
deltaof0.0000per surface over complete UTC days. Per-day rows inV_RECONCILE_METERINGcan show small offsetting deltas on adjacent days (requests that straddle UTC midnight) and a large delta for the current day, which billing has not closed yet. When such a pair falls on the first day of the Q8 window, Q8 shows that one day’s small delta for the surface;SELECT * FROM V_RECONCILE_METERING WHERE delta <> 0 ORDER BY usage_dateshows the matching day just outside the window. - If you ran 04:
GET_QUOTA_SCOPE()lists theAI_FUNCTIONandCORTEX_AGENTdomains and theCOST_CENTER = cookbooktag, andGET_USERS()returns only users you tagged (none if you tagged no one). After 05,GET_CONFIG()showsPER_USER_LIMIT_DAILYof 5 andBLOCK_ENFORCEMENT_ENABLEDtrue. - The sum of
creditsinV_AGENT_TOKENS_BY_MODELequals the sum oftoken_creditsinV_AGENT_REQUESTS, and the flattened token categories sum toTOKENS.
Gotchas¶
| Symptom | Cause | Fix |
|---|---|---|
| Today’s numbers are lower than expected | Agent, CoWork, and Cortex Code views can lag up to 1 hour | Report on complete periods; reconcile only closed UTC days |
sql_query_credits is NULL for recent agent rows | SQL cost attribution fills in separately and can lag the row by up to 8 hours; it also excludes Adaptive Warehouses | Treat NULL as “not yet attributed”, not zero; Q3 counts them |
| Cache ratio above 100% | Divided cache_read_input by input | Divide by input + cache_read_input + cache_write_input |
| Totals higher than the bill | Summed overlapping views, such as SNOWFLAKE_COCO_USAGE_HISTORY plus CORTEX_CODE_CLI_USAGE_HISTORY, or legacy AI function views | Use one view per service type and check Q8 |
Object 'SNOWFLAKE.ACCOUNT_USAGE.USERS' does not exist or not authorized when creating views | USAGE_VIEWER does not cover USERS | Grant SNOWFLAKE.SECURITY_VIEWER, as 01_setup.sql does |
| Query works in a worksheet but view creation fails | Secondary roles gave your session access the view owner lacks | View DDL checks the primary role only; grant the database roles to the owning role |
| REST API spend missing from credit totals | CORTEX_REST_API_USAGE_HISTORY reports tokens and is billed in currency | Report it from V_REST_API_TOKENS, separately |
| Budget exceeded but nothing happened yet | Budgets evaluate periodically | Expect up to 8 hours (2 with latency-optimized); use a quota for fast blocking |
| Quota blocked more people than intended | A new quota covers all users until scoped, and scope changes take 5 to 10 minutes to propagate | Set tags first (04), confirm GET_USERS(), wait at least 10 minutes, then set the limit and enable blocking (05) |
| A tagged user keeps spending on CoWork or Cortex Code after hitting the quota | The recipe’s quota monitors CORTEX AGENT and AI FUNCTION only | Add the SNOWFLAKE INTELLIGENCE and CORTEX CODE domains with ADD_SHARED_RESOURCE |
| Budget custom action procedure uses owner’s rights | The SNOWFLAKE application calls it and it writes the log table | Keep the body small and fixed; it takes no identifiers from its arguments |
Teardown¶
Run sql/99_teardown.sql as ACCOUNTADMIN. It drops the quota and budget (if created, which also removes the limit and blocking from 05), the database (views, tag, log table, procedure), the warehouse, and the role. If you tagged a user or agent with the recipe’s COST_CENTER tag, unset it first; the file shows the statements.
Adapt this recipe¶
Keep these, so a customized version keeps the recipe’s security model:
- Least privilege:
ACCOUNTADMINonly for the scripts listed under Privileges; everything else runs as the recipe’s own role. - No grants to
PUBLIC. - Owner’s rights (
EXECUTE AS OWNER) only where the recipe uses them, for the reason it gives. Keep those procedures narrow, and don’t switch other procedures to owner’s rights. - Credentials: create keys, tokens, and secrets yourself, in a separate terminal, and read them from environment variables. Never put them in a script or give them to an agent.
- Dynamic SQL, if you add any, uses bind variables or an allowlist of identifiers, never text concatenated from user or model input.
Change these to fit your use case:
- Object names. Rename them in every script, including teardown, then check that teardown still removes everything.
- Roles, personas, and users: map them to your own, each granted no more than the recipe grants.
- Data: point the scripts at your tables instead of the sample data, then run Verify again.
- Models and warehouse sizes.
To have a coding agent adapt it:
Run it with a coding agent¶
Related skills¶
cost-intelligence(bundled with Cortex Code): ad-hoc questions about the same usage views, budgets, and quotas in plain language. Use it for exploration; use this recipe when you want durable, reconciled views.