# SuperPenguin FOCUS AI Provider Profile v0.1

Status: Draft

FOCUS semantic baseline: 1.4

Transport: HTTPS JSON

Last updated: 2026-07-28

## 1. Purpose

This profile defines a small, provider-implementable API for exporting AI
usage and billed costs to authorized FinOps clients such as SuperPenguin.

It uses field names and semantics from FOCUS 1.4 where FOCUS has an applicable
concept. AI-specific dimensions use FOCUS custom columns prefixed with `x_`.
Implementing this profile does not, by itself, imply conformance with the full
FOCUS Cost and Usage dataset.

The design goals are:

1. Easy implementation by inference, speech, gateway, and agent providers.
2. Lossless extension to new meters, dimensions, currencies, and permissions.
3. Secure, revocable, least-privilege access to customer billing data.

The normative machine-readable contract is `openapi.yaml`. This document is
normative for behavior that OpenAPI cannot fully express.

## 2. Conformance language

The terms MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are used as described by
RFC 2119 and RFC 8174.

A Core implementation MUST provide:

- `GET /v1/finops/capabilities`
- `GET /v1/finops/usage`
- HTTPS in production
- Bearer-token authorization
- Exhaustive cursor pagination
- Stable record identifiers and repeatable time-window reads

A Billing implementation additionally MUST provide:

- `GET /v1/finops/costs`
- Provider-reported `BilledCost`
- Billing currency and billing-period fields

A Delegated implementation additionally MUST provide:

- OAuth 2.0 Authorization Code with PKCE
- Refresh tokens suitable for unattended synchronization
- The scopes and discovery metadata defined in section 8

## 3. General HTTP behavior

All endpoints MUST:

- Send and receive `application/json`, except RFC 9457 problem responses which
  use `application/problem+json`.
- Use UTC RFC 3339 timestamps.
- Treat `start` as inclusive and `end` as exclusive.
- Return `401` for a missing or invalid credential and `403` for a valid
  credential that lacks the required scope or account access.
- Return `429` with `Retry-After` when rate limited.
- Return an RFC 9457 problem document for non-2xx responses.
- Avoid returning inference request content, prompts, completions, or secret
  API-key material.

The API MUST accept a dedicated scoped bearer token. It MUST NOT require a
customer to provide the same credential used to run inference.

## 4. Capability discovery

`GET /v1/finops/capabilities` describes what the authenticated customer can
export. It MUST be safe to call during connection validation.

The response declares:

- Profile and FOCUS versions.
- Supported datasets.
- Endpoint paths.
- Supported granularities.
- Retention and maximum query windows.
- Data freshness or settlement lag.
- Available dimensions and currencies.
- Supported authorization methods.

Capabilities MUST describe actual behavior. A provider MUST NOT advertise
hourly granularity if it spreads a daily total across synthetic hourly rows.

The `dimensions` array distinguishes unsupported dimensions from unattributed
values. When a dimension is advertised but unavailable for one record, that
record returns the corresponding field as `null`. When a dimension is not
advertised, the field is omitted.

## 5. Usage export

### 5.1 Request

```http
GET /v1/finops/usage?start=2026-07-01T00:00:00Z&end=2026-07-08T00:00:00Z&granularity=1h&limit=1000
Authorization: Bearer <credential>
```

Supported granularities in v0.1 are `1h` and `1d`. A provider MUST reject an
unsupported granularity with `400`.

Providers SHOULD support at least 90 days of history. Providers with shallower
retention MUST declare the actual limit in capabilities.

### 5.2 Record shape

One usage record represents one meter for one UTC time bucket and one complete
set of available dimensions. Input tokens, output tokens, cache reads, and
requests therefore appear as separate records.

Required fields:

- `x_RecordId`: Stable opaque identifier for this logical record.
- `x_RecordUpdatedAt`: Last time this record was restated.
- `x_Granularity`: `1h` or `1d`.
- `ChargePeriodStart`: Inclusive bucket start.
- `ChargePeriodEnd`: Exclusive bucket end.
- `ServiceProviderName`: Provider display name.
- `ServiceCategory`: MUST be `AI and Machine Learning`.
- `ServiceName`: Provider product or service offering.
- `SkuId`: Stable provider SKU identifier.
- `SkuMeter`: Human-readable meter name.
- `ConsumedQuantity`: Decimal string.
- `ConsumedUnit`: Canonical unit from section 5.3 or a provider extension.
- `x_UsageType`: Canonical machine-readable meter type.

AI dimensions:

- `x_ModelId`: Provider-native model identifier. Required for model inference
  when the source data contains it.
- `x_ProjectId`
- `x_WorkspaceId`
- `x_ApiKeyId`: An opaque identifier or irreversible hash, never the secret.
- `x_ServiceTier`
- `x_WorkloadType`: Optional modality such as `completions`, `embeddings`,
  `audio_in`, or `tts`.
- `x_UpstreamProvider`: Actual serving host for gateways or routers.

`SkuId` identifies the billable good. It MUST NOT be used as the only model
identifier when one model has multiple input, output, cache, or tier SKUs.

### 5.3 Usage vocabulary

Core `x_UsageType` values and their corresponding `ConsumedUnit` are:

| x_UsageType | ConsumedUnit | Semantics |
|---|---|---|
| `input_tokens` | `tokens` | Uncached input tokens only |
| `output_tokens` | `tokens` | Generated output tokens |
| `cache_read_tokens` | `tokens` | Input tokens served from cache |
| `cache_write_5m_tokens` | `tokens` | Input tokens written to a 5-minute cache |
| `cache_write_1h_tokens` | `tokens` | Input tokens written to a 1-hour cache |
| `input_audio_tokens` | `tokens` | Audio input tokens |
| `output_audio_tokens` | `tokens` | Audio output tokens |
| `requests` | `requests` | Completed or billable requests |
| `audio_seconds` | `seconds` | Audio duration |
| `characters` | `characters` | Text characters |
| `events` | `events` | Billable events |
| `session_minutes` | `minutes` | Session duration |
| `credits` | `credits` | Provider-native credits consumed |

Providers MAY add meter values prefixed with `x_`. Consumers MUST preserve
unknown extension records even when they cannot yet map them to a native
metric. Providers SHOULD propose broadly useful meters for a future profile
revision instead of creating semantically equivalent extensions.

`input_tokens` is deliberately uncached input. Summing it with
`cache_read_tokens` therefore produces total input without double-counting.

### 5.4 Completeness

The usage feed MUST be exhaustive for the selected time window and authorized
account. It MUST NOT silently return only the top N models, projects, or keys.

If the provider API has an internal grouping cap, it MUST paginate all groups
or expose a less detailed but complete aggregate. Complete aggregates are
preferable to incomplete detailed data.

## 6. Cost export

### 6.1 Request

```http
GET /v1/finops/costs?start=2026-07-01T00:00:00Z&end=2026-07-08T00:00:00Z&limit=1000
Authorization: Bearer <credential>
```

Usage and costs MAY have different granularities, dimensions, retention, and
freshness. Providers MUST NOT fabricate model-level or API-key-level costs by
allocating an organization total unless the resulting row is explicitly
identified as an allocation rather than provider-billed truth.

### 6.2 Cost semantics

`BilledCost` follows FOCUS 1.4 semantics. It is the provider-reported amount
in `BillingCurrency`, after applicable pricing adjustments. It MUST NOT contain
an estimate calculated from a public rate card.

`EffectiveCost` equals `BilledCost` for simple pay-as-you-go usage. It may
differ for commitments, prepaid credits, quota coverage, or amortization.

Money fields are JSON decimal strings to preserve precision in transit.
Consumers convert them to fixed-point storage after parsing.

For non-USD billing:

- `BilledCost` MUST contain the actual amount in `BillingCurrency`.
- `PricingCurrency` and `PricingCurrencyBilledCost` SHOULD contain the
  provider-computed USD equivalent when available.
- A consumer MUST NOT invent an exchange rate and label it billed cost.

`x_CostStatus` is:

- `provisional`: The provider expects the amount may be restated.
- `final`: The provider considers the amount settled for the represented
  billing state.

### 6.3 Corrections

Open-period restatements MUST update the same `x_RecordId` and advance
`x_RecordUpdatedAt`.

Closed-period corrections SHOULD use a new negative `Credit` or `Adjustment`
record with `ChargeClass` set according to FOCUS and `x_CorrectsRecordId`
pointing to the corrected record when known.

## 7. Pagination and snapshot consistency

Responses use:

```json
{
  "data": [],
  "next_cursor": null,
  "as_of": "2026-07-08T01:05:00Z",
  "available_through": "2026-07-08T01:00:00Z"
}
```

Rules:

- `next_cursor` is an opaque string or `null`.
- The cursor MUST bind the original account, filters, time window, requested
  granularity, page size, and logical snapshot.
- Clients repeat the original query parameters when sending a cursor.
- A page MUST NOT repeat a record already returned by an earlier page.
- The final page MUST return `next_cursor: null`.
- `as_of` identifies the logical export snapshot.
- `available_through` identifies the latest complete charge period.
- Empty windows return `200` with an empty `data` array.
- Providers SHOULD produce a deterministic order such as
  `ChargePeriodStart`, then `x_RecordId`.

Records may be restated between separate snapshot traversals. They MUST remain
stable during one cursor traversal.

## 8. Authorization

### 8.1 Baseline scoped token

A Core implementation MUST support a dedicated bearer token with:

- Read-only access.
- Binding to the authorized billing account or tenant.
- Separate scopes for usage and cost.
- Revocation.
- Optional expiration and rotation.
- One-time display when created.

Recommended scopes:

- `billing.account.read`
- `billing.usage.read`
- `billing.cost.read`

The token MUST NOT grant inference, key-management, user-management, or billing
mutation permissions.

### 8.2 Delegated OAuth

A Delegated implementation MUST use OAuth 2.0 Authorization Code with PKCE
`S256`, following RFC 9700.

It MUST:

- Require an authorized customer administrator to approve access.
- Use exact redirect URI matching.
- Issue audience-restricted access tokens.
- Issue refresh tokens suitable for unattended synchronization.
- Rotate refresh tokens and detect replay.
- Support provider-side grant revocation.
- Publish RFC 8414 authorization-server metadata.
- Publish RFC 9728 protected-resource metadata.
- Advertise the profile scopes.

Access tokens SHOULD expire within 60 minutes.

For confidential clients, `private_key_jwt` is preferred. `client_secret_basic`
is acceptable for v0.1. DPoP or mutual TLS MAY be offered as an enhanced
sender-constrained-token profile.

OpenID Connect is not required. OAuth authorizes billing access; consumers do
not require the customer user's identity.

The client-credentials grant MAY be used for customer-managed service
principals, but it is not the default delegated customer-consent flow.

## 9. Errors and rate limits

Errors follow RFC 9457:

```json
{
  "type": "https://provider.example/problems/unsupported-granularity",
  "title": "Unsupported granularity",
  "status": 400,
  "detail": "This account supports 1d usage exports only."
}
```

Providers SHOULD send the standardized RateLimit fields from RFC 9331.
Providers MUST send `Retry-After` for `429` and temporary `503` responses.

## 10. Privacy and identifier rules

The export MUST NOT include:

- Prompt or completion content.
- Audio, image, or document payloads.
- Plaintext inference API keys.
- Customer credentials.
- Personal data unrelated to billing attribution.

`x_ApiKeyId` may contain a provider-generated opaque ID or irreversible hash.
It MUST NOT contain the key itself or a reversible fragment sufficient to use
the credential.

## 11. Implementation checklist for providers and coding agents

An implementation is ready for validation when all of the following are true:

1. Read `openapi.yaml` and this document.
2. Implement capability discovery first.
3. Implement bearer authentication and verify missing credentials fail.
4. Implement half-open UTC time filters.
5. Return one usage meter per record using the canonical vocabulary.
6. Return complete groups with cursor pagination.
7. Generate deterministic, stable record IDs.
8. Keep cursor traversals snapshot-consistent.
9. Return only provider-reported values as `BilledCost`.
10. Add OAuth after the scoped-token path works.
11. Run the conformance command:

```bash
node conformance.mjs \
  --base-url http://localhost:8787 \
  --token test-billing-token \
  --start 2026-07-01T00:00:00Z \
  --end 2026-07-02T00:00:00Z
```

The conformance runner intentionally requests a page size of one to exercise
pagination. A test provider should expose at least two usage records and two
cost records in the selected window.

## 12. Versioning

Breaking transport or semantic changes require a new profile version and a new
asset path. Additive optional fields and new `x_` extensions do not require a
new URL version.

Providers MUST report the implemented `profile_version` in capabilities.
Consumers MUST reject unsupported major versions and MAY accept compatible
minor revisions.
