AI Studio Usage API
Summary
We’re adding programmatic access to AI Studio usage data through two new read-only endpoints under a new ai_studio resource group:
- Credit utilization (
/workspaces/{workspace_gid}/ai_studio/runs): one row per AI Studio run (rule execution), including what ran, who owns it, the model used, and the credits consumed. - Seats (
/workspaces/{workspace_gid}/ai_studio/seats): a current snapshot of who holds an AI Studio seat, at what tier, and in what state.
Today, the only way to get AI Studio usage data out of Asana is a manual CSV export from the admin console. These endpoints expose that same data programmatically, so you can pipe AI Studio usage directly into the internal dashboards and data warehouses you already build from other usage APIs.
Both endpoints are admin-only (service-account token) and modeled on the existing Audit Log Events API: date-filtered, cursor-paginated, and read-only.
Who is affected
This change is additive and non-breaking. Nothing existing changes, and the CSV export remains available as-is.
Access is restricted to service accounts in organizations licensed for AI Studio. When provisioning the service account in the admin console, grant it the admin.ai_studio_usage:read scope — this is a service-account scope, not a standard OAuth app scope, so it’s selected at provisioning time rather than requested through a user consent screen. Requests from non-service-account tokens, or from domains without the AI Studio entitlement, will be rejected.
No action is required for existing integrations.
Timeline
**These changes are now live.
Note: Credit data does not exist before January 8, 2025. Requests with a
start_atearlier than this date are silently clamped up to the data floor (matching the audit log behavior), not rejected. Rate limits: These endpoints return large, paginated datasets and may carry a higher per-request cost than typical reads. If you poll incrementally, budget for pagination and expect to hit rate limits differently than you would on lightweight GETs.
Usage
New endpoints
| Endpoint | Method | Scope | Description |
|---|---|---|---|
/workspaces/{workspace_gid}/ai_studio/runs |
GET | admin.ai_studio_usage:read |
List AI Studio runs (rule executions) for a workspace, oldest first. Paginated. |
/workspaces/{workspace_gid}/ai_studio/seats |
GET | admin.ai_studio_usage:read |
Current snapshot of AI Studio seat allocations for a workspace. Paginated. |
Authentication
Both endpoints are restricted to service accounts in organizations licensed for AI Studio. Grant the service account the admin.ai_studio_usage:read scope when you provision it in the admin console. This is a service-account scope rather than a standard OAuth app scope, so it is selected at provisioning time, not requested through a user consent flow.
| Scope | Permission |
|---|---|
admin.ai_studio_usage:read |
Read AI Studio credit-utilization and seat data. Selected when provisioning a service account. |
Credit utilization
Returns one row per AI Studio run, ordered oldest-first by when each run’s usage was recorded (a monotonic metering timestamp, not run_started_at). This ordering is built for incremental polling: poll forward from the last run you saw, and the cursor advances monotonically through time. Use gid as the de-duplication key for incremental loads.
// GET /workspaces/{workspace_gid}/ai_studio/runs
Query parameters
| Parameter | Type | Description |
|---|---|---|
start_at |
ISO 8601 date-time | Inclusive lower bound. Runs are filtered and ordered by when their usage was recorded (the monotonic metering timestamp used for incremental polling), not by run_started_at. Omitted ⇒ clamped to the 2025-01-08 data floor. A value earlier than the floor is silently clamped, not rejected. |
end_at |
ISO 8601 date-time | Exclusive upper bound, also by when usage was recorded (not run_started_at). Omitted ⇒ the time of the request. |
division_gid |
string | Scope results to a single division. Omitted ⇒ the organization’s first licensed division, not an org-wide aggregate. There is no single call that aggregates across all divisions; multi-division organizations must iterate per division. |
limit |
integer | Page size, 1–100. These datasets are large and paginate by default; the response includes a next_page.offset token. |
offset |
string | Opaque pagination token from a previous response’s next_page.offset. |
Response fields
| Field | Type | Description |
|---|---|---|
gid |
string | The run’s ID. De-duplication key for incremental loads. |
resource_type |
string | Always ai_studio_run. |
rule |
object | The rule (automation) that ran, as a compact reference { gid, name, resource_type: "rule" }. |
rule_owner |
object | The user who owns the rule: { gid, resource_type: "user", email }. |
triggered_by |
object | The user the run executed as / to whom credits are attributed: { gid, resource_type: "user", email }. |
triggering_container |
object | The object the rule lives in, as a compact reference { gid, resource_type }. resource_type is project or portfolio; null when the rule has no container. |
division |
object | The division the actor belonged to at the time of the run, as a compact reference (resource_type: "division"). null when division metering is not enabled for the domain. Not dereferenceable yet (no public Division API). |
run_started_at |
date-time | When the run was triggered. May be null if the trigger time is unavailable. |
run_completed_at |
date-time | When the run finished. null only for runs that did not complete (e.g. cancelled). Runs still in progress do not appear in this endpoint — a row exists only once a run is metered — so this is non-null for every success/failed run. |
status |
enum | One of success, failed, cancelled. |
model |
string | The LLM model used for the run. |
credits_used |
number | Credits consumed by the run. |
credit_source |
enum | paid or free. |
// GET /workspaces/{workspace_gid}/ai_studio/runs?start_at=2026-06-01T00:00:00Z&limit=100
{
"data": [
{
"gid": "1234567890",
"resource_type": "ai_studio_run",
"rule": { "gid": "111", "name": "Auto-summarize intake", "resource_type": "rule" },
"rule_owner": { "gid": "222", "resource_type": "user", "email": "owner@acme.com" },
"triggered_by": { "gid": "333", "resource_type": "user", "email": "runner@acme.com" },
"triggering_container": { "gid": "444", "resource_type": "project" },
"division": null,
"run_started_at": "2026-06-01T14:03:00Z",
"run_completed_at": "2026-06-01T14:03:08Z",
"status": "success",
"model": "claude-sonnet-4-5",
"credits_used": 12,
"credit_source": "paid"
}
],
"next_page": { "offset": "eyJ...", "path": "...", "uri": "..." }
}
Seats
Returns a current snapshot of AI Studio seat allocations. History is not stored or returned here — customers build their own history tables from daily pulls.
/seats returns paid seats only. Each seat’s effective tier is resolved server-side; any seat that resolves to a free or off tier is dropped before the response is returned, so ai_studio_free never appears on the wire. Do not build logic expecting free-tier rows from this endpoint.
// GET /workspaces/{workspace_gid}/ai_studio/seats
Query parameters
| Parameter | Type | Description |
|---|---|---|
state |
enum | Filter seats to this state. The filter accepts only active and revoked — expired cannot be filtered on (sending ?state=expired returns a 400). Note the response state field can still return expired (see below). |
division_gid |
string | Scope results to a single division. Omitted ⇒ the organization’s first licensed division, not an org-wide aggregate. Multi-division organizations must iterate per division. |
limit |
integer | Page size, 1–100. Paginates by default, with a next_page.offset token. |
offset |
string | Opaque pagination token from a previous response’s next_page.offset. |
Response fields
| Field | Type | Description |
|---|---|---|
gid |
string | The seat’s ID. De-duplication key for incremental loads. |
resource_type |
string | Always ai_studio_seat. |
user |
object | The seat holder: { gid, resource_type: "user", email }. |
license |
enum | The effective paid tier: ai_studio_plus, ai_studio_pro, or ai_studio_max. Free-tier seats are not returned — /seats lists paid seats only. Any seat that resolves to a free/off tier is excluded from results. |
state |
enum | active, revoked, or expired. Note that expired can appear here in the response even though it is not accepted by the state filter parameter. |
assigned_at |
date-time | When the seat was assigned. |
revoked_at |
date-time | When the seat was revoked/expired. null for active seats. |
assigned_by |
object | The user who assigned the seat: { gid, resource_type: "user" }. null when assigned by the system. |
// GET /workspaces/{workspace_gid}/ai_studio/seats?state=active&limit=100
{
"data": [
{
"gid": "9876543210",
"resource_type": "ai_studio_seat",
"user": { "gid": "222", "resource_type": "user", "email": "owner@acme.com" },
"license": "ai_studio_pro",
"state": "active",
"assigned_at": "2026-01-15T09:00:00Z",
"revoked_at": null,
"assigned_by": { "gid": "100", "resource_type": "user" }
}
],
"next_page": { "offset": "eyJ...", "path": "...", "uri": "..." }
}