Reporting usage (activity, screentime, transactions)
This guide covers how your product sends usage data to k-ID, so that a trusted adult can see it in Family Connect. There are three entry points into the same ingestion model:
/activity/pushtakes records against the activity types your product declares./screentime/pushtakes completed screentime usage segments./transaction/pushtakes completed purchases.
All three are report-only and need no parent setup: your product reports what happened, k-ID aggregates it, and the trusted-adult surfaces read the aggregate. None of them drives any real-time enforcement. For the full screentime feature, the parent-configured limits, quiet hours, break reminders, the session state machine, and the events it fires, see the Screentime guide. For transaction visibility, purchase approval, and payment-as-verification, see the Transactions guide.
For what these are and where their boundaries sit, see Activity and Transactions.
Before you start
All three endpoints attribute what you send to a k-ID session, so your product needs the sessionId it received from the age gate. See Sessions. Calls are server-to-server and authenticated with an API key, as described in Authentication.
Beyond the session, each path has one prerequisite:
- Activity needs a declared activity type. Every record names a type your product has declared in Compliance Studio, and a type is local until you push it to the environment your API key serves. See Activity types and Testing and publishing.
- Screentime needs the Screentime feature enabled for the product. Without it,
/screentime/pushreturnsFEATURE_DISABLED. - Transactions needs the Transactions feature enabled for the product. Without it,
/transaction/pushreturnsFEATURE_DISABLED.
Three ingestion endpoints
The three endpoints share one model and differ only in what a single item carries.
/activity/push | /screentime/push | /transaction/push | |
|---|---|---|---|
| Accepts | records against declared activity types | completed screentime usage segments | completed purchases |
| Batch field, max | records, up to 1000 | events, up to 100 | events, up to 100 |
| Each item carries | id, type, value, timestamp, optional attributes | id, durationSeconds, timestamp | id, title, amount, currency, timestamp, status, optional description and url |
| Prerequisite | a declared, pushed activity type | the Screentime feature enabled | the Transactions feature enabled |
| Real-time events | none | none (the live path is in the Screentime guide) | none (the live path is in the Transactions guide) |
What they have in common is the whole ingestion model: each item carries a client-generated id for idempotency and a timestamp within the last seven days, a batch covers exactly one session, items are validated one at a time with a per-item accept or reject, and what lands feeds the aggregate a trusted adult sees rather than triggering anything.
Pushing activity records
Send a batch of records for one session to /activity/push.
type is the activity type's key: the identifier k-ID generates when you create the type, a UUID. Copy it from the activity type's configuration in Compliance Studio rather than constructing it.
Replace timestamp with the moment the activity happened. The example value is illustrative: a timestamp more than seven days old is rejected, so a copied example goes stale.
Example request
POST /api/v1/activity/push
Content-Type: application/json
Authorization: Bearer your-api-key
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"records": [
{
"id": "7c3e0c6e-9c2a-4f1e-bd2b-1f9a3c6d8e10",
"type": "2f9a1c84-6b3e-4d52-9f08-1a7c3e5d9b20",
"value": { "seconds": 1800 },
"timestamp": "2026-08-19T14:32:05Z",
"attributes": { "mode": "co_op" }
}
]
}
Example response
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"accepted": 1,
"rejected": 1,
"records": [
{ "id": "7c3e0c6e-9c2a-4f1e-bd2b-1f9a3c6d8e10", "status": "accepted" },
{ "id": "a4f2b8d1-3e7c-4a90-8b6f-2c5d9e0a1b34", "status": "rejected", "reason": "timestamp_too_old" }
]
}
A 200 response doesn't mean every record landed: check rejected and the per-record status rather than the HTTP status alone.
Value shapes
Each activity type declares a metric type, and a record's value object must carry that metric type's fields and nothing else. A record that mixes fields, such as seconds alongside count, is rejected even when both are valid on their own.
| Metric type | value | Notes |
|---|---|---|
duration | { "seconds": 1800 } | Whole seconds, zero or greater |
currency | { "amount": 999, "currency": "USD" } | amount in minor units, zero or greater. currency is an ISO 4217 code |
count | { "count": 7 } | Zero or greater |
gauge | { "number": 0.85 } | Any double |
boolean | { "bool": true } |
Currency codes are matched against ISO 4217, so an in-game currency name such as GEMS is rejected. Report virtual-currency purchases as a count, or as a real-money amount if that's what you want a parent to see.
Attributes
attributes is optional key/value metadata for dimensions you want broken out, such as which mode of your product the time was spent in. Each type declares the attribute keys it accepts, and the rules are strict:
- A record can carry at most 20 attributes.
- Keys are at most 64 characters, values at most 256.
- A key that the type doesn't declare is rejected. A type that declares no attribute keys accepts no attributes.
- Values that look like an email address, a phone number, or a payment card number are rejected.
Send an opaque identifier your own systems can resolve rather than personal data.
Activity rejection reasons
| Reason | What happened |
|---|---|
invalid_id | id isn't a UUID |
unknown_type | type isn't declared for this product in the configuration the API key's mode serves |
invalid_value | value doesn't match the type's metric type, is negative, or carries fields from more than one metric type |
timestamp_in_future | timestamp is later than now |
timestamp_too_old | timestamp is more than seven days old |
attributes_too_many | More than 20 attributes |
attribute_key_too_long | An attribute key is longer than 64 characters |
attribute_value_too_long | An attribute value is longer than 256 characters |
attribute_key_not_allowed | An attribute key isn't declared for this type |
attribute_value_forbidden_content | An attribute value looks like an email address, a phone number, or a payment card number |
type_metric_type_unsupported | The type's metric type isn't one this API version handles |
transient_error | The record was valid but couldn't be stored. Retry it |
Every reason except transient_error describes a record your product has to change. Retrying an unchanged record produces the same rejection, so route these to your logs rather than to a retry queue.
Pushing screentime usage
/screentime/push reports completed segments of usage after the fact, for a server that buffers segments or a game engine that reports totals on shutdown. Each accepted segment adds its durationSeconds to the child's daily screentime total and is displayed in the parent's Family Connect charts, the same shape of ingestion as activity records.
It's report-only. It fires no break reminder, no limit warning, and no limit-reached event, because those are scheduled at the moment a session starts, not reconstructed after the fact. If your product needs those real-time signals, or the parent-configured limits, quiet hours, and overrides behind them, that's the live path in the Screentime guide, not this endpoint.
Example request
POST /api/v1/screentime/push
Content-Type: application/json
Authorization: Bearer your-api-key
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"events": [
{
"id": "9d4e2f81-7a3b-4c56-8e90-1f2a3b4c5d6e",
"durationSeconds": 1800,
"timestamp": "2026-06-24T09:00:00Z"
}
]
}
Example response
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"accepted": 1,
"rejected": 1,
"events": [
{ "id": "9d4e2f81-7a3b-4c56-8e90-1f2a3b4c5d6e", "status": "accepted" },
{ "id": "c1b2a3d4-5e6f-4708-9a1b-2c3d4e5f6071", "status": "rejected", "reason": "invalid_input" }
]
}
As with activity, check rejected and the per-event status rather than the HTTP status alone.
Segment rules
Each event is one completed segment, carrying its own id for idempotency, the timestamp (UTC) when the span of usage began, and its durationSeconds.
durationSecondsis between 1 second and 24 hours. Split a longer span into several segments.- The segment must already have completed:
timestamp + durationSecondscan't be in the future. timestampmust be within the last seven days.- A segment that crosses midnight in the parent's configured time zone is split across both dates, so each day's total reflects the usage that happened on that date.
- Up to 100 segments per call.
Screentime rejection reasons
| Reason | What happened |
|---|---|
invalid_input | The segment broke a validation rule: a bad id, a durationSeconds outside 1 second to 24 hours, or a timestamp out of range. Retrying it unchanged fails again |
internal_error | A transient storage failure. Safe to retry |
Pushing transactions
Send a batch of completed purchases for one session to /transaction/push. It's report-only, the same as the other two pushes: each purchase you report is recorded and surfaced to the linked parent in Family Connect, and reporting one never blocks a charge.
The Transactions feature must be enabled for the product; without it, /transaction/push returns FEATURE_DISABLED. Each accepted purchase also feeds activity as a currency-metric event, so reported purchases flow through the same aggregate a trusted adult reads.
Each item carries the purchase: a title, an amount in the currency's minor units, a currency, the timestamp it completed, and a status of successful or failed. For the full field set, the request and response shapes, and the per-event statuses, see Reporting purchases in the transactions guide.
Batch-level errors
Some conditions fail the whole call, and the response carries an error code instead of per-item results. See Error handling.
| Error | Cause |
|---|---|
NOT_FOUND | No session with that sessionId for this product |
INVALID_INPUT | The session is revoked, the batch is empty or over its size limit, or the body doesn't parse |
FEATURE_DISABLED | /screentime/push was called without the Screentime feature enabled, or /transaction/push without the Transactions feature |
Retries and idempotency
All three endpoints take a client-generated id on every item, a record's id on /activity/push, a segment's id on /screentime/push, and a purchase's id on /transaction/push, and that id is what makes a retry safe. Pushing an item whose id was already accepted counts as accepted again and stores nothing new, so a batch that timed out mid-flight can be sent again as-is.
That only holds if the id is stable. Generate it once, when the usage happens, and store it with the item you're about to send. An id generated at send time turns every retry into a duplicate.
Choosing a batching strategy
The seven-day timestamp window is the outer bound, not a target. Two things push toward smaller, more frequent batches:
- A trusted adult sees usage only after it arrives, so a product that pushes daily shows a parent yesterday's picture.
- Items older than seven days are rejected outright. For activity, backfills beyond that window must use the async file-upload path; screentime segments have no such path, so report them within the window.
Batch per session for a few minutes of play, within each endpoint's size limit (1000 records, 100 segments, or 100 purchases), and both problems stay away.
Verifying the integration
/activity/send-test-digest renders the digest email a linked adult receives and sends it, using the session's real data from the trailing seven days. The digest reflects the screentime, activity, and purchases a session accrued, so it's the fastest way to confirm that what you're pushing turns into something a parent can read.
Example request
POST /api/v1/activity/send-test-digest
Content-Type: application/json
Authorization: Bearer your-api-key
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672"
}
Example response
{
"sent": true
}
Four things to know about it:
- Test mode only. A live API key gets a
NOT_FOUNDerror. The endpoint can never email a real linked adult. - The session needs a linked adult. A session whose
hasApproverEmailis false returnsINVALID_INPUT. sent: falseis a success. It means the trailing week had nothing to summarize, so no email went out.- It covers the trailing seven days. The window ends today, so data you pushed for this session is included.
Pass an optional locale, such as ja, to preview a translation of the email.
Integration checklist
- Activity types declared in Compliance Studio and pushed to the environment your API key serves, the Screentime feature enabled if you push screentime, and the Transactions feature enabled if you push transactions.
- Item ids generated where the usage happens, and stored alongside the item so retries reuse them.
- Response handling that reads
rejectedand the per-itemreason, alerts on a rejection-rate change, and retries only the transient reason (transient_errorfor activity,internal_errorfor screentime and transactions). - Timestamps in UTC, and a push cadence well inside the seven-day window.
- Attributes limited to declared keys, carrying no personal data.
- A test digest sent and read end to end.