Factory Analytics API

REST API for Factory Standard Credits consumption, tool usage, activity, and productivity metrics, plus Advanced Analytics data: intent, linked issues and pull requests, and cost attribution per session, pull request, ticket, and project (/sessions, /prs, /tickets, /projects).

The Factory Analytics API returns organization-level and self-scoped usage data for Factory. Query Factory Standard Credits consumption, tool invocations, user activity, and productivity metrics across your organization or for the authenticated user.

In Private Preview, /sessions returns one row per root session, and three cost attribution routes return one row per pull request, ticket, or project with the Factory Standard Credits attributed to it. Intent, linked issues and pull requests, and attribution are Advanced Analytics data.

Authentication

All requests require a Factory API key in the Authorization header.

Authorization header
Authorization: Bearer fk-your-api-key

Generate API keys in the Factory API keys settings.

Permissions

Organization-level routes need Enterprise, and a caller with the Manager or Owner role, using their access token or their own API key. /sessions, /prs, /tickets, /projects, and /cost/by-user-trigger also accept a service-account key created by a Manager or Owner; the other routes refuse service-account keys with 403 insufficient_role. Those five routes also refuse keys that Factory issues to computers, sandboxes, and delegations, with 403 credential_not_allowed.

RouteEnterpriseManager or Owner rolePrivate Preview allowlistAdvanced Analytics consent
/tokens, /cost/by-user-trigger, /tools, /activity, /productivity, /usersRequired, else 403 enterprise_requiredRequired, else 403 insufficient_roleNot requiredNot required
/cost/me/queryRequired, else 403 enterprise_requiredNot required: any member, own data onlyNot requiredNot required
/sessionsRequired, else 403 enterprise_requiredRequired, else 403 insufficient_roleRequired, else 403 preview_access_requiredOptional. Without it, Advanced Analytics fields are null and Advanced Analytics filters return 403 advanced_analytics_required
/prs, /tickets, /projectsRequired, else 403 enterprise_requiredRequired, else 403 insufficient_roleRequired, else 403 preview_access_requiredRequired, else 403 advanced_analytics_required

To join the Private Preview, contact your account team. To turn on Advanced Analytics, see Enable Agent Effectiveness.

Base URL

Base URL
https://api.factory.ai/api/v1/analytics

Response format

All responses follow a consistent envelope structure:

JSON
{
  "data": [ ... ],
  "meta": { ... }
}
FieldTypeDescription
dataarrayArray of result objects (one per day, or per group when using group_by)
metaobjectRequest metadata: org_id, start_date, end_date, and pagination fields on paginated routes

Pagination

/sessions, /prs, /tickets, and /projects return pages of rows. Grouped /prs responses are not paginated.

Query Parameters

limitintegerqueryoptional
Rows per page, 1-500. Default 100.
cursorstringqueryoptional
meta.next_cursor from the previous page. Send it with the same filters; a cursor sent with other filters returns 400.

Meta Fields

has_moreboolean
Whether another page exists.
next_cursorstring | null
Opaque cursor for the next page; null on the last page.
total_countinteger | null
Rows matching the whole window. Present on the first page only; null on later pages.

Request pages until meta.has_more is false:

Python
import os
 
import requests
 
url = "https://api.factory.ai/api/v1/analytics/sessions"
headers = {"Authorization": f"Bearer {os.environ['FACTORY_API_KEY']}"}
params = {"startDate": "2026-10-01", "endDate": "2026-10-31", "limit": 500}
 
rows = []
while True:
    response = requests.get(url, headers=headers, params=params)
    response.raise_for_status()
    body = response.json()
    rows.extend(body["data"])
    if not body["meta"]["has_more"]:
        break
    params["cursor"] = body["meta"]["next_cursor"]

/users paginates differently: limit is 1-100 or all, and cursor is a user ID. See its parameters.

Endpoints

The following endpoints cover usage and productivity metrics:

EndpointDescription
/sessionsOne row per root session with its linked issues and pull requests (Private Preview)
/tokensOrganization-wide Factory Standard Credits by model and user
/cost/me/querySelf-scoped Factory Standard Credits and attribution metrics
/cost/by-user-triggerEach billed user's spend, split by who triggered it
/toolsTool invocations and autonomy metrics
/activityDaily, weekly, and monthly active users
/productivityFile operations and git activity
/usersPer-user metrics with pagination

Advanced Analytics adds three cost attribution routes:

RouteOne row per
/prsPull request
/ticketsLinear issue or Jira issue
/projectsLinear project or Jira epic

Understanding group_by

Several endpoints support a group_by parameter:

  • Without group_by: Returns one row per day with nested breakdowns (e.g., by_tool, daily_active_users_by_client). Use this when you want all dimensions in a single response.

  • With group_by: Flattens one of those nested arrays into separate rows. Each row has a group_key field identifying the dimension value. Use this when piping data into tools that expect flat rows (spreadsheets, BI tools, time-series databases).

For example, /activity without group_by returns daily_active_users_by_client as an object. With group_by=client, you get separate rows for terminal-ui, web, and non-interactive-cli - useful for plotting each client type as its own line on a chart.

Sessions

GET/sessions

For access, see Permissions. Shares pagination, filters, and terms with the cost attribution routes; see Joining rows to connect its rows to theirs.

Returns one row per root session whose completed_at falls in the window. Sub-agent sessions are folded into their root. This is the only route with identity lookups: by session id, mission id, issue key, or pull request URL.

Query Parameters

startDatestringqueryrequired
Start of the window on completed_at, YYYY-MM-DD. The window may span up to 366 days.
endDatestringqueryrequired
End of the window, YYYY-MM-DD.
user_idsstringqueryoptional
Comma-separated Factory user ids. Matches the root's owner. Up to 200.
session_idsstringqueryoptional
Comma-separated root session ids. Up to 200.
mission_idstringqueryoptional
Returns the orchestrator row and every worker row of one mission.
issue_keystringqueryoptional
Issue key, case-insensitive, for example ENG-123. Matches any issue linked in the subtree. Requires Advanced Analytics.
pull_request_urlstringqueryoptional
Pull request URL. Canonicalized before matching. Requires Advanced Analytics.
attributedstringqueryoptional
true keeps rows with at least one issue or pull request; false keeps the rest. Requires Advanced Analytics.
providerstringqueryoptional
linear or jira. Keeps rows with at least one issue from this tracker. Requires Advanced Analytics.
primary_intentstringqueryoptional
Keeps rows whose primary intent is this value. See primary_intent below for the values. Requires Advanced Analytics.
includestringqueryoptional
Comma-separated extra fields: titles, owners, link_metadata, duration. See Optional Fields below.
group_bystringqueryoptional
user returns one row per owner instead of one row per session. See Grouped Response below.
limitintegerqueryoptional
Rows per page, 1-500. Default 100. See Pagination.
cursorstringqueryoptional
meta.next_cursor from the previous page.

Response

200
JSON
{
  "data": [
    {
      "session_id": "sess_01HPMQ7NXKHM7Y7PR3TTZY3JZS",
      "owner_user_id": "user_01HPMQ7NXKHM7Y7PR3TTZY3JZS",
      "work_role": "engineering",
      "started_at": "2026-10-20T14:02:11Z",
      "completed_at": "2026-10-20T15:40:03Z",
      "session_count": 3,
      "mission_id": null,
      "mission_role": null,
      "automation_id": null,
      "factory_standard_credits": 1820,
      "primary_intent": "code_generation",
      "issues": [
        {
          "provider": "linear",
          "issue_id": "9f1c2b3a-4d5e-4f60-8a7b-1c2d3e4f5a6b",
          "key": "ENG-123",
          "url": "https://linear.app/acme/issue/ENG-123/add-checkout-retries",
          "project_id": "7a8b9c0d-1e2f-4a3b-9c4d-5e6f7a8b9c0d",
          "project_name": "Checkout v2",
          "epic_id": null,
          "epic_key": null,
          "epic_name": null,
          "status_category": "completed"
        }
      ],
      "pull_requests": [
        {
          "url": "https://github.com/acme/shop/pull/481",
          "provider": "github",
          "repo": "acme/shop",
          "number": 481
        }
      ]
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE7Y7PR3TTZY4KLM",
    "start_date": "2026-10-01",
    "end_date": "2026-10-31",
    "has_more": true,
    "next_cursor": "eyJ2IjoxLCJrIjoi...",
    "total_count": 517
  }
}

Response Fields

session_idstring
Root session id.
owner_user_idstring | null
Factory user who started the root.
work_rolestring | null
Owner's onboarding work role. There is no work_roles filter; filter on this field client-side.
started_atstring <date-time>
Start of the root session.
completed_atstring <date-time>
Last user-driven billed activity across the subtree. Background work such as title generation does not move it.
session_countinteger
Sessions folded into the row, root included. At least 1.
mission_idstring | null
Mission the root belongs to.
mission_rolestring | null
orchestrator or worker.
automation_idstring | null
Automation that started the root.
factory_standard_creditsnumber
Lifetime FSC of the whole subtree. Not limited to the window: summed over a window, it gives the lifetime spend of the sessions that completed in it, not the spend that occurred in it.
primary_intentstring | null
One of code_generation, debugging, code_review, knowledge_qa, devops, data, documentation, research, planning, automation, agent_config, noise, other, unknown. null without Advanced Analytics.
issuesobject[] | null
Issues linked anywhere in the subtree. null without Advanced Analytics; [] when none were found.
pull_requestsobject[] | null
Pull requests linked anywhere in the subtree. null without Advanced Analytics; [] when none were found.

issues[]

providerstring
linear or jira.
issue_idstring
Provider's issue id.
keystring | null
Issue key, for example ENG-123.
urlstring | null
Issue URL. Equals url on /tickets.
project_idstring | null
Linear project or Jira project id.
project_namestring | null
Linear project or Jira project name.
epic_idstring | null
Jira parent epic id. Always null for Linear.
epic_keystring | null
Jira parent epic key.
epic_namestring | null
Jira parent epic name.
status_categorystring | null
Provider status category when the data was built.

pull_requests[]

urlstring
Canonical PR URL. Equals url on /prs.
providerstring
github or gitlab.
repostring
owner/name, lower-cased. GitLab: full namespace path.
numberinteger
Pull request number.

Optional Fields

include adds keys to each row. A key appears only when you request it.

ValueAdds
titlessession_title: the root session's title. null when it has none.
ownersowner: the resolved owner (shape below). null when the owner cannot be resolved.
link_metadatatitle on each issues[] item, and title, status, and author on each pull_requests[] item. null when the item is not synced.
durationduration_seconds: completed_at - started_at in whole seconds.

owner and pull_requests[].author

display_namestring
Display name. A service account resolves to its configured name.
avatar_urlstring
Avatar URL. Omitted when there is none, as for service accounts.

pull_requests[].status

statestring
One of unknown, open, in_progress, completed, canceled, paused, draft, merged, closed, mixed. Treat any other value as unknown.
labelstring
Provider-native status name, for example Merged.
provider_categorystring
Provider-native status category. Optional.

Grouped Response

With group_by=user, each row is one owner instead of one session. The other filters still select which root sessions are folded into each owner's row.

user_idstring
Owner's Factory user id.
session_countinteger
Root sessions folded into the row.
ticket_countinteger | null
Distinct issues linked in the subtrees of those roots. null without Advanced Analytics.
project_countinteger | null
Distinct containers (Linear projects or Jira epics) linked in those subtrees. null without Advanced Analytics.
pr_countinteger | null
Distinct pull requests linked in those subtrees. null without Advanced Analytics.
latest_completed_atstring <date-time>
Latest completed_at among those roots.
factory_standard_creditsnumber
Lifetime subtree FSC, summed over the owner's roots.
ownerobject | null
Resolved owner, same shape as with include=owners above. Present only with include=owners.

Example

Bash
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/sessions?startDate=2026-10-01&endDate=2026-10-31&attributed=true&limit=100"

Factory Standard Credits usage

GET/tokens

Returns daily Factory Standard Credits consumption across your organization.

Query Parameters

startDatestringqueryrequired
Start date in YYYY-MM-DD format. No cap on the range.
endDatestringqueryrequired
End date in YYYY-MM-DD format
group_bystringqueryoptional
Set to model or reasoning_effort to group results by that dimension

Response Fields

datestring
Date in YYYY-MM-DD format
billable_tokensnumber
Factory Standard Credits consumed, computed from raw input and raw output tokens with cache discounts
input_tokensnumber
Raw input tokens sent to model
output_tokensnumber
Tokens generated by model
cache_read_tokensnumber | null
Tokens read from prompt cache. Always present; null when this value is not reported. null does not indicate zero cache usage.
cache_write_tokensnumber | null
Tokens written to prompt cache. Always present; null when this value is not reported. null does not indicate zero cache usage.
by_modelarray
Breakdown per model
by_userarray
Breakdown per user

Example

Bash
# Factory Standard Credits usage for a date range
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/tokens?startDate=2026-01-14&endDate=2026-01-28"
 
# Grouped by model
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/tokens?startDate=2026-01-15&endDate=2026-01-15&group_by=model"

Personal Factory Standard Credits usage

GET/cost/me/query

Returns Factory Standard Credits and attribution metrics for the authenticated user. The API derives the user ID from the bearer credential and does not accept a user selector, so callers cannot query another user.

Any member of an Enterprise organization can call it; see Permissions.

Query Parameters

queryKeystringqueryrequired
Selects the metric returned in data.
startDatestringqueryrequired
Start date in YYYY-MM-DD format. The range may span up to 90 days.
endDatestringqueryrequired
End date in YYYY-MM-DD format.

Query Keys

ValueDescription
headline_dailyDaily Factory Standard Credits consumed
cost_summaryTotal sessions, messages, credits, and averages
top_sessionsSessions with the highest credit consumption
by_modelCredit consumption by model
fsc_by_model_dailyDaily credit consumption grouped by model
fsc_by_multiplier_dailyDaily credit consumption grouped by multiplier
by_ticketCredit consumption attributed to tickets
by_prCredit consumption attributed to pull requests
my_activity_summaryActivity totals for the authenticated user
my_time_spentEstimated time spent by the authenticated user
my_intent_breakdownCredit consumption grouped by intent
my_completed_tickets_dailyDaily completed-ticket counts
my_completed_ticketsCompleted-ticket details
my_merged_prs_dailyDaily merged-pull-request counts
my_merged_pr_detailMerged-pull-request details

Response

200

The fields inside data depend on queryKey. For headline_daily, each row contains:

JSON
{
  "data": [
    {
      "date": "2026-08-01",
      "fsc": 123456
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ8ABCDE7Y7PR3TTZY4KLM",
    "start_date": "2026-08-01",
    "end_date": "2026-08-31"
  }
}

Response Fields

dataobject[]
Rows for the requested queryKey, scoped to the authenticated user.
meta.org_idstring
Organization associated with the bearer credential.
meta.start_datestring
Start of the requested UTC date range.
meta.end_datestring
End of the requested UTC date range.

Example

Bash
# Daily personal usage
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/cost/me/query?queryKey=headline_daily&startDate=2026-08-01&endDate=2026-08-31"

Spend by triggering user

GET/cost/by-user-trigger

Returns each billed user's spend in the window, split by who triggered it. A service account's delegated sessions break down by the person who started each one.

Query Parameters

startDatestringqueryrequired
Start date in YYYY-MM-DD format. The range may span up to 90 days.
endDatestringqueryrequired
End date in YYYY-MM-DD format.

Response

200
JSON
{
  "data": [
    {
      "user_id": "user_01HPMQ7NXKHM7Y7PR3TTZY3JZS",
      "user_email": "developer@company.com",
      "triggered_by_user_id": "user_01HPMQ7NXKHM7Y7PR3TTZY3JZS",
      "triggered_by_email": "developer@company.com",
      "fsc": 5230,
      "sessions": 14
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE7Y7PR3TTZY4KLM",
    "start_date": "2026-10-01",
    "end_date": "2026-10-31"
  }
}

Response Fields

user_idstring | null
Billed user, or a service account (sa_ prefix). null for billing rows with no user id; those rows group by user_email.
user_emailstring | null
Billed user's email.
triggered_by_user_idstring | null
Factory user who triggered the spend.
triggered_by_emailstring | null
Triggering user's email. null when that user has no email in the session's organization.
fscnumber
Factory Standard Credits billed in the window. Omitted when the organization hides billable usage from API keys.
sessionsinteger
Sessions behind the row.

Example

Bash
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/cost/by-user-trigger?startDate=2026-10-01&endDate=2026-10-31"

Tool usage

GET/tools

Returns daily tool invocations, MCP usage, skills, slash commands, and autonomy metrics.

Query Parameters

startDatestringqueryrequired
Start date in YYYY-MM-DD format. The range may span up to 90 days.
endDatestringqueryrequired
End date in YYYY-MM-DD format
group_bystringqueryoptional
Set to tool_name to group results by tool

Response

200
JSON
{
  "data": [
    {
      "date": "2026-01-15",
      "tool_calls": 45000,
      "by_tool": [
        { "tool": "Read", "invocations": 12500 },
        { "tool": "Edit", "invocations": 8200 },
        { "tool": "Execute", "invocations": 6100 }
      ],
      "mcp_users_with_mcp": 42,
      "mcp_by_server": [
        { "server": "github", "invocations": 1200 },
        { "server": "notion", "invocations": 850 }
      ],
      "skills_invocations": 320,
      "skills_by_name": [
        { "name": "browser", "count": 180 },
        { "name": "frontend-ui", "count": 95 }
      ],
      "slash_commands_invocations": 1500,
      "slash_commands_by_name": [
        { "name": "review", "count": 420 },
        { "name": "test", "count": 380 }
      ],
      "hooks_invocations": 2800,
      "hooks_by_event": [
        { "event": "PostToolUse", "matcher": "*.ts", "command": "eslint --fix", "count": 1200 }
      ],
      "web_users": 42,
      "autonomy_ratio_avg": 8.5,
      "autonomy_ratio_p50": 6.2,
      "autonomy_ratio_p90": 18.4,
      "tool_calls_per_session_avg": 45.2,
      "user_turns_per_session_avg": 5.3,
      "tool_autonomy_level_ratio": {
        "auto_high": 0.35,
        "auto_medium": 0.42,
        "auto_low": 0.18,
        "manual": 0.05
      }
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE...",
    "start_date": "2026-01-15",
    "end_date": "2026-01-15"
  }
}

Response Fields

datestring
Date in YYYY-MM-DD format
tool_callsnumber
Total tool invocations
by_toolarray
Breakdown by tool name
mcp_users_with_mcpnumber
Users who used MCP servers
mcp_by_serverarray
Invocations per MCP server
skills_invocationsnumber
Total skill activations
skills_by_namearray
Breakdown by skill
slash_commands_invocationsnumber
Total slash command uses
slash_commands_by_namearray
Breakdown by command
hooks_invocationsnumber
Total hook executions
hooks_by_eventarray
Breakdown by event type
web_usersnumber
Users who used web/workspace interface
autonomy_ratio_avgnumber
Average tool calls per user turn
autonomy_ratio_p50number
Median autonomy ratio
autonomy_ratio_p90number
90th percentile autonomy ratio
tool_calls_per_session_avgnumber
Average tool calls per session
user_turns_per_session_avgnumber
Average user messages per session
tool_autonomy_level_ratioobject
Distribution of autonomy levels

Grouped Response

When group_by=tool_name, returns one row per tool per day inside data:

JSON
{
  "data": [
    {
      "date": "2026-01-15",
      "group_key": "Read",
      "tool_calls": 12500
    },
    {
      "date": "2026-01-15",
      "group_key": "Edit",
      "tool_calls": 8200
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE...",
    "start_date": "2026-01-15",
    "end_date": "2026-01-15"
  }
}

User activity

GET/activity

Returns daily, weekly, and monthly active users along with session counts.

Query Parameters

startDatestringqueryrequired
Start date in YYYY-MM-DD format. The range may span up to 90 days.
endDatestringqueryrequired
End date in YYYY-MM-DD format
group_bystringqueryoptional
Set to client to group by client type

Response

200
JSON
{
  "data": [
    {
      "date": "2026-01-15",
      "daily_active_users": 128,
      "weekly_active_users": 312,
      "monthly_active_users": 485,
      "daily_active_users_by_client": {
        "terminal-ui": 95,
        "web": 42,
        "non-interactive-cli": 18
      },
      "sessions": 890,
      "messages": 12500,
      "user_messages": 4200
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE...",
    "start_date": "2026-01-15",
    "end_date": "2026-01-15"
  }
}

Response Fields

datestring
Date in YYYY-MM-DD format
daily_active_usersnumber
Unique users on this day
weekly_active_usersnumber
Unique users in trailing 7 days
monthly_active_usersnumber
Unique users in trailing 30 days
daily_active_users_by_clientobject
DAU breakdown by client type
sessionsnumber
Total sessions started
messagesnumber
Total messages across all roles (user + assistant + tool + system)
user_messagesnumber
Messages from users only

Client Types

ClientDescription
terminal-uiInteractive CLI sessions
webFactory App
non-interactive-cliHeadless/automated CLI (droid exec)

Grouped Response

When group_by=client, returns one row per client type per day inside data:

JSON
{
  "data": [
    {
      "date": "2026-01-15",
      "group_key": "terminal-ui",
      "daily_active_users": 95
    },
    {
      "date": "2026-01-15",
      "group_key": "web",
      "daily_active_users": 42
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE...",
    "start_date": "2026-01-15",
    "end_date": "2026-01-15"
  }
}

Productivity

GET/productivity

Returns daily file operations and git activity.

Query Parameters

startDatestringqueryrequired
Start date in YYYY-MM-DD format. The range may span up to 90 days.
endDatestringqueryrequired
End date in YYYY-MM-DD format

Response

200
JSON
{
  "data": [
    {
      "date": "2026-01-15",
      "files_created": 245,
      "files_edited": 1820,
      "by_extension": [
        { "extension": ".ts", "count": 890 },
        { "extension": ".tsx", "count": 420 },
        { "extension": ".py", "count": 310 }
      ],
      "by_language": [
        { "language": "TypeScript", "count": 1310 },
        { "language": "Python", "count": 310 }
      ],
      "git_commits": 156,
      "git_prs_created": 42
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE...",
    "start_date": "2026-01-15",
    "end_date": "2026-01-15"
  }
}

Response Fields

datestring
Date in YYYY-MM-DD format
files_creatednumber
New files created by agent
files_editednumber
Existing files modified by agent
by_extensionarray
Operations per file extension
by_languagearray
Operations per programming language
git_commitsnumber
Commits made via agent
git_prs_creatednumber
Pull requests created via agent

Per-user metrics

GET/users

Returns detailed metrics per user with cursor-based pagination.

Query Parameters

startDatestringqueryrequired
Start date in YYYY-MM-DD format. The range may span up to 90 days.
endDatestringqueryrequired
End date in YYYY-MM-DD format
limitstringqueryoptional
Users per page, 1-100, or all (default: 20)
cursorstringqueryoptional
User ID for pagination (from next_cursor)
searchstringqueryoptional
Case-insensitive substring match on user email

Response

200
JSON
{
  "data": [
    {
      "user_id": "user_01HPMQ7NXKHM7Y7PR3TTZY3JZS",
      "user_email": "developer@company.com",
      "display_name": "Dana Developer",
      "date": "2026-01-15",
      "tool_calls": 1250,
      "billable_tokens": 450000,
      "primary_model": "claude-opus-5-5",
      "primary_model_tier": "standard",
      "files_created": 12,
      "files_edited": 85,
      "git_commits": 8,
      "git_prs_created": 2,
      "mcp_calls": 45,
      "skill_calls": 8,
      "slash_commands": 22,
      "hooks": 120,
      "sessions": 15,
      "messages": 310,
      "user_messages": 62,
      "assistant_messages": 118,
      "autonomy_ratio": 9.2,
      "delegation_level": "auto-high",
      "languages": ["TypeScript", "Python", "Go"],
      "academy_highest_certification": null,
      "work_role": "engineering"
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE...",
    "start_date": "2026-01-15",
    "end_date": "2026-01-15",
    "has_more": true,
    "next_cursor": "user_01HPMQ8ABCDE7Y7PR3TTZY4KLM",
    "total_users": 73
  }
}

Response Fields

user_idstring
Unique user identifier
user_emailstring | null
User email
display_namestring | null
Resolved display name; absent when unresolved
datestring
Date in YYYY-MM-DD format
tool_callsnumber
Tool invocations by this user
billable_tokensnumber
Factory Standard Credits consumed by this user
primary_modelstring
Most-used model
primary_model_tierstring
Model tier (standard or thinking)
files_creatednumber
Files created
files_editednumber
Files edited
git_commitsnumber
Commits made
git_prs_creatednumber
Pull requests created
mcp_callsnumber
MCP tool invocations
skill_callsnumber
Skill activations
slash_commandsnumber
Slash command uses
hooksnumber
Hook executions
sessionsnumber
Sessions started
messagesnumber
Total messages across all roles (user + assistant + tool + system)
user_messagesnumber
User messages only
assistant_messagesnumber
Assistant messages
autonomy_rationumber
Tool calls per user turn
delegation_levelstring
Primary autonomy mode
languagesarray
Programming languages worked in
academy_highest_certificationstring | null
Highest Factory Academy certification
work_rolestring | null
Onboarding work role; null when none was chosen
meta.total_usersnumber
Users matching the query across all pages

Delegation Levels

LevelDescription
auto-highMaximum autonomy, minimal confirmations
auto-mediumBalanced autonomy with some confirmations
auto-lowLimited autonomy, frequent confirmations
specSpecification mode, planning before execution
manualFull manual control, confirm each action

Pagination

Use cursor-based pagination to iterate through users:

Bash
# First page
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/users?startDate=2026-01-15&endDate=2026-01-15&limit=50"
 
# Next page
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/users?startDate=2026-01-15&endDate=2026-01-15&limit=50&cursor=user_01HPMQ8ABCDE7Y7PR3TTZY4KLM"

Advanced Analytics

Private Preview

/sessions, /prs, /tickets, and /projects are in Private Preview. For access, see Permissions.

Advanced Analytics data is the intent of each session, the issues and pull requests it linked, and the cost attributed to each pull request, ticket, and project. It requires the Advanced Analytics enterprise control; see Enable Agent Effectiveness. The same data powers the Effectiveness dashboard. Use it to build your own ROI reporting: how much agent spend went into each pull request, ticket, project, or session, sliced by repository, project, user, date range, or state.

What you can pull

RouteOne row perEach row carries
/sessionsRoot session, sub-agents folded inOwner, lifetime Factory Standard Credits, primary intent, linked issues and pull requests
/prsPull requestState and timestamps, line statistics, author, attributed sessions and credits
/ticketsLinear issue or Jira issueState, estimate, assignee, parent, project, attributed sessions and credits
/projectsLinear project or Jira epicState, dates, linked ticket count, attributed sessions and credits

Shared conventions

These routes and /sessions share the response format, pagination, and the conventions below.

Filters

RuleDetail
ListsComma-separated. user_ids and session_ids accept up to 200 values; repo and project up to 50.
Unknown parametersIgnored.
Unknown state400. Dropping a value would widen the result.
Unknown work_rolesDropped.
include=titlesAdds the provider-reported title to every row.

Terms

TermMeaning
Root sessionA session a user started, with its sub-agent sessions folded in. A mission's orchestrator and each worker are separate roots that share mission_id.
AttributionThe root sessions whose subtree linked an item, through a pull request URL or an issue key. attribution.factory_standard_credits sums the lifetime FSC of those roots. A root that linked three items counts fully under each; the column is not a share of organization spend.
FSCFactory Standard Credits, the billing unit, as a raw number.
work_roleThe role a member chose at onboarding. In a work_roles filter, unknown selects members who chose none.
date_basisOn /projects and /tickets: which timestamp the window and the page order apply to.
Stable idOn /projects and /tickets: UUIDv5 of provider:site_id:external_id under namespace 8a6d23b3-391b-4048-b001-65e10778367c. Unchanged by renames or key changes, and identical across organizations connected to the same site.
Canonical PR URLLower-cased host and path, no query or fragment. The same string identifies a pull request on /prs and in /sessions pull_requests[].

Using the data

QuestionAnswer
What did one pull request, ticket, or project cost?Its attribution.factory_standard_credits. See Attribution under Terms.
What did the organization spend?/tokens, which counts spend on the day it occurred. Never sum factory_standard_credits across the rows of /prs, /tickets, or /projects; the overlap between rows makes that sum overstate spend.
What did a set of items cost together?Union their root_session_ids, then sum each session once from /sessions (session_ids=). Do not add ticket rows to project rows or pull request rows to ticket rows: a project's value already covers its tickets' sessions (see the project cost rule), and a session that linked both a pull request and a ticket appears under both. A row with session_ids_truncated: true lists only 500 of its roots, so a union built from it is incomplete.
How much work is attributed at all?Compare attributed=true with attributed=false counts on /sessions, /prs, /tickets, and /projects. An unattributed item had no root session link it through a pull request URL or an issue key; its factory_standard_credits is 0.
Which numbers tie?A pull request, ticket, or project row equals the sum of /sessions factory_standard_credits over its root_session_ids, unless truncated. Reconcile tickets to projects only through root_session_ids. /sessions summed over a window does not equal /tokens for the same dates: /sessions places a root's lifetime spend on its completed_at, while /tokens counts spend on the day it occurred. They converge over long windows; they do not match.
Which dates does a window select?/prs: state_at, when the pull request entered its current state. /tickets and /projects: a linked session's date (date_basis=active, the default), completed_at, or created_at. /sessions: the root's completed_at. The same startDate/endDate therefore selects different events on each route; do not compare counts across routes as if they described one population.

Example

Pull requests merged or closed in October with at least one attributed session:

Bash
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/prs?startDate=2026-10-01&endDate=2026-10-31&attributed=true&limit=500"

To page through results, see Pagination. To connect rows across routes, see Joining rows.

Pull requests

GET/prs

Requires Advanced Analytics

For access, see Permissions.

Returns one row per pull request the organization has synced. Each row is windowed on the moment it entered its current state: merged_at, closed_at, or opened_at for open and draft pull requests. state selects which states to list; the default, merged,closed, lists the pull requests that finished in the window.

Query Parameters

startDatestringqueryrequired
Start of the window on state_at, YYYY-MM-DD. The window may span up to 366 days.
endDatestringqueryrequired
End of the window, YYYY-MM-DD.
statestringqueryoptional
Comma-separated states to list: open, draft, merged, closed. Default merged,closed. An unknown value returns 400.
providerstringqueryoptional
github or gitlab.
repostringqueryoptional
Comma-separated owner/name (GitLab: namespace path). Case-insensitive. Up to 50.
user_idsstringqueryoptional
Comma-separated Factory user ids. Matches the author. Up to 200.
work_rolesstringqueryoptional
Comma-separated onboarding work roles of the author, unknown included.
attributedstringqueryoptional
true keeps pull requests with at least one linked root session; false keeps the rest.
include_botsstringqueryoptional
true includes bot-authored pull requests. Default false.
includestringqueryoptional
titles adds the provider-reported title to each row.
group_bystringqueryoptional
day or day,author returns daily counts instead of rows. See Grouped Response below. Returns 400 with include=titles.

Response

200
JSON
{
  "data": [
    {
      "url": "https://github.com/acme/shop/pull/481",
      "provider": "github",
      "host": "github.com",
      "repo": "acme/shop",
      "state": "merged",
      "state_at": "2026-10-21T16:05:44Z",
      "opened_at": "2026-10-19T09:12:00Z",
      "merged_at": "2026-10-21T16:05:44Z",
      "closed_at": "2026-10-21T16:05:44Z",
      "additions": 212,
      "deletions": 40,
      "changed_files": 6,
      "author": {
        "login": "adev",
        "user_id": "user_01HPMQ7NXKHM7Y7PR3TTZY3JZS",
        "is_bot": false
      },
      "attribution": {
        "session_count": 2,
        "contributor_user_ids": ["user_01HPMQ7NXKHM7Y7PR3TTZY3JZS"],
        "factory_standard_credits": 2410,
        "first_session_at": "2026-10-19T10:00:00Z",
        "last_session_at": "2026-10-21T15:30:00Z",
        "root_session_ids": [
          "sess_01HPMQ8ABCDE7Y7PR3TTZY4KLM",
          "sess_01HPMQ7NXKHM7Y7PR3TTZY3JZS"
        ],
        "session_ids_truncated": false
      },
      "source_updated_at": "2026-10-21T16:06:10Z"
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE7Y7PR3TTZY4KLM",
    "start_date": "2026-10-01",
    "end_date": "2026-10-31",
    "state": ["merged", "closed"],
    "has_more": false,
    "next_cursor": null,
    "total_count": 1
  }
}

Response Fields

urlstring
Canonical PR URL; the row's identity. The pull request number is its last path segment.
providerstring
github or gitlab.
hoststring | null
Provider host. null when the URL shape is unknown.
repostring | null
owner/name, lower-cased. GitLab: full namespace path.
statestring
open, draft, merged, or closed. draft is an open draft; closed is closed without merging.
state_atstring <date-time>
When the pull request entered state. The window and page order apply to it.
titlestring | null
Provider-reported title. Present only with include=titles.
opened_atstring <date-time>
When the pull request was opened.
merged_atstring <date-time> | null
When it was merged.
closed_atstring <date-time> | null
When it was closed.
additionsinteger | null
Lines added. null when the provider sync carried no line statistics.
deletionsinteger | null
Lines deleted.
changed_filesinteger | null
Files changed.
authorobject
Provider author, bridged to a Factory member.
attributionobject
Root sessions that linked the pull request.
source_updated_atstring <date-time>
Provider's last-updated time for the pull request.

author

loginstring | null
Provider login.
user_idstring | null
Factory user id. null while the provider identity is unlinked.
is_botboolean
Whether the provider identity is a bot.

attribution

session_countinteger
Number of linked root sessions. 0 when unattributed.
contributor_user_idsstring[]
Distinct owners of the linked roots, sorted.
factory_standard_creditsnumber
Sum of each linked root's lifetime FSC.
first_session_atstring <date-time> | null
Earliest linked root.
last_session_atstring <date-time> | null
Latest linked root.
root_session_idsstring[]
Linked root session ids, newest first. Capped at 500.
session_ids_truncatedboolean
true when root_session_ids hit the cap; session_count then floors at 500.

Meta Fields

statestring[]
The state set the window was applied to.

Grouped Response

With group_by=day, each row is one UTC day of state_at; days with no matching pull request are absent. Without state, only merged pull requests are counted. Grouped responses are not paginated, and meta.group_by echoes the grouping.

datestring
UTC day, YYYY-MM-DD.
pr_countinteger
Pull requests that entered their state that day.
attributed_pr_countinteger
Of those, pull requests with at least one linked root session.
total_additionsinteger | null
Lines added. null when none of the day's pull requests carry line statistics.
total_deletionsinteger | null
Lines deleted.
total_changed_filesinteger | null
Files changed.
prs_with_line_statsinteger
Pull requests that carry line statistics.

With group_by=day,author, each row is one author's day and adds author (same shape as above) and work_role.

Example

Bash
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/prs?startDate=2026-10-01&endDate=2026-10-31&attributed=true&repo=acme/shop"
  • A pull request opened in the window but still open is listed only with state=open or state=draft. There is no query for "opened in the window, whatever happened since".
  • To look up one pull request or the pull requests of one session, use /sessions with pull_request_url or session_ids.
  • Bitbucket pull requests are not served.

Tickets

GET/tickets

Requires Advanced Analytics

For access, see Permissions.

Returns one row per work item the organization has synced: a Linear issue or a Jira issue below the epic level. date_basis selects which timestamp the window applies to. project and parent narrow the list to one container's or one work item's children; there is no lookup by key, URL, or session id.

Query Parameters

startDatestringqueryrequired
Start of the window, YYYY-MM-DD. The window may span up to 366 days.
endDatestringqueryrequired
End of the window, YYYY-MM-DD.
date_basisstringqueryoptional
active (default) keeps work items with a linked session in the window, ordered by created_at. completed windows on completed_at and keeps completed items only. created windows on created_at.
statestringqueryoptional
Comma-separated states: open, in_progress, completed, canceled. Returns 400 with date_basis=completed.
providerstringqueryoptional
linear or jira.
projectstringqueryoptional
Comma-separated container URLs (Linear project or Jira epic), up to 50, or none alone for work items in no container.
parentstringqueryoptional
One work item or container URL. Keeps its direct children.
user_idsstringqueryoptional
Comma-separated Factory user ids. Matches the assignee. Up to 200.
work_rolesstringqueryoptional
Comma-separated onboarding work roles of the assignee, unknown included.
attributedstringqueryoptional
true keeps work items with at least one linked root session; false keeps the rest.
includestringqueryoptional
titles adds the provider-reported title to each row.

Response

200
JSON
{
  "data": [
    {
      "id": "9b7a2c10-1d4e-5f6a-8b9c-0d1e2f3a4b5c",
      "url": "https://linear.app/acme/issue/ENG-123/add-checkout-retries",
      "key": "ENG-123",
      "provider": "linear",
      "external_id": "9f1c2b3a-4d5e-4f60-8a7b-1c2d3e4f5a6b",
      "kind": "issue",
      "parent": null,
      "project": { "url": "https://linear.app/acme/project/checkout-v2-ab12" },
      "state": "completed",
      "story_points": 3,
      "assignee": { "user_id": "user_01HPMQ7NXKHM7Y7PR3TTZY3JZS" },
      "created_at": "2026-10-12T09:00:00Z",
      "completed_at": "2026-10-21T16:10:00Z",
      "attribution": {
        "session_count": 2,
        "contributor_user_ids": ["user_01HPMQ7NXKHM7Y7PR3TTZY3JZS"],
        "factory_standard_credits": 2410,
        "root_session_ids": [
          "sess_01HPMQ8ABCDE7Y7PR3TTZY4KLM",
          "sess_01HPMQ7NXKHM7Y7PR3TTZY3JZS"
        ],
        "session_ids_truncated": false
      }
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE7Y7PR3TTZY4KLM",
    "start_date": "2026-10-01",
    "end_date": "2026-10-31",
    "date_basis": "active",
    "has_more": true,
    "next_cursor": "eyJ2IjoxLCJrIjoi...",
    "total_count": 19
  }
}

Response Fields

idstring
Stable id, UUIDv5 of the external entity. See Terms.
urlstring
Issue URL as the provider reports it. Equals issues[].url on /sessions.
keystring
Issue key, for example ENG-123. The value issue_key takes on /sessions.
providerstring
linear or jira.
external_idstring
Provider's own id: Linear issue UUID or Jira issue id.
kindstring
sub_issue when the parent is a synced work item, else issue.
parentobject | null
{ url } of the synced parent at any level. A Jira story's parent is its epic.
projectobject | null
{ url } of the container the item rolls up to. Matches a /projects row.
statestring
open, in_progress, completed, or canceled.
titlestring | null
Provider-reported title. Present only with include=titles.
story_pointsnumber | null
Linear estimate or Jira story points.
assigneeobject | null
{ user_id } of the assignee as a Factory member. null when unassigned or the provider identity is unlinked.
created_atstring <date-time>
When the work item was created.
completed_atstring <date-time> | null
When it was completed.
attributionobject
Root sessions that linked this work item.

attribution

session_countinteger
Number of linked root sessions. 0 when unattributed.
contributor_user_idsstring[]
Distinct owners of the linked roots, sorted.
factory_standard_creditsnumber
Sum of each linked root's lifetime FSC.
root_session_idsstring[]
Linked root session ids, newest first. Capped at 500.
session_ids_truncatedboolean
true when root_session_ids hit the cap; session_count then floors at 500.

Meta Fields

date_basisstring
The basis applied to the window and order.

Example

Bash
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/tickets?startDate=2026-10-01&endDate=2026-10-31&date_basis=completed&attributed=true"
  • project and parent must be Linear or Jira URLs; anything else returns 400. project=none cannot be combined with URLs.
  • Each ticket's attribution is direct: the roots that linked this work item, not its children.

Projects

GET/projects

Requires Advanced Analytics

For access, see Permissions.

Returns one row per container the organization has synced: a Linear project or a Jira epic. date_basis selects which timestamp the window applies to. There is no lookup by URL or session id; join from /tickets project.url or filter /sessions instead.

Query Parameters

startDatestringqueryrequired
Start of the window, YYYY-MM-DD. The window may span up to 366 days.
endDatestringqueryrequired
End of the window, YYYY-MM-DD.
date_basisstringqueryoptional
active (default) keeps containers with a linked session in the window, ordered by created_at. completed windows on completed_at and keeps completed containers only. created windows on created_at.
statestringqueryoptional
Comma-separated states: open, in_progress, paused, completed, canceled. Returns 400 with date_basis=completed.
providerstringqueryoptional
linear or jira.
attributedstringqueryoptional
true keeps containers with at least one linked root session; false keeps the rest.
includestringqueryoptional
titles adds the provider-reported title to each row.

Response

200
JSON
{
  "data": [
    {
      "id": "4c1e0f7a-5b2d-5b1a-9c3e-2f7d8a9b0c11",
      "url": "https://linear.app/acme/project/checkout-v2-ab12",
      "provider": "linear",
      "external_id": "7a8b9c0d-1e2f-4a3b-9c4d-5e6f7a8b9c0d",
      "kind": "project",
      "state": "completed",
      "created_at": "2026-07-02T08:00:00Z",
      "completed_at": "2026-10-15T17:20:00Z",
      "attribution": {
        "session_count": 2,
        "contributor_user_ids": [
          "user_01HPMQ7NXKHM7Y7PR3TTZY3JZS",
          "user_01HPMQ8ABCDE7Y7PR3TTZY4KLM"
        ],
        "factory_standard_credits": 9650,
        "root_session_ids": [
          "sess_01HPMQ8ABCDE7Y7PR3TTZY4KLM",
          "sess_01HPMQ7NXKHM7Y7PR3TTZY3JZS"
        ],
        "session_ids_truncated": false,
        "linked_ticket_count": 5
      }
    }
  ],
  "meta": {
    "org_id": "org_01HPMQ6ABCDE7Y7PR3TTZY4KLM",
    "start_date": "2026-08-01",
    "end_date": "2026-10-31",
    "date_basis": "completed",
    "has_more": false,
    "next_cursor": null,
    "total_count": 1
  }
}

Response Fields

idstring
Stable id, UUIDv5 of the external entity. See Terms.
urlstring
Linear project URL or Jira epic URL.
providerstring
linear or jira.
external_idstring
Provider's own id: Linear project UUID or Jira epic issue id.
kindstring
project for every row today. initiative and theme are reserved for higher levels.
statestring
open, in_progress, paused, completed, or canceled.
titlestring | null
Provider-reported project name. null when not synced. Present only with include=titles.
created_atstring <date-time>
When the container was created.
completed_atstring <date-time> | null
When it was completed.
attributionobject
Root sessions that linked the container or any work item under it.

attribution

session_countinteger
Number of distinct linked root sessions. 0 when unattributed.
contributor_user_idsstring[]
Distinct owners of the linked roots, sorted.
factory_standard_creditsnumber
Sum of each distinct linked root's lifetime FSC.
root_session_idsstring[]
Linked root session ids, newest first. Capped at 500.
session_ids_truncatedboolean
true when root_session_ids hit the cap; session_count then floors at 500.
linked_ticket_countinteger
Work items under the container with at least one linked root session.

Meta Fields

date_basisstring
The basis applied to the window and order.

Example

Bash
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/projects?startDate=2026-08-01&endDate=2026-10-31&date_basis=completed"
  • Project cost rule: a root session that linked several tickets of one project counts once, and a root that linked the container directly counts even when it linked none of its tickets. A project's factory_standard_credits can therefore be above or below the sum of its tickets' values; the union of root_session_ids is the only safe reconciliation.
  • linked_ticket_count counts attributed work items, not every work item in the container.

Joining rows

FromToKey
/prs url/sessions pull_requests[].urlCanonical PR URL
/tickets url, key/sessions issues[].url, issue_key=Issue URL, issue key
/tickets project.url/projects urlContainer URL
attribution.root_session_ids[]/sessions session_idRoot session id
attribution.contributor_user_ids[], author.user_id, assignee.user_id/sessions owner_user_id, /users user_idFactory user id (null while the provider identity is unlinked)

Important constraints

Date requirements

Format
All dates must be YYYY-MM-DD.
Timezone
UTC only (no timezone parameter).
Data availability

endDate may be today (UTC), which returns partial data for the current UTC day. A future date returns a 400 error.

Historical data
Available from January 14, 2026.
Range
At most 90 days per request. /tokens has no cap; /sessions and the cost attribution routes allow 366 days.

Rate limits

100 requests per minute per organization. Exceeding it returns 429. Contact us if you need higher limits for dashboard or automation use cases.

Errors

Errors carry a stable code next to title, status, detail, and requestId. Branch on code; detail is human-readable text and can change.

StatuscodeDescription
400invalid_parameterInvalid date format, future end date, range over the cap, limit out of range, unknown state, or a cursor issued under other filters
401Missing or invalid API key
403enterprise_requiredOrganization is not on Enterprise
403insufficient_roleCaller does not have the Manager or Owner role, or uses a service-account key on a route that does not accept one
403credential_not_allowedKey that Factory issues to computers, sandboxes, or delegations, on /sessions, /prs, /tickets, /projects, or /cost/by-user-trigger
403preview_access_requiredOrganization is not in the Private Preview
403advanced_analytics_requiredAdvanced Analytics is not enabled, including an Advanced Analytics filter on /sessions
429rate_limitedRate limit exceeded. Retry after the Retry-After header
500Internal error
503data_not_ready/sessions, /prs, /tickets, or /projects data is not built yet or is rebuilding. Retry after the Retry-After header

See Permissions for which 403 codes each route can return.

Error response format

JSON
{
  "title": "Bad Request",
  "code": "invalid_parameter",
  "detail": "endDate cannot be in the future. Please use today (UTC) or earlier as the end date.",
  "status": 400,
  "requestId": "req_01HPMQ9WXYZ..."
}

Data pipeline

Analytics data flows through the following pipeline:

CLI/Daemon → OTEL Events → BigQuery (raw) → dbt models → API
  • Source: OpenTelemetry spans from the CLI and daemon
  • Processing: Daily batch aggregation via dbt
  • Availability: Today's date returns partial data for the current UTC day. /sessions, /prs, /tickets, and /projects can return 503 while their data rebuilds

Data quality notes

Note

A few known data quality considerations:

  • MCP server names: Some duplicates exist due to case sensitivity (e.g., axiom vs Axiom)
  • Tool names: Approximately 0.006% of entries contain parsing artifacts
  • User counts: A user active on multiple clients counts once in DAU but appears in each client breakdown

Use cases

Cost monitoring dashboard

Track usage trends and identify cost drivers:

Bash
# Daily usage for the month
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/tokens?startDate=2026-01-14&endDate=2026-01-28"

Adoption tracking

Monitor DAU/WAU/MAU and identify adoption patterns:

Bash
# Activity metrics with client breakdown
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/activity?startDate=2026-01-14&endDate=2026-01-28&group_by=client"

Team productivity reports

Measure output and efficiency:

Bash
# Productivity metrics
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/productivity?startDate=2026-01-14&endDate=2026-01-28"

Individual performance

Pull per-user metrics for team leads:

Bash
# Paginate through all users
curl -H "Authorization: Bearer $FACTORY_API_KEY" \
  "https://api.factory.ai/api/v1/analytics/users?startDate=2026-01-15&endDate=2026-01-15&limit=100"