PayKitBeta

Usage

Report how much of a metered feature a customer used, one total per customer, feature and day. PayKit rates each usage period after it closes, using the metered price on the plan version, and charges any overage to the customer's default card. A write sets the day's total, so a re-sent day replaces the earlier value.

PUT/v1/clients/{clientId}/projects/{projectId}/customers/{externalRef}/usage/{featureCode}/days/{day}

Set one day's usage

usage.write

Set the total used on one day. This replaces the day's value, it does not add to it. Requires an Idempotency-Key header.

Path parameters

clientIdstringYour PayKit client id.
projectIdstringThe project.
externalRefstringYour id for the user.
featureCodestringCode of a feature whose kind is metered.
daystringyyyy-MM-dd: a calendar date in the metered price's time zone, not UTC.

Request body

quantity *string | integerThe total for the day, from 0 to 9007199254740991 (2^53 - 1), as a decimal string or a JSON integer.

Errors: 400 invalid_body (bad quantity, including one above 2^53 - 1), 400 invalid_request (bad day), 400 invalid_customer_ref (empty externalRef), 404 customer_not_found, 404 feature_not_found, 409 customer_erased, 422 feature_not_metered, 422 day_in_future.

409 usage_period_closed: the day lies in a period that is already rated, or rating claimed it while the write landed, in which case your value may or may not be in the rated total. error.details.period is { usagePeriodId, subscriptionId, periodStart, periodEnd, firstDay, lastDay, status }. Reconcile against the usage read, by hand.

A customer with no subscription that meters the feature is accepted: the day is stored and billed only if a metered subscription later counts it.

curl -X PUT "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/projects/f6e5d4c3b2a1908172635445362718a9/customers/user_123/usage/bandwidth_bytes/days/2026-10-11" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"quantity":"39728447488"}'
200 OK
{
  "usageDay": {
    "customerId": "c_4d5e…",
    "externalRef": "user_123",
    "featureCode": "bandwidth_bytes",
    "day": "2026-10-11",
    "quantity": "39728447488",
    "previousQuantity": "39000000000",
    "changed": true,
    "updatedAt": "2026-10-12T01:00:03.000Z"
  }
}
POST/v1/clients/{clientId}/projects/{projectId}/usage/batch

Set many days

usage.write

Set up to 500 day totals in one call. Each record is applied on its own, exactly as the single write, and results come back in request order. Requires an Idempotency-Key header.

Path parameters

clientIdstringYour PayKit client id.
projectIdstringThe project.

Request body

records *array1 to 500 records of { externalRef, featureCode, day, quantity }.

An empty batch, more than 500 records or a malformed body is 400 invalid_body and nothing is written.

invalid_record: the record failed validation (bad quantity, including one above 2^53 - 1, bad day, empty externalRef). duplicate_in_batch: the same externalRef, featureCode and day appeared earlier in the batch, so every occurrence after the first fails.

curl -X POST "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/projects/f6e5d4c3b2a1908172635445362718a9/usage/batch" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"records":[{"externalRef":"user_123","featureCode":"bandwidth_bytes","day":"2026-10-11","quantity":"39728447488"}]}'
200 OK
{
  "results": [
    {
      "index": 0,
      "status": "ok",
      "usageDay": {
        "customerId": "c_4d5e…",
        "externalRef": "user_123",
        "featureCode": "bandwidth_bytes",
        "day": "2026-10-11",
        "quantity": "39728447488",
        "previousQuantity": null,
        "changed": true,
        "updatedAt": "2026-10-12T01:00:03.000Z"
      }
    },
    {
      "index": 1,
      "status": "error",
      "error": {
        "code": "usage_period_closed",
        "message": "That period has already been rated",
        "details": {
          "period": {
            "usagePeriodId": "up_7c8d…",
            "status": "paid"
          }
        }
      }
    }
  ],
  "counts": {
    "ok": 1,
    "failed": 1,
    "changed": 1
  }
}
GET/v1/clients/{clientId}/projects/{projectId}/customers/{externalRef}/usage

Read usage

usage.read

The stored days for one customer and feature in a range, plus every usage period that overlaps it with its status and, once rated, its billing figures.

Path parameters

clientIdstringYour PayKit client id.
projectIdstringThe project.
externalRefstringYour id for the user.

Query parameters

featureCodestringThe metered feature. Required.
fromstringFirst day, inclusive (yyyy-MM-dd). Required.
tostringLast day, inclusive (yyyy-MM-dd). Required, at most 366 days after from.

A missing or invalid query parameter is 400 invalid_request. 404 customer_not_found and 404 feature_not_found also apply.

A day counts only when the subscription was active, past_due or grace at that day's first instant in the price's time zone. trialing, pending_action, incomplete, suspended, paused, cancelled and expired days never count. A day counts for at most one subscription of a customer; when two of them could count it, which one does is not defined.

curl -X GET "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/projects/f6e5d4c3b2a1908172635445362718a9/customers/user_123/usage" \
  -H "Authorization: Bearer YOUR_API_KEY"
200 OK
{
  "customerId": "c_4d5e…",
  "externalRef": "user_123",
  "featureCode": "bandwidth_bytes",
  "days": [
    {
      "day": "2026-09-01",
      "quantity": "1288490188",
      "updatedAt": "2026-09-02T01:00:02.000Z"
    }
  ],
  "periods": [
    {
      "usagePeriodId": "up_7c8d…",
      "subscriptionId": "0f1e2d3c4b5a6978869504132231ffee",
      "usagePeriod": "calendar_month",
      "timeZone": "Africa/Johannesburg",
      "periodStart": "2026-08-31T22:00:00.000Z",
      "periodEnd": "2026-09-30T22:00:00.000Z",
      "firstDay": "2026-09-01",
      "lastDay": "2026-09-30",
      "status": "paid",
      "quantity": "39728447488",
      "includedQuantity": "26843545600",
      "unitSize": "1073741824",
      "rounding": "up",
      "unitAmountMinor": "600",
      "maxChargeMinor": "104700",
      "billableUnits": "12",
      "amountMinor": "7200",
      "capped": false,
      "currency": "ZAR",
      "ratesAt": "2026-10-03T22:00:00.000Z",
      "ratedAt": "2026-10-03T22:07:12.000Z",
      "attempts": 1,
      "nextAttemptAt": null,
      "failureCode": null,
      "invoiceId": "99887766554433221100ffeeddccbbaa",
      "transactionId": "aa11bb22cc33dd44ee55ff6677889900"
    }
  ]
}
POST/v1/clients/{clientId}/projects/{projectId}/usage-periods/{usagePeriodId}/retry

Retry an uncollectible usage charge

billing.retry

One immediate attempt on a usage period whose charge ran out of retries, against the customer's current default card. Reinstating a subscription does not collect unpaid usage, so retry this first. Requires an Idempotency-Key header.

Path parameters

clientIdstringYour PayKit client id.
projectIdstringThe project.
usagePeriodIdstringA usage period with status uncollectible.

402 charge_failed: declined or no usable card. error.details.failureCode says which, and the period stays uncollectible.

404 usage_period_not_found. 409 usage_period_not_retryable: its status is not uncollectible.

502 gateway_error, two cases. The gateway could not be reached after the charge was sent: the period is left charging and resolved by verifying its reference, never by charging again. Or the attempt was refused on PayKit's side or failed before it was sent: it is handed back, not counted, and the period stays uncollectible, so retry later.

A paid retry recovers a subscription this debt escalated from active: once no escalated usage period is unpaid, and it is still past_due or grace and has not been active since, it returns to active with its original renewal date (subscription.recovered). A subscription already suspended stays suspended until you reinstate it.

curl -X POST "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/projects/f6e5d4c3b2a1908172635445362718a9/usage-periods/up_7c8d%E2%80%A6/retry" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)"
200 OK
{
  "usagePeriod": {
    "usagePeriodId": "up_7c8d…",
    "status": "paid",
    "amountMinor": "7200",
    "currency": "ZAR",
    "invoiceId": "99887766554433221100ffeeddccbbaa",
    "transactionId": "aa11bb22cc33dd44ee55ff6677889900"
  }
}