[ New ] AI Studio Usage API

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_at earlier 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 revokedexpired 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": "..." }
}

Resources

4 Likes