Spend, delivery and accuracy reports

Read what a project spent, how its executions ended, how close the estimates of its model calls were and what its customers cost.

The operations of this page are reports over what a project has already done. Each of them reads the project of the key and changes nothing. The console draws them on Overview, and the customer margin on Customers.

Which report answers what

ReportWhat it answers
GET /v1/analytics/spendWhat the project spent today and in the current calendar month, day by day, and how much of its budgets is left.
GET /v1/analytics/costsWhere the money of a window went: by day, by model and by provider connection.
GET /v1/analytics/deliveryHow the executions of the project's flows ended in a window, how many answers came from the cache and what the cache saved.
GET /v1/analytics/accuracyHow far the estimates of the model calls were from what the calls cost, in total and by operation.
GET /v1/analytics/customer-marginThe credits your customers were charged, against what their calls cost.

The spend report and the cost breakdown count differently, so their totals may differ. The spend report sums what the ledger settled. The cost breakdown sums the cost of each attempt at a provider.

The delivery report counts executions of flows.

The window of a report

The spend report takes no window. Each of the other reports takes from and to, and both are optional.

  • Without to the window ends now. Without from it starts seven days before its end.

  • A window that is too long is not refused: its start is moved forward. The cost breakdown and the customer margin reach 31 days back from the end of the window, and the delivery and the accuracy reports 366 days.

  • When from is not earlier than to, the window is the seven days before to.

The rule of the lists — both ends together, seven days at most — does not apply to a report, and a report does not answer invalid_date_range. The answer names the window that was used.

The dashboard of a flow (Flows) takes its window the same way and counts only whole hours that have closed: its from and to are moved to the start of their hour, and the hour in progress is left out.

Operations

The ledger's standing figure — today, this month, headroom

GET /v1/analytics/spend

  • Scope: read

No window: today, this month, and the headroom right now.

Responses

200

Spend summary

application/json
{
  "data": {
    "currency": "USD",
    "spend_today": "0.02700000",
    "spend_month": "0.02700000",
    "active_reservations": 1,
    "request_count": 1,
    "average_cost": "0.02700000",
    "denied_operations": 1,
    "available_budget": "0.02700000",
    "daily": [
      {
        "date": "string",
        "spend": "0.02700000",
        "count": 1
      }
    ]
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

Headers

  • X-Request-Id

The body of every error is the error envelope.

Success and cache rates over the hourly rollup

GET /v1/analytics/delivery

  • Scope: read

Parameters

ParameterDescription
from optional

querystring

Start of the window an analytics answer covers — a date the server can parse, ISO-8601 recommended (a value without an offset is read as UTC). Unlike the lists, each end is optional on its own and the window is never refused, only fitted (AnalyticsWindowResolver):

  • Format: date-time
to optional

querystring

End of the window an analytics answer covers; now when absent. See from (WindowFrom) for how the window is fitted rather than refused.

  • Format: date-time

Responses

200

Delivery summary

application/json
{
  "data": {
    "from": "2026-10-02T09:30:00Z",
    "to": "2026-10-02T09:30:00Z",
    "requests": 1,
    "completed": 1,
    "failed": 1,
    "rejected": 1,
    "cache_hits": 1,
    "success_rate": "string",
    "cache_hit_rate": "string",
    "p95_duration_ms": 1,
    "active_flows": 1,
    "saved_cost": "0.02700000",
    "prevented_spend": "0.02700000"
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

Headers

  • X-Request-Id

The body of every error is the error envelope.

How wrong the estimates were

GET /v1/analytics/accuracy

  • Scope: read

Summed over the daily accuracy snapshots, so the window is honoured. A window the snapshots do not cover yet answers the lifetime counters with is_all_time: true.

Parameters

ParameterDescription
from optional

querystring

Start of the window an analytics answer covers — a date the server can parse, ISO-8601 recommended (a value without an offset is read as UTC). Unlike the lists, each end is optional on its own and the window is never refused, only fitted (AnalyticsWindowResolver):

  • Format: date-time
to optional

querystring

End of the window an analytics answer covers; now when absent. See from (WindowFrom) for how the window is fitted rather than refused.

  • Format: date-time

Responses

200

Estimation accuracy

application/json
{
  "data": {
    "window": {
      "from": "string",
      "to": "string"
    },
    "is_all_time": true,
    "history_since": "string",
    "trend": [
      {
        "day": "string",
        "sample_count": 1,
        "mae_cost": "0.02700000",
        "bias_cost": "0.02700000",
        "cost_relative_error": "string"
      }
    ],
    "sample_count": 1,
    "underestimated_input_count": 1,
    "underestimated_input_rate": "string",
    "avg_input_token_error": "string",
    "avg_output_token_error": "string",
    "avg_cost_error": "0.02700000",
    "cost_error_sum": "0.02700000",
    "mae_cost": "0.02700000",
    "mae_input_tokens": "string",
    "mae_output_tokens": "string",
    "cost_relative_error": "string",
    "input_token_relative_error": "string",
    "output_token_relative_error": "string",
    "bias_direction": "under",
    "estimated_cost_sum": "0.02700000",
    "actual_cost_sum": "0.02700000",
    "by_operation": [
      {
        "operation_code": "generate_email",
        "provider": "string",
        "model": "gpt-4o-mini",
        "sample_count": 1,
        "underestimated_input_count": 1,
        "avg_cost_error": "0.02700000",
        "mae_cost": "0.02700000",
        "cost_relative_error": "string",
        "bias_direction": "under",
        "needs_attention": true,
        "estimated_cost_sum": "0.02700000",
        "actual_cost_sum": "0.02700000"
      }
    ]
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

Headers

  • X-Request-Id

The body of every error is the error envelope.

Where the money went, cut by day / model / connection

GET /v1/analytics/costs

  • Scope: read

Attributed per attempt over a caller-chosen window — deliberately not the same number as /v1/analytics/spend, which is the ledger's.

Parameters

ParameterDescription
from optional

querystring

Start of the window an analytics answer covers — a date the server can parse, ISO-8601 recommended (a value without an offset is read as UTC). Unlike the lists, each end is optional on its own and the window is never refused, only fitted (AnalyticsWindowResolver):

  • Format: date-time
to optional

querystring

End of the window an analytics answer covers; now when absent. See from (WindowFrom) for how the window is fitted rather than refused.

  • Format: date-time

Responses

200

Cost breakdown

application/json
{
  "data": {
    "window": {
      "from": "2026-10-02T09:30:00Z",
      "to": "2026-10-02T09:30:00Z"
    },
    "currency": "USD",
    "total_cost": "0.02700000",
    "requests": 1,
    "attempts": 1,
    "by_day": [
      {
        "key": "string",
        "cost": "0.02700000",
        "requests": 1,
        "attempts": 1,
        "share": "string"
      }
    ],
    "by_model": [
      {
        "key": "string",
        "cost": "0.02700000",
        "requests": 1,
        "attempts": 1,
        "share": "string"
      }
    ],
    "by_connection": [
      {
        "key": "string",
        "cost": "0.02700000",
        "requests": 1,
        "attempts": 1,
        "share": "string"
      }
    ],
    "by_model_connection": [
      {
        "model_code": "gpt-4o-mini",
        "connection_id": "74438200973594624",
        "cost": "0.02700000",
        "requests": 1,
        "attempts": 1,
        "share": "string"
      }
    ]
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

Headers

  • X-Request-Id

The body of every error is the error envelope.

What we charged in credits against what the calls cost us

GET /v1/analytics/customer-margin

  • Scope: read

Two sides in two units, never a ratio: credits and dollars are different things, and only the tenant knows what a credit sells for. Reads raw journal and attempt rows, so the window is capped.

Parameters

ParameterDescription
from optional

querystring

Start of the window an analytics answer covers — a date the server can parse, ISO-8601 recommended (a value without an offset is read as UTC). Unlike the lists, each end is optional on its own and the window is never refused, only fitted (AnalyticsWindowResolver):

  • Format: date-time
to optional

querystring

End of the window an analytics answer covers; now when absent. See from (WindowFrom) for how the window is fitted rather than refused.

  • Format: date-time
external_user_id optional

querystring

Narrows the report to one customer; absent means all of them.

  • Maximum length: 190

Responses

200

Margin for the window, in total and per customer

application/json
{
  "data": {
    "window": {
      "from": "2026-10-02T09:30:00Z",
      "to": "2026-10-02T09:30:00Z"
    },
    "currency": "USD",
    "total": {
      "external_user_id": "user_8421",
      "credits_spent": "12.5000",
      "usd_cost": "0.02700000",
      "requests": 1
    },
    "by_customer": [
      {
        "external_user_id": "user_8421",
        "credits_spent": "12.5000",
        "usd_cost": "0.02700000",
        "requests": 1
      }
    ]
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Examples

Read the spend of the month

average_cost is spend_month divided by request_count. available_budget is the smallest amount left among the budgets of the project, and null when the project has none.

Terminal
curl https://api.slen.ai/api/v1/analytics/spend \
  -H "Authorization: Bearer $SLEN_API_KEY"
200 OK
{
  "data": {
    "currency": "USD",
    "spend_today": "3.18240000",
    "spend_month": "41.90775000",
    "active_reservations": 0,
    "request_count": 1301,
    "average_cost": "0.03221195",
    "denied_operations": 5,
    "available_budget": "458.09225000",
    "daily": [
      {
        "date": "2026-10-01",
        "spend": "38.72535000",
        "count": 1204
      },
      {
        "date": "2026-10-02",
        "spend": "3.18240000",
        "count": 97
      }
    ]
  }
}

Read how executions ended

The window is the month of September.

Terminal
curl "https://api.slen.ai/api/v1/analytics/delivery?from=2026-09-01T00%3A00%3A00Z&to=2026-10-01T00%3A00%3A00Z" \
  -H "Authorization: Bearer $SLEN_API_KEY"

Read the accuracy of estimates

When the window has no samples, the answer carries the figures of the whole history of the project, and says so with is_all_time set to true.

Terminal
curl "https://api.slen.ai/api/v1/analytics/accuracy?from=2026-09-01T00%3A00%3A00Z&to=2026-10-01T00%3A00%3A00Z" \
  -H "Authorization: Bearer $SLEN_API_KEY"

Break the cost down

The window is seven days. share of a slice is its part of total_cost.

Terminal
curl "https://api.slen.ai/api/v1/analytics/costs?from=2026-09-25T00%3A00%3A00Z&to=2026-10-02T00%3A00%3A00Z" \
  -H "Authorization: Bearer $SLEN_API_KEY"

Read the margin by customer

The two sides are in two units: credits_spent is credits, and usd_cost is money. Only captured calls count. external_user_id in the query narrows the report to one customer.

Terminal
curl "https://api.slen.ai/api/v1/analytics/customer-margin?from=2026-09-25T00%3A00%3A00Z&to=2026-10-02T00%3A00%3A00Z" \
  -H "Authorization: Bearer $SLEN_API_KEY"
200 OK
{
  "data": {
    "window": {
      "from": "2026-09-25T00:00:00+00:00",
      "to": "2026-10-02T00:00:00+00:00"
    },
    "currency": "USD",
    "total": {
      "credits_spent": "36.0000",
      "usd_cost": "0.41280000",
      "requests": 18
    },
    "by_customer": [
      {
        "external_user_id": "user_8421",
        "credits_spent": "24.0000",
        "usd_cost": "0.27520000",
        "requests": 12
      },
      {
        "external_user_id": "user_3107",
        "credits_spent": "12.0000",
        "usd_cost": "0.13760000",
        "requests": 6
      }
    ]
  }
}

Where to go next

  • Overview — how money, credits and time are written in an answer.

  • Authentication — the scope a key needs to read a report.

Built from openapi b070e1f, sdk contract 5cc7055, php ae4a53b, node 84bc62d