# Client Activity (https://dev.cosmospay.lat/docs/api/activity/)

> Report what your own client did (errors, timings, transactions) and read it back. Unlike the API logs, which only ever see requests that reached this service, these are the events that happen entirely on a device: a crash, a cancelled signature, a screen that failed before any request left it.

Base URL: `https://api.cosmospay.lat/cosmos-api`

## Recent client events for this consumer, newest first

`GET /v1/activity/events`

`level` is a floor, not an exact match: `level=warn` returns warnings and errors.

Requires the `activity:read` scope.

**Query parameters**

- `take` (number, optional): Page size: how many items to return (1 to 100).
- `skip` (number, optional): How many items to skip before this page (offset).
- `source` (string, optional): Only events reported by this client. One of: `wallet`, `dashboard`, `server`, `sdk`.
- `level` (string, optional): Minimum severity, not an exact match: `warn` returns warnings and errors. One of: `debug`, `info`, `warn`, `error`.
- `category` (string, optional): Only events of this category (exact match).
- `type` (string, optional): Prefix match on the event name.
- `network` (string, optional): Only events reported on this network (exact match).
- `since` (string, optional): Only events that occurred at or after this instant.
- `until` (string, optional): Only events that occurred at or before this instant.

**Responses**

- `200` (`ActivityEventListEntity`): OK
  - `data` (ActivityEventEntity[], required): The events of this page, newest first by `at`.
  - `total` (number, required): Total matching rows, not the page length.
  - `take` (number, required): The page size applied (the `take` query parameter).
  - `skip` (number, required): Rows skipped before this page (the `skip` query parameter).
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `500`: InternalError (see Shared responses)

## Report a batch of client events (errors, metrics, transactions)

`POST /v1/activity/events`

Accepted, not created: the batch is written under the consumer the gateway authenticated, and nothing in the body can change that attribution. Over-long messages are truncated and an over-sized `props` is replaced with a marker rather than failing the call: losing a whole batch to one malformed field is worst exactly when the client is in a state it did not anticipate.

Requires the `activity:write` scope. The shared public key may call it.

**Request body** (`application/json`, required): `IngestActivityDto`

- `events` (ActivityEventDto[], required): Up to 100 events. Clients batch: a wallet queues while offline and flushes on the next launch.

**Responses**

- `202` (`ActivityIngestResultEntity`): Accepted
  - `accepted` (number, required): Events written.
  - `duplicates` (number, required): Events dropped as duplicates of a batch already written. Never an error: a client that retries a flush it never saw acknowledged is doing the right thing.
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `429`: RateLimited (see Shared responses)
- `500`: InternalError (see Shared responses)

## Counts, top event types, top errors and a daily series

`GET /v1/activity/summary`

Rolls up the client events this consumer reported over the last `days` days (default 7, at most 90), optionally for one `source`: the total, distinct sessions and devices, counts per level, source and category, and the 10 most frequent event types and error messages. `series` has one entry per UTC day, zero on a quiet one.

Requires the `activity:read` scope.

**Query parameters**

- `days` (number, optional): Days the rollup covers, today included.
- `source` (string, optional): Only events reported by this client. One of: `wallet`, `dashboard`, `server`, `sdk`.

**Responses**

- `200` (`ActivitySummaryEntity`): OK
  - `total` (number, required): Events in the window.
  - `sessions` (number, required): Distinct `sessionId`s in the window: app runs, not accounts.
  - `devices` (number, required): Distinct `distinctId`s in the window: installs, not accounts.
  - `levels` (ActivityCountEntity[], required): Count per level.
  - `sources` (ActivityCountEntity[], required): Count per source.
  - `categories` (ActivityCountEntity[], required): Count per category.
  - `topTypes` (ActivityCountEntity[], required): Most frequent event types.
  - `topErrors` (ActivityCountEntity[], required): Most frequent error messages: what to fix first.
  - `series` (ActivitySeriesPointEntity[], required): Daily buckets, pre-seeded so a quiet day is a zero.
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `500`: InternalError (see Shared responses)

## Shared responses

- **ValidationFailed** (`ApiErrorBodyEntity`): The body, query or path failed validation. `message` is an array of the individual failures. Routes that reject a *valid* request for a reason of their own (an amount, a memo, a state) document the code for it on their own 400.
  - `statusCode` (number, required): Always the HTTP status of the response itself.
  - `code` (string, required): Stable machine-readable code. Branch on this, never on the message: messages are human-facing and may be reworded; codes are never renamed once published. Each response documents the subset it can return. One of: `insufficient_scope`, `no_authenticated_consumer`, `gateway_required`, `admin_console_only`, `not_found`, `validation_failed`, `payload_too_large`, `idempotency_conflict`, `operation_in_flight`, `invalid_state_transition`, `slippage_exceeded`, `insufficient_balance`, `no_path_found`, `trustline_missing`, `invalid_amount`, `invalid_memo`, `transaction_rejected`, `transaction_already_settled`, `recipient_not_registered`, `payment_transfer_mismatch`, `fee_transfer_mismatch`, `merchant_fee_transfer_mismatch`, `payer_mismatch`, `transaction_not_found`, `expired`, `network_unsupported`, `asset_unsupported`, `merchant_fee_unsupported`, `provider_error`, `provider_unavailable`, `quote_not_found`, `quote_expired`, `kyc_state_invalid`, `kyc_review_required`, `account_disabled`, `payload_expired`, `rate_limited`, `customer_wallet_taken`, `alias_taken`, `alias_name_invalid`, `alias_challenge_invalid`, `alias_signature_invalid`, `alias_recovery_invalid`, `alias_address_conflict`, `wallet_handshake_invalid`, `wallet_verifier_invalid`, `wallet_login_code_invalid`, `wallet_login_code_cooldown`, `wallet_session_invalid`, `wallet_signature_invalid`, `wallet_backup_invalid`, `wallet_backup_limit`, `wallet_account_mismatch`, `wallet_provider_unavailable`, `wallet_return_url_not_allowed`, `wallet_recovery_setup_refused`, `easy_recovery_not_found`, `easy_recovery_already_offered`, `easy_recovery_not_offered`, `easy_recovery_code_invalid`, `easy_recovery_burned`, `plugin_not_installed`, `plugin_consent_mismatch`, `plugin_rejected`, `plugin_quota_exceeded`, `plugin_failed`, `protected_payment_buyer_mismatch`, `protected_payment_signature_invalid`, `misconfigured`, `internal_error`.
  - `error` (string, required): The HTTP reason phrase for `statusCode`.
  - `message` (string | string[], required): Human-readable detail. An array when class-validator rejected the body.
  - `details` (object, optional): Machine-readable facts about this failure, present only on the codes whose example shows it: `easy_recovery_code_invalid` carries `{ "attemptsLeft": 4 }`. Absent everywhere else.
  - `path` (string, required): The path of the request that failed.
  - `timestamp` (string, required): When the envelope was produced, ISO 8601.
- **Unauthorized** (`ApiErrorBodyEntity`): `no_authenticated_consumer`: The gateway forwarded no API key identity
- **Forbidden** (`ApiErrorBodyEntity`): `insufficient_scope`: the API key does not hold the scope this route requires. The shared public API key also gets this on every route that is not marked `x-cosmos-public-key`, with a message saying so; more scopes will not help, the key is the wrong one. `gateway_required`: the request did not arrive through APISIX.
- **InternalError** (`ApiErrorBodyEntity`): Unexpected server error. The detail is logged server-side and never returned: there is nothing in the body to branch on beyond the code.
- **RateLimited** (`ApiErrorBodyEntity`): The budget in `x-cosmos-rate-limit` is spent for this caller. Honour `Retry-After`. The `ratelimit-*` triple rides along on successful responses too, so a client can pace itself instead of being refused. If the counter store itself is unreachable the guard fails closed and answers `503 provider_unavailable`.
