# Report Usage via API

## Overview

You report usage to **Suger**, against a Suger entitlement; Suger aggregates pending usage and submits it
to OCI on an **hourly** cadence. The endpoints are the same generic
[Metering API](/billing/metering) used for every marketplace — this page documents the Oracle-specific
field semantics.

The two rules that make Oracle different from AWS/Azure/GCP:

1. **The value you report is a monetary amount, not a quantity.** OCI has no dimension catalog and no
   per-dimension rate card, so Suger applies no rate — `"records": {"usage": 10}` bills **10 units of the
   entitlement's currency**.
2. **Usage keys are open.** Any key name is accepted and auto-registered on the entitlement the first time
   it is seen — no dimension configuration in advance. An entitlement may accumulate at most **100**
   distinct keys.

## Endpoints

| Purpose | Endpoint | Operation |
|---|---|---|
| Report one group (deduplicated by `id`) | `POST /org/{orgId}/entitlement/{entitlementId}/usageRecordGroup` | `ReportUsageRecordGroup` |
| Report a batch (no dedup key) | `POST /org/{orgId}/batchCreateUsageRecordGroups` | `BatchReportUsageRecordGroups` |
| List groups (check status / reported time) | `GET /org/{orgId}/usageRecordGroup` | `ListUsageRecordGroups` |

Auth: `Authorization: Bearer <access_token>` — see [OAuth App](/get-started/oauth-app).

Prefer the single-group endpoint with your own `id` for anything automated: the `id` is the idempotency
key, so a retried request cannot double-report. The batch endpoint carries no dedup key.

Supporting operations (aggregate, validate, retry, delete) are documented in the generic
[Metering API](/billing/metering) reference.

## Request fields — `CreateUsageRecordGroupParams`

| Field | Required | Type | Notes |
|-------|----------|------|-------|
| `id` | ⬜ | string (≤36 chars) | Your idempotency key, typically a UUID. Reporting the same `id` again within **15 days** is rejected with a `400` instead of double-billing. Omitted → Suger generates one (and you lose dedup protection). |
| `entitlementID` | ✅ | string | The Suger entitlement to bill. Must belong to the org, partner `ORACLE`. In the single-group endpoint this comes from the URL path instead. |
| `records` | ✅ | map&lt;string, number&gt; | **The data.** Key → monetary amount (see dictionary below). |
| `timestamp` | ⬜ | RFC-3339 date-time | When the usage was **generated** (not when it is reported). Defaults to the report time. |
| `billableRecords` | ❌ | `MeteringUsageRecord[]` | **Not supported for Oracle.** This is the Metering API v2 field; Oracle reports **v1 `records` only**. A group reported with `billableRecords` instead of `records` is accepted at ingestion but skipped by the hourly OCI submission — the usage is never billed. Always use `records`. |
| `metaInfo` | — | object | **Read-only.** Populated by Suger (origin records, source, invoice linkages). Sending it has no effect. |
| `usageAllocations` | — | map | AWS-only feature; ignored for Oracle. |

### `records` — the field data dictionary

`records` is a flat map of usage key → amount:

```json
"records": {
  "usage": 12.5,
  "api-calls": 3.99
}
```

| Aspect | Rule |
|---|---|
| **Key** (map key) | Any name you choose — Oracle usage keys are **open**. The key is auto-registered on the entitlement on first use and becomes the OCI `usageDimensionName`. Use stable, human-readable names (`usage`, `api-calls`, `gb-processed`). |
| **Key cap** | At most **100 distinct keys** per entitlement, matching OCI's per-submission record cap. The 101st new key is rejected at ingestion with a `usage dimensions … exceeding the 100 cap` error. |
| **Value** (map value) | The **monetary amount to bill**, in the entitlement's currency — not a consumption quantity. Decimals are supported; the submitted amount is **rounded to 5 decimal places** (OCI's per-record limit), and an amount that rounds to zero is skipped. |
| **Zero values** | Skipped — OCI rejects zero-amount records. A group whose values are *all* zero is rejected at ingestion (`400: all the quantity of usage records are zero`). |
| **Negative values** | Rejected. Usage can only add charges; corrections go through Oracle support. |
| **Commit interaction** | None. Amounts are forwarded as-is; no commit/credit/included-usage deduction is applied. |

## Response fields — `MeteringUsageRecordGroup`

| Field | Type | Notes |
|-------|------|-------|
| `id` | string | The group's ID (yours, or generated). Used for retry/delete and as the dedup key. |
| `organizationID` / `entitlementID` / `buyerID` | string | Ownership. `buyerID` is resolved from the entitlement. |
| `partner` | string | `ORACLE`. |
| `records` | map | The records as stored (after any dimension mapping; for Oracle open keys this equals what you sent). |
| `status` | string | Lifecycle state — see below. |
| `creationTime` / `lastUpdateTime` | date-time | Bookkeeping. |
| `usageRecordReportID` | string | Set once the hourly job folds the group into an OCI submission — joins to `GET /org/{orgId}/usageRecordReport`. |
| `reportedTime` | date-time \| null | When the group was reported to OCI. `null` until then. |
| `metaInfo.originRecords` | map | The records exactly as you reported them, before any conversion. |
| `metaInfo.timestamp` | date-time | The usage-generation timestamp you supplied. |
| `metaInfo.source` | string | `API` for groups created this way. |

### Status lifecycle

| Status | Meaning |
|---|---|
| `CREATED` | Accepted and queued; the hourly job will pick it up. |
| `REPORTED` | Folded into an OCI submission; `usageRecordReportID` and `reportedTime` are set. |
| `REPORT_FAILED` | The OCI submission failed. Inspect, then `retry` (moves it back to `CREATED`). |
| `INVALID` | Rejected by validation after creation. |
| `DELETED` | Hard-deleted via the delete endpoint. |

:::warning
Automatic retries inside one submission are duplicate-protected, but a **manual `retry` is not**: the
resubmitted records carry new identifiers, so OCI cannot recognise them as repeats. Before retrying a
`REPORT_FAILED` group, confirm in OCI that the usage was not already recorded. See
[Usage Metering – duplicate protection](/oracle-marketplace/usage-metering).
:::

## What Suger submits to OCI

Each key/amount pair becomes one record in OCI's `submitUsageRecords` call (at most 100 records per call,
all-or-nothing):

| OCI field | Sourced from |
|---|---|
| `id` | Derived deterministically from the group ID + key, so an automatic retry is idempotent. |
| `marketplaceOfferId` | The entitlement's Oracle private-offer OCID. |
| `usageDimensionName` | The `records` map key (the literal `usage` when no specific dimension applies). |
| `amount` | The `records` map value, at full precision. |
| `currencyCode` | The entitlement's currency. |
| `timeUsageStarted` / `timeUsageEnded` | The aggregation window of the hourly report run. |

## Example

Report one group, idempotent by `id`:

```shell
curl -L -X POST 'https://api.suger.cloud/org/YOUR_ORG_ID/entitlement/YOUR_ENTITLEMENT_ID/usageRecordGroup' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  --data-raw '{
    "id": "2f9c1e58-9a1b-4c7e-b1d2-8f3a5c6d7e90",
    "records": {
      "usage": 12.5,
      "api-calls": 3.99
    },
    "timestamp": "2026-08-05T00:00:00Z"
  }'
```

Response (trimmed):

```json
{
  "id": "2f9c1e58-9a1b-4c7e-b1d2-8f3a5c6d7e90",
  "organizationID": "YOUR_ORG_ID",
  "partner": "ORACLE",
  "entitlementID": "YOUR_ENTITLEMENT_ID",
  "buyerID": "gHYis9RKU",
  "records": { "usage": 12.5, "api-calls": 3.99 },
  "status": "CREATED",
  "metaInfo": {
    "originRecords": { "usage": 12.5, "api-calls": 3.99 },
    "timestamp": "2026-08-05T00:00:00Z",
    "source": "API"
  }
}
```

Report a batch — the body is an **array**; each entry needs `entitlementID` + `records` (the response is
the array of created groups, one per entry, with generated `id`s):

```shell
curl -L -X POST 'https://api.suger.cloud/org/YOUR_ORG_ID/batchCreateUsageRecordGroups' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  --data-raw '[
    {
      "entitlementID": "YOUR_ENTITLEMENT_ID",
      "records": { "usage": 0.75 },
      "timestamp": "2026-08-05T00:00:00Z"
    },
    {
      "entitlementID": "ANOTHER_ENTITLEMENT_ID",
      "records": { "api-calls": 4.2 }
    }
  ]'
```

Since batch entries carry no caller-supplied `id`, a retried batch request **re-creates** the groups —
use the single-group endpoint when you need idempotency.

## Errors

| Response | Cause / fix |
|---|---|
| `400: the record id … has already been reported in the last 15 days` | A group with the same `id` was reported within the 15-day dedup window — this is the dedup working. Do not resend with a new `id` unless the usage is genuinely new. |
| `400: all the quantity of usage records are zero` | Every value in `records` is `0`. Zero-amount records are never billable; skip the report instead. |
| `400: … usage dimensions, exceeding the 100 cap …` | The report would push the entitlement past 100 distinct usage keys. Reuse existing keys, or prune the entitlement's registered dimensions. |
| `400` naming the entitlement | The entitlement doesn't exist, isn't in this org, or isn't in a reportable state. |
