Skip to content
Hardcount
Run it locally
Pre-1.0 · one maintainer · Stripe and cards only · no SDKs (client libraries) yet · no production users yet. Docs describe v0.9.1 (29 Oct 2026) · What’s new →

Get subscription activity timeline

GET
/v1/subscriptions/{id}/timeline
curl --request GET \
--url http://localhost:8080/v1/subscriptions/example/timeline \
--header 'Authorization: Bearer <token>'

Returns the subscription’s history from the audit log as display-ready rows, in the order the actions took effect: by sim_effective_at for actions on a test-clock subscription, otherwise by timestamp. On a test-clock subscription timestamp (wall-clock) may therefore not be in ascending order. It holds at most the 100 most recent entries. truncated is true when older entries were left out.

id
required
string

Timeline events.

Media typeapplication/json
object
events
required
Array<object>
object
timestamp
required

When the action was recorded (wall-clock time).

string format: date-time
source
required
string
Allowed values: audit
event_type
required

The audit action, e.g. create, cancel, subscription.item_updated.

string
status
required

The line’s tone in older words, read by the dashboard’s dot.

string
Allowed values: neutral succeeded warning failed
description
required

The line’s title, for example “Subscription canceled”.

string
detail

The line’s meta line as text, without the actor (see actor_name) and without a date at its end, which is in detail_timestamp. For example “Starter → Pro”.

string
detail_timestamp

The date the end of detail refers to, for the client to format, as in “Trial now ends” followed by this date.

string format: date-time
error

The line’s problem.

string
actor_type
string
actor_name
string
actor_id
string
is_simulated

True when the action affected a subscription on a test clock.

boolean
sim_effective_at

The test clock’s simulated time when the action took effect.

string format: date-time
test_clock_id
string
line
required

One activity row in the words of the activity line standard (ADR-128): a title with no values, the amount when the event moves money, asks for it or cancels money owed, a problem line on failure and warning rows, the meta line’s parts and the actor, a tone, and the event’s time on the entity’s calendar. recorded_at is the real-world time, set only for an entity on a test clock.

object
id
required

The row’s stable key: the event’s id on an invoice timeline row, the audit entry’s id on a subscription timeline row, dunning: and the event’s id on a dunning run event (the same key the invoice timeline gives that event), and email: and the email’s id on a sent email (likewise).

string
kind
required

Machine name of the event.

string
title
required
string
amount
object
cents
required
integer format: int64
currency
required
string
problem
string
detail
required

The meta line before the actor. Each item is one segment, shown joined by “ · “; a segment’s parts read as one phrase, for example a money part followed by the text “ refunded to card”.

Array<Array<object>>
actor

Who did it. person and api_key carry the name (a person’s email when they have no name), customer says where through via, and automatic is anything Hardcount did on its own. Absent when no fact written with the row names one.

object
kind
required
string
Allowed values: person api_key customer automatic
name
string
via
string
Allowed values: payment_page portal
tone
required

Neutral: something happened. success: money arrived, or was returned as intended. warning: needs a look, or money will not be collected. failure: an attempt failed.

string
Allowed values: neutral success warning failure
occurred_at
required
string format: date-time
recorded_at
string format: date-time
truncated
required

True when the subscription has more than 100 audit entries, so the earliest events are missing.

boolean

Example

{
"events": [
{
"source": "audit",
"status": "neutral",
"line": {
"kind": "invoice.voided",
"title": "Payment failed",
"amount": {
"currency": "USD"
},
"problem": "Card declined: insufficient funds",
"detail": [
[
{
"money": {
"currency": "USD"
},
"link": {
"kind": "invoice"
}
}
]
],
"actor": {
"kind": "person",
"via": "payment_page"
},
"tone": "neutral"
}
}
]
}

Subscription not found.

Media typeapplication/json
object
error
object
type

One of invalid_request_error, authentication_error, rate_limit_error or api_error. Every error the API answers has this shape, including an unknown path (404, code route_not_found) and a method the path does not take (405, code method_not_allowed, with an Allow header). The few exceptions are listed in docs/api-surface.md.

string
code
string
message
string
request_id
string
param

The request field the error is about. Sent only when the error names one. A request body may hold only the fields its operation lists, spelled and cased as listed: any other field, or one the server sets (such as id), is refused with 422 and param naming it. An operation with no request body lists no fields: send no body, {} or null. The Stripe and Postmark webhooks and the LiteLLM callback take their sender’s body as it comes.

string

Example

{
"error": {
"type": "invalid_request_error"
}
}