Skip to content

Partial Usage Logging

Use partial usage logging when runtime traffic arrives before Spendware has a matching project, customer, or provider catalog record. Spendware accepts the usage event, stores unresolved partial resources, and lets an operator finalize those resources later so eligible usage can materialize into ledger-backed analytics and billing.

When To Use This

Use this flow for imports, staged migrations, or multi-tenant systems that know stable external IDs before the corresponding Spendware records exist. Prefer normal customer-key logging when the customer and project already exist.

Partial logging requires a user API key, allow_unregistered_usage_logging enabled for that user, and explicit project_external_id, customer_external_id, and provider_external_id values.

This is an advanced import and migration path. Normal runtime integrations should create the project and customer first, then use a customer-scoped key; normal logging does not require external IDs, though stable IDs remain recommended for reconciliation.

Concepts

  • Partial project: unresolved usage grouped by project_external_id.
  • Partial customer: unresolved usage grouped by project_external_id and customer_external_id.
  • Partial provider: unresolved usage grouped by provider/model identity.
  • Finalization: creates or links the real resource and transfers eligible partial usage.
  • Analytics inclusion: read requests default to finalized usage; use partials_mode: include_eligible_partials when a report should include eligible unresolved partials.

Example

curl "$SPENDWARE_BASE_URL/api/v1/usage/log" \
  -H "Authorization: Bearer $SPENDWARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_external_id": "acme-ai",
    "customer_external_id": "customer-a",
    "provider_external_id": "openai:gpt-4o",
    "service_provider_name": "openai",
    "service_name": "gpt-4o",
    "number_requests": 1,
    "input_tokens": 120,
    "output_tokens": 45,
    "cached_tokens": 30,
    "idempotency_key": "partial:usage-001"
  }'

curl "$SPENDWARE_BASE_URL/api/v1/partial/projects?is_resolved=false" \
  -H "Authorization: Bearer $SPENDWARE_API_KEY"

curl "$SPENDWARE_BASE_URL/api/v1/partial/projects/PARTIAL_PROJECT_ID/finalize" \
  -H "Authorization: Bearer $SPENDWARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Acme AI","currency":"USD","external_id":"acme-ai"}'
from spendware_provider_sdk.management import SpendwareManagementClient

client = SpendwareManagementClient(base_url="SPENDWARE_BASE_URL", api_key="SPENDWARE_API_KEY")
client.usage.log(
    {
        "project_external_id": "acme-ai",
        "customer_external_id": "customer-a",
        "provider_external_id": "openai:gpt-4o",
        "service_provider_name": "openai",
        "service_name": "gpt-4o",
        "number_requests": 1,
        "input_tokens": 120,
        "output_tokens": 45,
        "cached_tokens": 30,
        "idempotency_key": "partial:usage-001",
    }
)

partials = client.partial_usage.list_projects()
partial_id = partials["data"][0]["id"]
client.partial_usage.finalize_project(
    partial_id,
    {"title": "Acme AI", "currency": "USD", "external_id": "acme-ai"},
)
client.close()
import { SpendwareManagementClient } from "@spendware/provider-adapters-sdk/management";

const client = new SpendwareManagementClient({
  baseUrl: "SPENDWARE_BASE_URL",
  apiKey: "SPENDWARE_API_KEY",
});

await client.usage.log({
  project_external_id: "acme-ai",
  customer_external_id: "customer-a",
  provider_external_id: "openai:gpt-4o",
  service_provider_name: "openai",
  service_name: "gpt-4o",
  number_requests: 1,
  input_tokens: 120,
  output_tokens: 45,
  cached_tokens: 30,
  idempotency_key: "partial:usage-001",
});

const partials = await client.partialUsage.listProjects();
const partial = (partials.data as Array<{ id: string }>)[0];
await client.partialUsage.finalizeProject(partial.id, {
  title: "Acme AI",
  currency: "USD",
  external_id: "acme-ai",
});

Expected Result

The log call returns an accepted usage response. The partial list shows unresolved resources until they are finalized. After finalization, eligible usage can be queried through normal usage analytics, and future logs with the same external IDs can resolve to the finalized resources.

Common Errors

  • 403: unregistered usage logging is disabled for the user API key owner.
  • 422: one of project_external_id, customer_external_id, or provider_external_id is missing.
  • 422: an external ID conflicts with an already resolved project or provider.
  • 404: the partial resource ID was deleted, resolved, or belongs to another user.

Provider finalization is an administrative flow and may require the platform usage-operations permission. Project and customer finalization are the usual developer-facing cleanup path.