# Challenge Log

Challenge Logs are the **activity events** that drive challenge progress. Every time an end user performs a qualifying action — a purchase, a visit, a workout — your backend sends a Challenge Log entry. The platform aggregates these entries according to the challenge's `aggregationMethod` and `aggregationPeriod`, updating the user's `Challenge Progress` automatically.

:::info
Challenge Logs can only be **created** via the API (`CREATE`, `GET`, `LIST`). They cannot be updated or deleted. Once submitted, a log entry is permanent.
:::

---

## How it works

1. **User performs an action** — a customer places an order, visits a store, or completes any activity your challenge tracks.
2. **Your backend sends a log** — call `POST /schema/challenge_log/record` with the `challengeId`, `ownedById`, `activityAt`, `uniqueIdentifier` and `activityValue` (Required when `aggregationMethod` of the `Challenge` schema is `sum`).
3. **Progress updated** — This aggregates the new entry into the user's `Challenge Progress`, updating `currentProgressValue` and checking milestone thresholds.
4. **Milestone triggered** — if `currentProgressValue` crosses a milestone's `milestoneTargetValue`, a `Reward Log` is created and `achievedMilestone` + `achievedMilestoneRewardLog` are set on the response.
5. **Challenge completed** — if `currentProgressValue` reaches `completionTargetValue`, `challengeProgressStatus` transitions to `completed`.

---

## Fields

### Required on create

| Field | Type | Required | Description |
|---|---|---|---|
| `challengeId` | UUID | ✅ | ID of the challenge this log contributes to |
| `ownedById` | UUID | ✅ *(or use `ownedBy`)* | Gamopanda member ID of the user performing the activity. Use this if the member already exists. |
| `activityAt` | datetime | ✅ | When the activity occurred. Used to determine which aggregation period this log falls into. |
| `activityId` | string | ✅ | Activity Id for this event — e.g. made_a_purchase, visited_website, etc |
| `uniqueIdentifier` | string | ✅ | Unique Identifier for this event — e.g. order_id, transaction_id, review_id, etc |

### Optional on create

| Field | Type | Description |
|---|---|---|
| `activityValue` | number | The numeric value of the activity. Required when the challenge uses `aggregationMethod: sum` (e.g. spend amount, steps, calories). Ignored for `count` challenges. |
| `ownedBy` | object | Inline member upsert payload. Use instead of `ownedById` to sync the member in the same request. See [syncing a member inline](#syncing-a-member-inline) below. |

### Status

| Field | Type | Default | Description |
|---|---|---|---|
| `status` | enum | `live` | `draft` · `live` · `paused`. Only `live` logs are included in aggregation. |

---

## Extra fields returned in the response

After a log is created, the platform enriches the response with several computed fields:

| Field | Description |
|---|---|
| `challenge` | The full joined `Challenge` object this log belongs to |
| `challengeProgress` | The user's updated `Challenge Progress` record after this log was processed |
| `achievedMilestone` | The `Milestone` record crossed by this log entry, if any. `null` if no milestone was reached. |
| `nextMilestone` | The next `Milestone` the user is working toward, if any. `null` if no further milestones exist. |
| `achievedMilestoneRewardLog` | The `Reward Log` issued when a milestone was crossed by this entry. `null` if no milestone was reached. |

These fields make the create response self-contained — your backend can read the updated progress and any earned reward in a single API call without making additional requests.

---

## Handling `achievedMilestoneRewardLog`

When a milestone threshold is crossed, the platform creates a `Reward Log` record and returns it as `achievedMilestoneRewardLog` in the response. At this point the reward log exists but has **no coupon code or timestamps** yet — your backend is responsible for fulfilling the reward and updating the log.

### Reward fulfilment workflow

1. **Check the response** — after `POST /schema/challenge_log/record`, inspect `achievedMilestoneRewardLog`. If it is not `null`, a milestone was just crossed.
2. **Generate a coupon** — call your coupon or voucher system to issue a reward code for this user.
3. **Update the reward log** — `PATCH` the reward log with the coupon code and timestamps:

```http
PATCH /api/v1.0/schema/reward_log/record/{achievedMilestoneRewardLog.id}
x-api-key: <your-api-key>
x-api-secret: <your-api-secret>

{
  "couponCode": "COFFEE4FREE",
  "rewardIssuedAt": "2026-04-11T09:05:00Z",
  "rewardExpiryAt": "2026-04-30T23:59:59Z"
}
```

### Reward log lifecycle

| Step | Fields set | Who sets it |
|---|---|---|
| Milestone crossed | `milestoneId`, `linkedSchemaRecordId`, `ownedById` | Platform (automatic) |
| Reward issued | `couponCode`, `rewardIssuedAt`, `rewardExpiryAt` | Your backend |

---

## Syncing a member inline

Instead of managing a separate upsert call to [Member](/member) before logging activity, you can pass member details directly in the `ownedBy` field. The platform will **create or update** the member record automatically as part of processing the log`.

Use `ownedBy` (with `externalId`) **instead of** `ownedById` when you want to sync the user and log activity in a single API call:

```json
{
  "challengeId": "a1b2c3d4-0000-0000-0000-000000000000",
  "ownedBy": {
    "externalId": "cust_8472910",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "phoneNumber": "+14155552671"
  },
  "activityId": "made_a_purchase",
  "uniqueIdentifier": "order_9823741",
  "activityAt": "2026-04-11T14:23:00Z",
  "activityValue": 87.50,
  "status": "live"
}
```

The platform resolves the member by `externalId`, upserts their profile with any supplied fields, and uses the resulting member ID as `ownedById` for the log.

:::tip
This is the recommended pattern when logging activity from an order or checkout webhook — you get the member sync and the activity log in one request, reducing latency and the risk of a log being submitted before the member record exists.
:::

| Field | Type | Required | Description |
|---|---|---|---|
| `externalId` | string | ✅ | Your system's unique identifier for this user. Used to match or create the member. |
| `firstName` | string | ❌ | Member's first name |
| `lastName` | string | ❌ | Member's last name |
| `email` | email | ❌ | Member's email address |
| `phoneNumber` | phone | ❌ | Member's phone number in E.164 format — e.g. `+14155552671` |

See [Members](/member) for full details on member fields and the E.164 phone number format.

---

## The `uniqueIdentifier` — preventing duplicates

`uniqueIdentifier` is your system's unique identifier for the triggering event. The platform uses it to **idempotently reject duplicate submissions** — if you send the same `activityId` for the same `challengeId` and `ownedById` twice, the second request is rejected.

Use whatever identifier uniquely describes the event in your system:

```json
// E-commerce: use the order ID
{ "uniqueIdentifier": "order_9823741" }

// Food & Beverage: use the transaction ID
{ "uniqueIdentifier": "txn_cafe_20260411_003" }

// Fitness: use the workout session ID
{ "uniqueIdentifier": "workout_session_88421" }
```

:::warning
Always pass a stable, unique `uniqueIdentifier`. If you omit it or pass a generic value, duplicate logs may inflate a user's progress.
:::

---

## Real-world examples

**🛍️ E-commerce — sum challenge (monthly spend)**

A user spends $87.50 on an order. The challenge aggregates spend with `aggregationMethod: sum`:

```http
POST /api/v1.0/schema/challenge_log/record
x-api-key: <your-api-key>
x-api-secret: <your-api-secret>

{
  "challengeId": "a1b2c3d4-0000-0000-0000-000000000000",
  "ownedById": "user-uuid-here",
  "activityId": "made_a_purchase",
  "uniqueIdentifier": "order_9823741",
  "activityAt": "2026-04-11T14:23:00Z",
  "activityValue": 87.50,
  "status": "live"
}
```

**Response:**

```json
{
  "id": "log-uuid-here",
  "challengeId": "a1b2c3d4-0000-0000-0000-000000000000",
  "uniqueIdentifier": "order_9823741",
  "activityId": "made_a_purchase",
  "activityAt": "2026-04-11T14:23:00Z",
  "activityValue": 87.50,
  "ownedById": "user-uuid-here",
  "challengeProgress": {
    "currentProgressValue": 275.00,
    "challengeProgressStatus": "in_progress",
    "challengeStartedAt": "2026-04-01T09:12:00Z",
    "challengeCompletedAt": null,
    "challengeFailedAt": null
  },
  "achievedMilestone": null,
  "nextMilestone": {
    "id": "milestone-uuid-here",
    "milestoneTargetValue": 300,
    "name": "$300 Milestone"
  },
  "achievedMilestoneRewardLog": null
}
```

---

**☕ Food & Beverage — count challenge (monthly visits)**

A user's 4th café visit this month. The challenge aggregates visits with `aggregationMethod: count`:

```http
POST /api/v1.0/schema/challenge_log/record
x-api-key: <your-api-key>
x-api-secret: <your-api-secret>

{
  "challengeId": "b2c3d4e5-0000-0000-0000-000000000000",
  "ownedById": "user-uuid-here",
  "activityId: "visited_cafe",
  "uniqueIdentifier": "txn_cafe_20260411_003",
  "activityAt": "2026-04-11T09:05:00Z",
  "status": "live"
}
```

**Response — milestone crossed:**

```json
{
  "id": "log-uuid-here",
  "challengeId": "b2c3d4e5-0000-0000-0000-000000000000",
  "activityId": "visited_cafe",
  "uniqueIdentifier": "txn_cafe_20260411_003",
  "activityAt": "2026-04-11T09:05:00Z",
  "activityValue": null,
  "ownedById": "user-uuid-here",
  "challengeProgress": {
    "currentProgressValue": 4,
    "challengeProgressStatus": "in_progress",
    "challengeStartedAt": "2026-04-01T10:00:00Z",
    "challengeCompletedAt": null,
    "challengeFailedAt": null
  },
  "achievedMilestone": {
    "id": "milestone-uuid-here",
    "milestoneTargetValue": 4,
    "name": "Halfway There"
  },
  "nextMilestone": {
    "id": "milestone-uuid-2",
    "milestoneTargetValue": 8,
    "name": "Challenge Complete"
  },
  "achievedMilestoneRewardLog": {
    "id": "reward-log-uuid-here",
    "couponCode": null,
    "rewardIssuedAt": null,
    "rewardExpiryAt": null,
  }
}
```

---

**🏋️ Fitness — sum challenge (weekly steps) — challenge completed**

A user's final step log pushes them over 100,000 steps, completing the weekly challenge:

```http
POST /api/v1.0/schema/challenge_log/record
x-api-key: <your-api-key>
x-api-secret: <your-api-secret>

{
  "challengeId": "c3d4e5f6-0000-0000-0000-000000000000",
  "ownedById": "user-uuid-here",
  "activityId": "step",
  "uniqueIdentifier": "workout_session_88421",
  "activityAt": "2026-04-11T18:30:00Z",
  "activityValue": 12500,
  "status": "live"
}
```

**Response — challenge completed:**

```json
{
  "id": "log-uuid-here",
  "challengeId": "c3d4e5f6-0000-0000-0000-000000000000",
  "uniqueIdentifier": "workout_session_88421",
  "activityId": "step",
  "activityAt": "2026-04-11T18:30:00Z",
  "activityValue": 12500,
  "ownedById": "user-uuid-here",
  "challengeProgress": {
    "currentProgressValue": 100420,
    "challengeProgressStatus": "completed",
    "challengeStartedAt": "2026-04-07T08:00:00Z",
    "challengeCompletedAt": "2026-04-11T18:30:00Z",
    "challengeFailedAt": null
  },
  "achievedMilestone": {
    "id": "milestone-uuid-here",
    "milestoneTargetValue": 100000,
    "name": "100K Steps Champion"
  },
  "nextMilestone": null,
  "achievedMilestoneRewardLog": {
    "id": "reward-log-uuid-here",
    "couponCode": null,
    "rewardIssuedAt": null,
    "rewardExpiryAt": null,
  }
}
```

---

## Who can create challenge logs

Challenge Logs are created by your **backend server**. End users have no write access to this resource.

```http
POST /api/v1.0/schema/challenge_log/record
x-api-key: <your-api-key>
x-api-secret: <your-api-secret>
```

---

## Access & permissions

| Caller | Allowed operations | Notes |
|---|---|---|
| Admin | CREATE · GET · LIST | Non-editable, Cannot be deleted |
| End user | *(none)* | No direct access |
| Guest user | *(none)* | Not accessible |

:::warning
Challenge Logs **cannot be updated or deleted**. Once submitted, a log entry is permanent. To correct a mistake, contact support.
:::

---

## Related resources

| Resource | Description |
|---|---|
| [Challenge](/challenge) | The parent challenge that defines the rules for aggregation |
| [Challenge Progress](/challenge-progress) | The running summary of a user's progress, updated after each log |
| [Milestone](/milestone) | Reward checkpoints that fire when `currentProgressValue` crosses a target |
| [Reward Log](/reward_log) | Reward entries automatically created when a milestone is crossed |
| [Member](/member) | The end user (`ownedById`) whose progress is updated by this log |

---

## API reference

See the [API Reference](/api/challenge-log) for full request/response schemas and interactive examples for:

- `POST /schema/challenge_log/record` — create a challenge log
- `GET /schema/challenge_log/record` — list challenge logs
- `GET /schema/challenge_log/record/{id}` — get a challenge log by ID
