Skip to main content
A usage module joins two objects: the one holding what you sell and the one holding who consumes it. Your application records consumption against it; Custral stores the events on the product and customer records.

Attributes

string
Unique identifier, prefixed umod_.
string | null
What this metered product is called.
string | null
The object holding the things being sold.
string | null
The object holding the people who consume them.
string | null
Optional property an external system identifies products by: a SKU, say.
string | null
Optional property an external system identifies customers by.
string | null
Optional object holding the people inside a customer account (Contacts, say). An event’s user is matched to a record of this object.
string | null
Optional property on the actor object that an event’s user is matched against: an email, say.
string | null
ISO 8601 timestamp.

Two keys, on purpose

Recording usage and reading it back need different keys, and the split is deliberate: track takes a publishable key so it can be called straight from a browser at the moment of consumption. Reading events back is not publishable. A client-side key must never be able to enumerate a workspace’s usage.

Recording usage

Use @custral/js from your app:
Every event needs a customer, and either a product or an event name. user is optional. customerRecordId, productRecordId and user each accept a Custral record id, or a value of the module’s identifier property on that side (your own account id, a SKU, an email).

Identifiers that match no record

If an identifier matches no record, the event is still stored, but it counts toward no record’s usage. track tells you which side missed:
The event keeps the value you sent as customerKey / productKey. It is never stored as a record id. To see what is waiting and under which keys:
GET /v1/usage-modules/{id}/unmatched needs usage:read. Later events with that key start matching once a record carries it in the module’s identifier property.

Reading it back

With the server SDK, holding usage:read:
Each event carries event, actorRecordId / actorKey (who did it), and customerKey / productKey when that side matched no record. Filter by event to read one kind of event. Events come back newest first, 50 to a call. That is the one list in the API whose length is set by your own application rather than by anything a person does in the workspace, so it always pages. A call without limit is a page, not the history.
integer
Page size, 1–200. Defaults to 50.
integer
Rows to skip, for the next page.
To total a period, filter it rather than paging to the end:

Usage per record

Two reads answer the questions a usage table is usually built for, already totalled. They are the same figures the Usage by customer and Usage by product blocks show. Both need a secret key with usage:read and records:read. The response names records, and usage:read on its own never reveals a record’s name. You don’t pass a module. It is found from the record’s object: the module whose product object (for by-customer) or customer object (for by-product) that record belongs to. When several modules meter the object, the oldest is read, every one is listed in modules, and moduleId picks another. breakdown is null when no module meters the object on that side, for example a customer record passed to by-customer.
string
Query parameter. The usage module (umod_…) to read, when several meter the record’s object.

Usage by customer

Usage by product

products lists products in use first, most used first, and never cuts them. Products this customer did not use fill the rest, up to 25 rows, most recently used first; moreNotUsed counts the ones left out. Each row’s weeks lines up with weekStarts, and slippingPercent uses the same 14-day rule as slipping above, for this customer on this product.

Names and what you can read

Counts cover every customer. The named lists (topCustomers, slipping, neverTried.examples and products) only include records the key can read, so a private account is counted but not named. A record with no usable name comes back with name: "", never its id. Show your own placeholder. A record that doesn’t exist, or that the key can’t read, is not_found, and so is a record that isn’t in {object}.