User & Loyalty API
User-focused endpoints let you manage loyalty points, reward history, offers, coupons, streaks, challenges, surveys, and notifications for individual users.
- Authentication: All endpoints in this section require a valid JWT Bearer token.
See JWT Authentication for token structure and security best practices.
Endpoints Overview
POST /user– Create userDELETE /user/{userId}– Delete userGET /user/{userId}/coupon– Get user couponsPUT /user/{userId}/coupon/{couponId}– Redeem couponGET /user/{userId}/points– Get user pointsGET /user/{userId}/history– Get reward historyPUT /user/{userId}/notifications/overview– Mark overview as viewedGET /user/{userId}/notifications/overview– Get notifications overviewPUT /user/{userId}/notifications/change– Reset notification countersGET /user/{userId}/offer/feed– Get offer feedPOST /user/{userId}/offer/{offerId}/redeem– Redeem offerGET /user/{userId}/offer/{offerId}/redeemable– Check if offer is redeemableGET /user/{userId}/survey– Get available surveysPUT /user/{userId}/survey/{surveyId}– Submit survey responseGET /user/{userId}/challenge– Get user challengesGET /user/{userId}/challenge/{challengeId}– Get specific challengeGET /user/{userId}/streak– Get user streaksGET /user/{userId}/streak/{streakId}– Get specific streak
POST /user – Create User
Create a new user in the project and initialize their loyalty profile.
- Method:
POST - Path:
/v1/user - Auth:
Authorization: Bearer <JWT_TOKEN>
Request Body
{
"userId": "user_123"
}- userId
string(required): Unique user identifier in your system.
Responses
-
201 Created
{ "message": "user created successfully", "userId": "user_123", "createdAt": 1730818799123 } -
400 Bad Request
- User with this
userIdalready exists.
- User with this
-
500 Internal Server Error
- Error while writing to the database.
DELETE /user/{userId} – Delete User
Delete a user and all associated loyalty data (events, coupons, etc.).
- Method:
DELETE - Path:
/v1/user/{userId} - Auth:
Authorization: Bearer <JWT_TOKEN>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| userId | string | yes | Identifier of the user. |
Responses
-
200 OK
{ "message": "User data deletion process initiated successfully. Check logs for details.", "userId": "user_123" }The deletion is processed in batches in the background; see logs for any partial failures.
-
400 Bad Request
userIdmissing.
-
500 Internal Server Error
- Error while deleting user data.
GET /user/{userId}/coupon – Get User Coupons
Return all active, unredeemed coupons for a user.
- Method:
GET - Path:
/v1/user/{userId}/coupon - Auth:
Authorization: Bearer <JWT_TOKEN>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| userId | string | yes | Identifier of the user. |
Response
-
200 OK
[ { "PK": "USER#user_123#COUPON", "SK": "COUPON#abc123", "title": "10% Discount", "description": "Get 10% off your next order", "validUntil": 1762354799000, "redeemed": false, "metadata": { "shopId": "shop_1" } } ]Only coupons where
redeemed === falseandvalidUntil > noware returned. -
200 OK with
[]- User has no active coupons.
-
400 Bad Request
userIdmissing.
-
500 Internal Server Error
- Error while querying coupons.
PUT /user/{userId}/coupon/{couponId} – Redeem Coupon
Mark a specific user coupon as redeemed.
- Method:
PUT - Path:
/v1/user/{userId}/coupon/{couponId} - Auth:
Authorization: Bearer <JWT_TOKEN>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| userId | string | yes | Identifier of the user. |
| couponId | string | yes | Internal coupon SK id. |
Request Body
{
"userId": "user_123",
"SK": "COUPON#abc123"
}The API currently expects the coupon
SKin the body; thecouponIdin the path is used for validation/logical routing.
Response
-
200 OK
{ "redeemed": true } -
400 Bad Request
userIdmissing.
-
500 Internal Server Error
- Error updating the coupon.
GET /user/{userId}/points – Get User Points
For a detailed conceptual guide, also see Get User Points.
Return the current loyalty point balance for a user.
- Method:
GET - Path:
/v1/user/{userId}/points - Auth:
Authorization: Bearer <JWT_TOKEN>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| userId | string | yes | Identifier of the user. |
Response
-
200 OK (user exists)
{ "userId": "user_123", "points": 1200, "userExists": true } -
200 OK (user not found)
{ "userId": "user_123", "points": 0, "userExists": false } -
400 Bad Request
userIdmissing.
-
500 Internal Server Error
- Error while loading the loyalty item.
GET /user/{userId}/history – Get Reward History
Return the event-based reward history (events, redemptions, challenges, streak-related items) for a user.
- Method:
GET - Path:
/v1/user/{userId}/history - Auth:
Authorization: Bearer <JWT_TOKEN>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| userId | string | yes | Identifier of the user. |
Query Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sort | string | no | desc | asc or desc by timestamp. |
| showAll | boolean | no | false | If false, filter out entries with points === 0. |
Response
-
200 OK
[ { "id": "EVENT#purchase_completed#1730818799123", "timestamp": 1730818799123, "type": "EVENT", "points": 50, "image": "https://example.com/icons/purchase.png", "description": { "de": "Du hast deinen ersten Einkauf getätigt", "en": "You made your first purchase" }, "title": { "de": "Event abgeschlossen", "en": "Event completed" }, "userId": "user_123", "globalEventDateKey": "2025-11-05" } ] -
204 No Content
- No history entries for the user.
-
400 Bad Request
userIdmissing.
-
500 Internal Server Error
- Error while querying history.
PUT /user/{userId}/notifications/overview – Mark Overview as Viewed
Mark a notification screen as viewed by the user (affects unseen flags).
- Method:
PUT - Path:
/v1/user/{userId}/notifications/overview - Auth:
Authorization: Bearer <JWT_TOKEN>
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| userId | string | yes | Identifier of the user. |
Request Body
{
"screenId": "rewards"
}- screenId
string(required): One of"rewards","challenges","surveys".
Responses
-
200 OK
{ "message": "Screen viewed successfully" } -
400 Bad Request
screenIdmissing or invalid.
-
500 Internal Server Error
- Error updating the user profile.
GET /user/{userId}/notifications/overview – Get Notifications Overview
Return whether the user has unseen challenges, rewards, or surveys.
- Method:
GET - Path:
/v1/user/{userId}/notifications/overview - Auth:
Authorization: Bearer <JWT_TOKEN>
Response
-
200 OK
{ "userId": "user_123", "hasUnseenChallenges": true, "hasUnseenRewards": false, "hasUnseenSurveys": true }
If the user profile does not exist yet, all flags are false.
PUT /user/{userId}/notifications/change – Reset Notification Counters
Reset which notification sections are considered “viewed” for a user.
- Method:
PUT - Path:
/v1/user/{userId}/notifications/change - Auth:
Authorization: Bearer <JWT_TOKEN>
Request Body
{
"resetChallenges": true,
"resetRewards": false,
"resetSurveys": true
}- Each field is optional; when
true, the corresponding last-viewed marker is cleared.
Response
-
200 OK
{ "message": "User viewed screens reset successfully" }
GET /user/{userId}/offer/feed – Get Offer Feed
Return an offer feed for the user (e.g. local vs online offers).
- Method:
GET - Path:
/v1/user/{userId}/offer/feed - Auth:
Authorization: Bearer <JWT_TOKEN>
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | no | Offer type, e.g. LOCAL or ONLINE |
Response
-
200 OK
[ { "offerId": "offer_123", "title": "Free Coffee", "description": "Get a free coffee with any purchase", "type": "LOCAL", "points": 100, "shopId": "shop_1" } ]
The exact shape matches your offer model; the feed is filtered by type if provided.
POST /user/{userId}/offer/{offerId}/redeem – Redeem Offer
Redeem an offer for a user (deducts points and logs an event).
- Method:
POST - Path:
/v1/user/{userId}/offer/{offerId}/redeem - Auth:
Authorization: Bearer <JWT_TOKEN>(strict for certain hosts like VAG)
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| userId | string | yes | Identifier of the user. |
| offerId | string | yes | Identifier of the offer. |
Request Body
The body is passed through to the redeem handler; include any required metadata (e.g. quantity, context).
{
"metadata": {
"source": "app"
}
}Response
-
200 OK
- Offer redeemed successfully; response mirrors your redeem handler payload.
-
400 / 401 / 403 / 404
- Standard error responses for invalid offer, insufficient permissions, or missing user.
GET /user/{userId}/offer/{offerId}/redeemable – Check Offer Redeemable
Check whether a user can redeem a specific offer (points, status, limits).
- Method:
GET - Path:
/v1/user/{userId}/offer/{offerId}/redeemable - Auth:
Authorization: Bearer <JWT_TOKEN>
Response
Returns a structured object from checkOfferIsRedeemable indicating whether the offer is redeemable and why (e.g. not enough points, expired, already redeemed).
GET /user/{userId}/survey – Get Available Surveys
List all currently active surveys that the user has not answered yet.
- Method:
GET - Path:
/v1/user/{userId}/survey - Auth:
Authorization: Bearer <JWT_TOKEN>
Response
-
200 OK
[ { "surveyId": "nps_2025_01", "title": "How likely are you to recommend us?", "type": "SINGLE", "validFrom": 1730438400000, "validUntil": 1733116799000, "options": [ { "optionId": "1", "label": "Not likely" }, { "optionId": "10", "label": "Very likely" } ] } ] -
200 OK with
[]- No available surveys.
PUT /user/{userId}/survey/{surveyId} – Submit Survey Response
Submit responses for a survey, optionally awarding points.
- Method:
PUT - Path:
/v1/user/{userId}/survey/{surveyId} - Auth:
Authorization: Bearer <JWT_TOKEN>
Request Body
{
"userId": "user_123",
"surveyId": "nps_2025_01",
"responses": ["10"]
}- userId
string(required): Must match the path user. - surveyId
string(required): Must match the path survey. - responses
string[](required): Array of selected option IDs.
Rules:
- Only valid option IDs are accepted; invalid options trigger 400.
- For
SINGLEsurveys, exactly one response is allowed. - Users can only answer each survey once.
Responses
-
201 Created
{ "message": "Survey response submitted successfully" } -
400 Bad Request
- Missing
userId,surveyId, orresponses. - Invalid options.
- Survey already answered.
- Missing
-
404 Not Found
- Survey does not exist.
-
500 Internal Server Error
- Error while updating statistics or points.
GET /user/{userId}/challenge – Get User Challenges
Return the list of challenges for a user, including progress and completion state.
- Method:
GET - Path:
/v1/user/{userId}/challenge - Auth:
Authorization: Bearer <JWT_TOKEN>
Query Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| completed | boolean | no | - | true for completed, false for open, omitted = all |
Response
-
200 OK
{ "challenges": [ { "challengeId": "Favori-iY6sx", "status": "ACTIVE", "goal": 1, "points": 15, "title": { "de": "Stammstrecke: Favorisiere eine Verbindung", "en": "Go-to route: favor a route connection" }, "type": "FIXED", "progress": 0, "completed": false, "completedDate": null, "seriesId": "BikeSeries-JE3ew", "isLastStep": false } ], "lastEvaluatedKey": null } -
400 Bad Request
userIdmissing.
-
500 Internal Server Error
- Error while fetching challenges or progress.
GET /user/{userId}/challenge/{challengeId} – Get Specific Challenge
Retrieve a single challenge by id for a user (including progress).
- Method:
GET - Path:
/v1/user/{userId}/challenge/{challengeId} - Auth:
Authorization: Bearer <JWT_TOKEN>
This is a convenience wrapper around the same data used in the list endpoint.
GET /user/{userId}/streak – Get User Streaks
Return all streaks and their current progress for a user.
- Method:
GET - Path:
/v1/user/{userId}/streak - Auth:
Authorization: Bearer <JWT_TOKEN>
Response
-
200 OK
{ "streaks": [ { "userId": "user_123", "streakId": "daily_login", "currentStartDate": 1730438400000, "timeFrame": "daily", "currentCount": 5, "longestStreak": 14, "lastCompletedAt": "2025-11-05T00:00:00.000Z", "missedCount": 0, "completedDates": [ "2025-11-01", "2025-11-02" ], "milestonesClaimed": [7] } ], "count": 1 } -
500 Internal Server Error
- Missing
userIdor database error.
- Missing
GET /user/{userId}/streak/{streakId} – Get Specific Streak
Return details for a single streak of a user (see streak reference for schema).
- Method:
GET - Path:
/v1/user/{userId}/streak/{streakId} - Auth:
Authorization: Bearer <JWT_TOKEN>
Uses the same data as GET /user/{userId}/streak, filtered by streakId.